diff options
Diffstat (limited to 'packages/meshbay-node/tests')
| -rw-r--r-- | packages/meshbay-node/tests/test_roster_pairing.py | 48 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_webrtc_transport.py | 10 |
2 files changed, 40 insertions, 18 deletions
diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py index 11704ce..d13225d 100644 --- a/packages/meshbay-node/tests/test_roster_pairing.py +++ b/packages/meshbay-node/tests/test_roster_pairing.py @@ -445,6 +445,22 @@ async def test_revoked_member_stops_receiving_the_key(tmp_path, roster): assert _last(session).get("gek") is False +# ── What a first-time joiner can know ───────────────────────────────────────── + +def test_challenge_carries_node_pk_in_source(): + """ + Belt and braces for the above: the field must be in the message the node + builds, whatever the surrounding handshake does. + """ + source = (Path(__file__).parent.parent + / "src" / "meshbay_node" / "transport" / "webrtc_server.py").read_text() + challenge = source[source.find("MNP.HANDSHAKE_CHALLENGE,"):] + challenge = challenge[:challenge.find("})")] + assert "node_pk" in challenge, ( + "the challenge must announce the node key — a first-time joiner cannot " + "learn it any other way, and join_request signs it") + + # ── Code lifetimes ──────────────────────────────────────────────────────────── async def test_invitations_outlive_pairing_codes(roster): @@ -635,35 +651,33 @@ def _run_cli(monkeypatch, tmp_path, argv, responses): def test_cli_member_commands_reach_the_right_endpoints(monkeypatch, tmp_path, capsys): - roster_reply = {"identities": [{"user_id": "u-bob", "username": "bob", - "pk_ed25519": "K", "pinned_at": "now", - "pinned_via": "code"}], - "members": [{"group_id": GROUP, "user_id": "u-bob", - "role": "member", "status": "active"}], - "invites": []} + resolved = {"user_id": "u-bob", "source": "roster"} calls = _run_cli(monkeypatch, tmp_path, ["member", "revoke", "bob"], - {"/api/roster": roster_reply, + {"/api/resolve": resolved, "revoke": {"status": "revoked", "reminder": "gek-init"}}) assert ("POST", f"/api/members/u-bob/revoke?group_id={GROUP}") in calls # The operator is told the revocation does not take back the key they hold. assert "rotate" in capsys.readouterr().out.lower() calls = _run_cli(monkeypatch, tmp_path, ["member", "unpin", "bob"], - {"/api/roster": roster_reply, "unpin": {"status": "unpinned"}}) + {"/api/resolve": resolved, "unpin": {"status": "unpinned"}}) assert ("POST", "/api/members/u-bob/unpin") in calls -def test_cli_refuses_to_act_on_someone_it_does_not_know(monkeypatch, tmp_path, capsys): - """A typo must not silently do nothing — or worse, act on the wrong person.""" - calls = _run_cli(monkeypatch, tmp_path, ["member", "revoke", "nobody"], - {"/api/roster": {"identities": [], "members": [], - "invites": []}}) - assert ("exit", 1) in calls - assert not any(method == "POST" for method, _ in calls), ( - "the CLI acted on the server despite not knowing who was meant") - assert "not pinned" in capsys.readouterr().out +def test_cli_resolves_a_name_before_acting(monkeypatch, tmp_path): + """ + The name has to be turned into an account first, and the node's own roster is + asked before the hub. A JWT carries no username, so an identity pinned without + an invitation has none — the hub fallback is what keeps it manageable. + """ + calls = _run_cli(monkeypatch, tmp_path, ["member", "revoke", "bob"], + {"/api/resolve": {"user_id": "u-bob", "source": "hub"}, + "revoke": {"status": "revoked", "reminder": "gek-init"}}) + assert ("GET", "/api/resolve?username=bob") == calls[0], ( + "the CLI must resolve the name before acting on anyone") + assert ("POST", f"/api/members/u-bob/revoke?group_id={GROUP}") in calls def test_daemon_does_not_auto_pin_keystore_key(): """ diff --git a/packages/meshbay-node/tests/test_webrtc_transport.py b/packages/meshbay-node/tests/test_webrtc_transport.py index 07bdbea..93cd3fd 100644 --- a/packages/meshbay-node/tests/test_webrtc_transport.py +++ b/packages/meshbay-node/tests/test_webrtc_transport.py @@ -1155,11 +1155,19 @@ async def test_invite_then_join_delivers_the_gek(sk_node, sk_hub, gek, shared_di assert challenge["type"] == MNP.HANDSHAKE_CHALLENGE nonce_s = base64.b64decode(challenge["nonce"]) + # Bob signs a transcript naming the node, and he cannot complete the handshake + # that would prove its key — he has no GEK yet. So he has to be able to learn + # it from the challenge; taking it from the test's own knowledge of sk_node + # would hide the fact that a real client cannot. + assert challenge["node_pk"] == pk_to_b64(sk_node.public_key()), ( + "the challenge must announce the node key to a first-time joiner") + node_pk_b64 = challenge["node_pk"] + pk_ed_b64 = pk_to_b64(sk_bob_ed.public_key()) pk_x_b64 = base64.b64encode(pk_x_raw).decode() ts = int(time.time()) transcript = join_transcript( - node_pk_b64=pk_to_b64(sk_node.public_key()), + node_pk_b64=node_pk_b64, group_id=TEST_GROUP, user_id="user-002", pk_ed25519_b64=pk_ed_b64, pk_x25519_b64=pk_x_b64, nonce_node=nonce_s, ts=ts, |