diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-13 21:18:23 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-13 21:18:23 +0200 |
| commit | dd7d9c80baafe08287516fd4d486223b5199b102 (patch) | |
| tree | 5a9fd048b86d6d8794caa6d82ae4d1d7cdf4d327 /packages/meshbay-hub/src/meshbay_hub/api/groups.py | |
| parent | 729d28030746bb8b3688f85833c228acd304be61 (diff) | |
| download | meshbay-dd7d9c80baafe08287516fd4d486223b5199b102.tar.gz | |
fix(hub): a node that hosts nothing is not a free target, and a private group's hosts are its members'
Two checks that were not performed, in the same shape: something true of the
public case, written so it did not apply to the rest.
**Signaling.** The membership check read `if node_group_ids:`, so an empty set
skipped it — membership, group status and the public-group gate together — and
the offer was relayed. Since AV1 made an empty claim mean "no groups" rather
than "all of my owner's", that is the *normal* registration of a node hosting
nothing: the unconfigured node left running, the machine in this register's
founding incident, and the one least able to absorb the work. Each offer makes
it allocate an RTCPeerConnection and gather ICE, 30 a minute, which is H6
restored in exactly the case AV1 made common. It is refused now. Nothing
legitimate is lost: such a node refuses the handshake anyway — `group_id` is
mandatory (M1) and a node with no group key refuses (NS8) — so this declines
work the node would decline one step later at its own expense.
**The node list.** `GET /v1/groups/{id}/nodes` checked membership only for a
public group with public groups switched off. A private one answered any
authenticated account that knew the id — which an ex-member knows for ever —
with the ids and public keys of the machines hosting it. §7.4 already stated the
property for the public case. Membership is required now unless the group is
public and public groups are on. Nothing needs the list before joining: an open
join writes the membership row first, and an invitation registers the invitee's
when the code is created, both checked before making the change.
`test_webrtc_signaling_roundtrip` had to be repaired, and is the finding
restated: it registered a node in `_connected_nodes` and never in
`_node_groups`, so it drove the relay through the hole rather than through the
door — the check it routed around was the defect. It now registers the group a
real node registers.
§7.2 and §7.3 gain the rules, the register AV24 and AV25, and
test_availability_between_members.py two cases that fail on the property
against the previous source.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMxEQadpzPkYLFf5CYKhpW
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/groups.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/groups.py | 14 |
1 files changed, 13 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/groups.py b/packages/meshbay-hub/src/meshbay_hub/api/groups.py index b903fcf..72ba194 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/groups.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/groups.py @@ -124,7 +124,19 @@ async def group_online_nodes( # But when the hub has public groups switched off, an existing one keeps # working only for the people already in it: no non-member gets handed a # node to connect to. Members always have a row here, so they are unaffected. - if group.visibility == "public" and not await hub_settings.public_groups_allowed(db): + # + # A **private** group hands its node list to members and to nobody else. It + # used to answer any authenticated account that knew the id — which an + # ex-member knows for ever — with the ids and public keys of the machines + # hosting it. That is the "registered hub user with no membership" of §2.1 + # reaching an endpoint that did not check membership, and §7.4 already + # states the property for the public case: a non-member is handed no node. + # + # Nothing legitimate needs this before joining. An open join writes the + # membership row first (`POST /{id}/join`), and an invitation registers the + # invitee's membership when the code is created — so by the time either asks + # for a node, the row exists. + if group.visibility != "public" or not await hub_settings.public_groups_allowed(db): if not await db.get(GroupMember, (group_id, current_user.id)): raise HTTPException(status_code=403, detail="Not a member of this group") |