From 283771ff468bd9e1a60a4902a5ada4319c063073 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 15 Oct 2026 14:24:44 +0200 Subject: fix(node): a removal made at the machine is in audit.db The Node page and the CLI remove access through the loopback API, and revoke and unpin there wrote nothing to audit.db; only the signed MNP door did. A removed person's next connection then showed an auth_failed with nothing before it to explain it. Both routes now record member_revoke and member_unpin once the operation has succeeded, with the node's own account as the actor and 127.0.0.1 as the address, which tells the two doors apart. A refusal changes nothing and records nothing. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-node/tests/test_roster_pairing.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'packages/meshbay-node/tests/test_roster_pairing.py') diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py index b2799e9..e2180ba 100644 --- a/packages/meshbay-node/tests/test_roster_pairing.py +++ b/packages/meshbay-node/tests/test_roster_pairing.py @@ -845,6 +845,33 @@ async def test_unpin_endpoint_allows_repairing(tmp_path, roster): assert client.post("/api/members/bob/unpin?t=tok").status_code == 404 +async def test_a_removal_at_the_machine_is_in_the_audit_log(tmp_path, roster): + """The Node page and the CLI remove through here; the signed WebRTC door + already wrote its own row, this one wrote none.""" + from meshbay_node.audit import AuditStore + + audit = AuditStore(db_path=tmp_path / "audit.db") + await audit.open() + try: + client, _ = _ui_client(tmp_path, roster, audit_store=audit) + _, pk_ed_b64, pk_x_b64 = _keypair() + await roster.pin_identity("bob", "bob", pk_ed_b64, pk_x_b64, "code") + await roster.set_member(GROUP, "bob", ROLE_MEMBER, "active", "grenet") + + assert client.post(f"/api/members/bob/revoke?group_id={GROUP}&t=tok").status_code == 200 + assert client.post("/api/members/bob/unpin?t=tok").status_code == 200 + # A refusal changed nothing, so it records nothing. + assert client.post("/api/members/bob/unpin?t=tok").status_code == 404 + + rows = sorted(await audit.get_entries(), key=lambda e: e.id) + assert [(e.event, e.user_id, e.ip, e.group_id, e.detail) for e in rows] == [ + ("member_revoke", "grenet", "127.0.0.1", GROUP, "bob"), + ("member_unpin", "grenet", "127.0.0.1", "", "bob"), + ] + finally: + await audit.close() + + async def test_operator_surface_needs_the_session_token(tmp_path, roster): """11.5.3 applies to every one of these: they change who may hold the key.""" client, _ = _ui_client(tmp_path, roster) -- cgit v1.2.3