summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src')
-rw-r--r--packages/meshbay-node/src/meshbay_node/hub_client.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py34
2 files changed, 30 insertions, 8 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/hub_client.py b/packages/meshbay-node/src/meshbay_node/hub_client.py
index 334107d..d8417e3 100644
--- a/packages/meshbay-node/src/meshbay_node/hub_client.py
+++ b/packages/meshbay-node/src/meshbay_node/hub_client.py
@@ -128,8 +128,8 @@ class HubClient:
access_token = data["access_token"]
decoded = jwt.decode(access_token, hub_pk_pem, algorithms=["EdDSA"])
- assert decoded["pk_user"] == self._keys.pk_ed25519_b64, \
- "Hub returned token for wrong public key"
+ # No pk_user claim to check any more: tokens carry no key. What binds this
+ # token to this node is the Ed25519 challenge it was issued against.
assert "jti" in decoded, "Hub token missing jti — hub is outdated"
assert decoded.get("scope") == "node", \
"Expected node-scoped token"
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
index f659f56..5ea6376 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -231,7 +231,9 @@ class WebRTCPeerSession:
self._peer_id: str = peer_id
self._remote_ip: str = ""
self._username: str = ""
- self._pk_user: str = ""
+ # Set from the roster: the key this node pinned for this account. Never
+ # from the JWT — the hub picks what goes in there.
+ self._pinned_pk: str = ""
self._gek_challenge: bytes | None = None
# Same value as the GEK challenge, but kept for the life of the connection:
# a join_request is signed over it, and it must stay verifiable after the
@@ -377,7 +379,6 @@ class WebRTCPeerSession:
self._pending_sub = peer.user_id
self._pending_group = peer.group_id
self._pending_username = peer.username
- self._pending_pk_user = peer.pk_user
gctx = self._ctx["groups"][peer.group_id] if "groups" in self._ctx else self._ctx
if not gctx.get("gek"):
@@ -446,7 +447,7 @@ class WebRTCPeerSession:
self._user_id = self._pending_sub
self._group_id = self._pending_group
self._username = self._pending_username
- self._pk_user = self._pending_pk_user
+ asyncio.ensure_future(self._load_pinned_pk())
self._peer_registry()[self._user_id] = self
@@ -747,7 +748,11 @@ class WebRTCPeerSession:
roster, user_id, invite["username"] or username, pk_ed_b64, pk_x_b64,
group_id=invite["group_id"], role=invite["role"],
approved_by=invite["created_by"], via="code")
- await self._join_ok(user_id, pk_x_raw, invite["group_id"],
+ # The roster row comes from the invitation; the key comes from the
+ # connection. An operator pairing is node-wide (empty group), but they
+ # redeemed the code while opening a group and expect to read it — and
+ # is_authorized() already grants an operator every group on this node.
+ await self._join_ok(user_id, pk_x_raw, session_group or invite["group_id"],
role=invite["role"], recognised=False)
def _group_join_policy(self, group_id: str) -> str:
@@ -1168,14 +1173,22 @@ class WebRTCPeerSession:
self._register_uploader(ctx, rel_dir, filename)
def _register_uploader(self, ctx: dict, rel_dir: str, filename: str) -> None:
- """Tag the index entry with the uploader's identity after upload completes."""
+ """
+ Tag the index entry with the uploader's identity after upload completes.
+
+ The key recorded here is the one this node pinned, not the one the token
+ carried. `pk_user` was a hub-chosen claim, and it decided who could later
+ delete the file: a hub issuing a token naming its own key could delete
+ anyone's uploads on any node. Deletion is supposed to be authorized by the
+ node, and this closes the last place where it was not.
+ """
idx = ctx.get("index")
if not idx:
return
for entry in idx.entries:
if entry.name == filename and entry.path == rel_dir:
entry.uploader_id = self._user_id
- entry.uploader_pk = self._pk_user
+ entry.uploader_pk = self._pinned_pk
return
def _do_file_delete(self, msg: dict) -> None:
@@ -1242,6 +1255,15 @@ class WebRTCPeerSession:
except Exception:
return False
+ async def _load_pinned_pk(self) -> None:
+ """Remember which key this node pinned for the peer we just authenticated."""
+ roster = self._ctx.get("roster")
+ if roster is None or not self._user_id:
+ return
+ ident = await roster.get_identity(self._user_id)
+ if ident:
+ self._pinned_pk = ident["pk_ed25519"]
+
def _has_admin_authority(self) -> bool:
"""
Cheap synchronous pre-check: is there anyone who could authorize this?