aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_incoming_membership.py
blob: 708e327f90203473e10837b819bb364b82503725 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""The NAT-punch signal is not a liveness oracle, and only a member reaches it.

`POST /v1/nodes/{id}/incoming` used to check nothing but the caller's own
address, then reveal whether the node was connected (404 vs 504) and, with QUIC
on, make it punch. Any authenticated account could poll it for a node's liveness
and make a stranger's node emit a UDP probe. It now requires a shared active
group with the node first — the same gate the offer relay uses — checked before
anything depends on the node's connection state, so a non-member gets one uniform
403 whether the node is connected or not.
"""

import pytest
from test_availability_between_members import (
    _add_member,
    _announce_node,
    _make_group,
    _make_user,
)


def _incoming(client, node_id, user, peer_ip="1.2.3.4", peer_port=5000):
    return client.post(f"/v1/nodes/{node_id}/incoming",
                       json={"peer_ip": peer_ip, "peer_port": peer_port},
                       headers={"Authorization": f"Bearer {user['token']}"})


@pytest.mark.asyncio
async def test_a_non_member_is_refused_whether_the_node_is_connected_or_not(client):
    from meshbay_hub.api import revocation as rev

    owner = await _make_user(client, "inc_owner")
    stranger = await _make_user(client, "inc_stranger")
    group_id = await _make_group(client, owner, "inc-group")
    node_id = await _announce_node(client, owner)

    # Node NOT in the connected registry — the stranger gets the membership 403
    # (not the connection 404), so the answer says nothing about whether the node
    # is up. The detail is what distinguishes it from the peer_ip refusal that a
    # request without the gate would give.
    r_off = await _incoming(client, node_id, stranger)
    assert r_off.status_code == 403
    assert "member" in r_off.json()["detail"]

    # Node connected and serving the group — the stranger, not a member, still gets
    # the membership 403, and never reaches the punch or the connection-state answer.
    rev._connected_nodes[node_id] = object()
    rev._node_groups[node_id] = [group_id]
    try:
        r_on = await _incoming(client, node_id, stranger)
        assert r_on.status_code == 403
        assert "member" in r_on.json()["detail"]
    finally:
        rev._connected_nodes.pop(node_id, None)
        rev._node_groups.pop(node_id, None)


@pytest.mark.asyncio
async def test_a_member_passes_the_membership_gate(client):
    """A member is not turned away by the gate. (It then reaches the connection
    check — 404 here, since no real node socket is registered — never 403.)"""
    from meshbay_hub.api import revocation as rev

    owner = await _make_user(client, "inc2_owner")
    member = await _make_user(client, "inc2_member")
    group_id = await _make_group(client, owner, "inc2-group")
    await _add_member(client, owner, group_id, member)
    node_id = await _announce_node(client, owner)

    rev._node_groups[node_id] = [group_id]   # registered/hosted, but no live socket
    try:
        r = await _incoming(client, node_id, member)
        # Past the membership gate: the refusal, if any, is about the connection
        # or the peer address, never "not a member of any group on this node".
        assert r.status_code != 403 or "member" not in r.json().get("detail", "")
    finally:
        rev._node_groups.pop(node_id, None)