diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/roster.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/roster.py | 39 |
1 files changed, 30 insertions, 9 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/roster.py b/packages/meshbay-node/src/meshbay_node/roster.py index 2288ae4..3d3a143 100644 --- a/packages/meshbay-node/src/meshbay_node/roster.py +++ b/packages/meshbay-node/src/meshbay_node/roster.py @@ -418,6 +418,16 @@ class Roster: - it is scoped to *this* group. A person in two groups on one node is not disclosed to the second by being in the first. + "Member of this group" is `_MEMBER_OF_GROUP`, shared with + `is_authorized` — **the operator belongs to every group this node + hosts**, with their authority recorded under an empty group_id. Spelling + that out a second time here is exactly what went wrong first time: + the operator was missing from the roster, so every one of their messages + reached other members as a key nobody could vouch for. + + `DISTINCT` because an operator who is *also* an explicit member of the + group matches both halves of that clause. + `add_sig`/`add_nonce`/`add_ts` are empty for a device pinned before the evidence was kept, and for the first device of any account — which an operator code admitted, not a countersignature. Both read as @@ -426,13 +436,12 @@ class Roster: """ assert self._db async with self._db.execute( - "SELECT i.user_id, i.username, i.pk_ed25519, i.pk_x25519, " - " i.added_by_pk, i.add_sig, i.add_nonce, i.add_ts, i.pinned_at " - "FROM identities i " - "JOIN members m ON m.user_id = i.user_id " - "WHERE m.group_id = ? AND m.status = 'active' " - " AND i.revoked_at IS NULL " - "ORDER BY i.user_id, i.pinned_at", (group_id,) + f"SELECT DISTINCT i.user_id, i.username, i.pk_ed25519, i.pk_x25519, " + f" i.added_by_pk, i.add_sig, i.add_nonce, i.add_ts, i.pinned_at " + f"FROM identities i " + f"JOIN members m ON m.user_id = i.user_id " + f"WHERE i.revoked_at IS NULL AND m.{self._MEMBER_OF_GROUP} " + f"ORDER BY i.user_id, i.pinned_at", (group_id,) ) as cur: rows = await cur.fetchall() return [ @@ -546,6 +555,18 @@ class Roster: async def has_operator(self) -> bool: return bool(await self.operator_pks()) + # Who counts as a member of a group, in SQL, in **one** place. + # + # The operator's authority is node-wide and is stored with an empty + # group_id, so "belongs to this group" is not `group_id = ?`. Writing that + # clause a second time is how `group_devices` came to omit the operator + # from the roster it relays — and the symptom was a member seeing "this + # account is using a key you have not seen before" on every single message + # from the person running the node. Found on a live pair of machines, not + # by a test. + _MEMBER_OF_GROUP = ("status = 'active' AND " + "(group_id = ? OR (group_id = '' AND role = 'operator'))") + async def is_authorized(self, group_id: str, user_id: str) -> bool: """ May this person be handed the group key? @@ -560,8 +581,8 @@ class Roster: """ assert self._db async with self._db.execute( - "SELECT 1 FROM members WHERE user_id = ? AND status = 'active' " - "AND (group_id = ? OR (group_id = '' AND role = 'operator')) LIMIT 1", + f"SELECT 1 FROM members WHERE user_id = ? AND {self._MEMBER_OF_GROUP} " + f"LIMIT 1", (user_id, group_id), ) as cur: return await cur.fetchone() is not None |