aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py48
-rw-r--r--packages/meshbay-node/tests/test_webrtc_transport.py10
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,