""" MeshBay Node — local administration web UI (localhost:18000). FastAPI app providing: - Dashboard: node status, connected peers, group overview - Groups: file listing, shared directory info - Peers: connected WebRTC/QUIC clients - Audit log: IP + action log for legal compliance - API endpoints for all data (JSON) Served only on 127.0.0.1 — not exposed to the network. Gated by a per-run session token (11.5.3) — printed at daemon startup. """ import asyncio import base64 import json import logging import time from html import escape from pathlib import Path from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Query from fastapi.responses import HTMLResponse, JSONResponse from meshbay_node import __version__ from meshbay_node import ops from meshbay_node.config import DEFAULT_CONFIG_PATH from meshbay_node.indexer.indexer import DirectoryIndexer from meshbay_common.crypto import generate_gek, wrap_gek_aes from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR log = logging.getLogger(__name__) def _op(coro): """ Run an operation and translate its refusal into a JSON response. The operations live in `meshbay_node.ops` and know nothing about HTTP. This is the whole of the HTTP adapter: without it each handler would carry its own status codes, and the MNP handler in Stage B3 would carry a second set that slowly stopped agreeing. """ async def run(): try: return await coro() except ops.OpError as e: return JSONResponse(e.as_dict(), e.status) return run() def create_ui_app(state: dict) -> FastAPI: app = FastAPI( title="MeshBay Node Admin", version=__version__, docs_url=None, redoc_url=None, ) @app.middleware("http") async def _require_session_token(request, call_next): """ Gate the admin UI behind a per-run token (11.5.3). "localhost only" is weaker than it sounds: any process on the machine can reach it, and a page in the operator's browser can reach it too via DNS rebinding. Since this API can re-initialise a group's GEK and read the audit log, an unauthenticated loopback service is a privilege boundary waiting to be crossed. The token is printed at startup and accepted as ?t= or the X-MeshBay-Token header. """ from fastapi.responses import PlainTextResponse token = state.get("ui_token") if token: supplied = (request.query_params.get("t") or request.headers.get("X-MeshBay-Token")) if supplied != token: return PlainTextResponse("Forbidden", status_code=403) return await call_next(request) @app.middleware("http") async def _security_headers(request, call_next): """ Defence in depth behind the escaping fixes for H2. This UI is unauthenticated on loopback, so script execution here equals full control of the node admin API. Note what this does and does not do: the page relies on inline """ def _render_audit_page(token: str = "") -> str: return _AUDIT_HTML.replace("__TOKEN__", json.dumps(token)) _AUDIT_HTML = """ MeshBay Node — Audit Log

Audit Log

TimeEventUserIPGroupDetail
"""