aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-common')
-rw-r--r--packages/meshbay-common/src/meshbay_common/handshake.py6
-rw-r--r--packages/meshbay-common/tests/test_handshake.py34
2 files changed, 39 insertions, 1 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/handshake.py b/packages/meshbay-common/src/meshbay_common/handshake.py
index 65b4e85..4d2cac2 100644
--- a/packages/meshbay-common/src/meshbay_common/handshake.py
+++ b/packages/meshbay-common/src/meshbay_common/handshake.py
@@ -278,7 +278,11 @@ def authorize_token(
raise HandshakeError("Not a member of this group", code="not_a_member")
if hosted_groups is not None and group_id not in hosted_groups:
- raise HandshakeError("Group not hosted on this node")
+ # Coded, because a client handed several nodes for one group has to tell
+ # "this node cannot serve it, try the next one" apart from "you, in this
+ # browser, must do something first". Uncoded it was neither, and a node
+ # wrongly registered for a group took that group down for everyone.
+ raise HandshakeError("Group not hosted on this node", code="not_hosted")
return AuthorizedPeer(
user_id=user_id,
diff --git a/packages/meshbay-common/tests/test_handshake.py b/packages/meshbay-common/tests/test_handshake.py
index 8981db8..d4f6d2b 100644
--- a/packages/meshbay-common/tests/test_handshake.py
+++ b/packages/meshbay-common/tests/test_handshake.py
@@ -223,3 +223,37 @@ def test_membership_refusal_carries_a_code_a_client_can_act_on():
with pytest.raises(HandshakeError) as excinfo:
authorize_token(token, pem_pub, group_id="g" * 32)
assert excinfo.value.code == "not_a_member"
+
+
+def test_a_group_this_node_does_not_host_is_refused_with_a_code():
+ """
+ A client is handed every node the hub registered for a group, and only some
+ of them may be able to serve it. Telling "try the next node" apart from
+ "you, here, must do something first" is what this code is for: without it
+ the client either stopped at the first refusal — which is how a group went
+ dark on 2026-09-11 with its real host online — or had to match on wording.
+ """
+ import jwt as _jwt
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+ from cryptography.hazmat.primitives import serialization
+
+ sk = Ed25519PrivateKey.generate()
+ pem_priv = sk.private_bytes(
+ serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8,
+ serialization.NoEncryption())
+ pem_pub = sk.public_key().public_bytes(
+ serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo)
+
+ group = "g" * 32
+ token = _jwt.encode(
+ {"sub": "u1", "jti": "j1", "scope": "user", "groups": [group]},
+ pem_priv, algorithm="EdDSA")
+
+ # A member of the group, on a node that does not host it.
+ with pytest.raises(HandshakeError) as excinfo:
+ authorize_token(token, pem_pub, group_id=group, hosted_groups={"other"})
+ assert excinfo.value.code == "not_hosted"
+
+ # And the node that does host it still lets them in.
+ peer = authorize_token(token, pem_pub, group_id=group, hosted_groups={group})
+ assert peer.group_id == group