diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc/admission.py | 28 |
1 files changed, 27 insertions, 1 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/admission.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/admission.py index baa8001..3a5ec0b 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/admission.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/admission.py @@ -339,6 +339,11 @@ class AdmissionMixin: role=invite["role"], status="active", approved_by=invite["created_by"], ) + # As `_pin_and_admit` does for a key paired fresh: without it, a + # member who pairs as the operator is refused every admin + # operation until the node restarts. + if invite["role"] == ROLE_OPERATOR: + self._ctx["has_admin_authority"] = True self._audit_join( "join_pinned", f"group={invite['group_id'][:8]} role={invite['role']} " @@ -352,7 +357,7 @@ class AdmissionMixin: return await self._join_ok( user_id, pk_x_raw, session_group, - role=member["role"] if member else "", + role=await self._announced_role(roster, user_id, member["role"]), recognised=True, ) return @@ -800,6 +805,27 @@ class AdmissionMixin: log.info("Identity pinned (%s): user=%s role=%s", via, user_id[:8], role) self._audit_join("join_pinned", f"role={role} via={via}") + @staticmethod + async def _announced_role(roster, user_id: str, role: str) -> str: + """ + The role the join result names: `operator` for an account holding the + node-wide operator row, whatever its row in this group says. + + Operator authority is node-wide and checked on every admin operation's + signature (`_verify_admin_sig`), not taken from this answer — which only + tells the client what to offer. An operator who was a member of the group + before pairing has both rows, and naming the group's one hid the + authority: the page and the CLI refused, before asking, deletions the node + would have accepted. + """ + if role == ROLE_OPERATOR: + return role + node_wide = await roster.get_member("", user_id) + if node_wide and node_wide.get("role") == ROLE_OPERATOR \ + and node_wide.get("status") == "active": + return ROLE_OPERATOR + return role + async def _join_ok( self, user_id: str, pk_x_raw: bytes, group_id: str, *, role: str, recognised: bool, |