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/src/meshbay_node/ui/app.py | 36 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'packages/meshbay-node/src/meshbay_node/ui/app.py') diff --git a/packages/meshbay-node/src/meshbay_node/ui/app.py b/packages/meshbay-node/src/meshbay_node/ui/app.py index f5cd5d0..9ed014b 100644 --- a/packages/meshbay-node/src/meshbay_node/ui/app.py +++ b/packages/meshbay-node/src/meshbay_node/ui/app.py @@ -371,15 +371,47 @@ def create_ui_app(state: dict) -> FastAPI: """Map a username to an account id, through the hub.""" return await _op(lambda: ops.resolve_user(state, username)) + async def _audit_local(event: str, subject: str, group_id: str = "") -> None: + """ + Record a removal made at the machine (Node page or CLI) in audit.db. + + The signed door over WebRTC records its own (`_audit` in the session); + this one has no session. The actor is the node's own account — whoever + holds the control API's token acts as it — and the address is the + loopback, which tells the two doors apart in the log. + """ + audit = state.get("audit_store") + if not audit: + return + try: + await audit.log_event( + user_id=state.get("node_user_id") or "", + event=event, + ip="127.0.0.1", + username=getattr(getattr(state.get("config"), "hub", None), "username", "") or "", + group_id=group_id, + detail=subject, + ) + except Exception as e: + log.warning("audit: could not record %s of %s: %s", event, subject[:8], e) + @app.post("/api/members/{user_id}/revoke") async def revoke_member(user_id: str, group_id: str): """Stop serving the group key to a member.""" - return await _op(lambda: ops.revoke_member(state, user_id, group_id)) + async def run(): + result = await ops.revoke_member(state, user_id, group_id) + await _audit_local("member_revoke", user_id, group_id) + return result + return await _op(run) @app.post("/api/members/{user_id}/unpin") async def unpin_member(user_id: str): """Forget a pinned identity, so the person can pair again with a new key.""" - return await _op(lambda: ops.unpin_member(state, user_id)) + async def run(): + result = await ops.unpin_member(state, user_id) + await _audit_local("member_unpin", user_id) + return result + return await _op(run) # ── Chat encryption (operator only, localhost) ───────────────────────── -- cgit v1.2.3