From 8d564fbbe5ec302f39f359577c63d2150fd7c5a1 Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 3 Mar 2026 08:03:49 -0800 Subject: [PATCH] feat(sofiia-console): add internal audit ingest endpoint for trusted services Adds POST /api/audit/internal authenticated via X-Internal-Service-Token header (SOFIIA_INTERNAL_TOKEN env). Allows matrix-bridge-dagi and other internal services to write audit events without team keys. Reuses existing audit_log() + db layer. Made-with: Cursor --- services/sofiia-console/app/main.py | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/services/sofiia-console/app/main.py b/services/sofiia-console/app/main.py index c184d8c0..80f89078 100644 --- a/services/sofiia-console/app/main.py +++ b/services/sofiia-console/app/main.py @@ -3783,6 +3783,51 @@ async def api_audit_list( } +# ── Internal Audit Ingest (trusted services only) ───────────────────────────── + +_INTERNAL_TOKEN: str = os.getenv("SOFIIA_INTERNAL_TOKEN", "") + + +class _InternalAuditPayload(BaseModel): + event: str + operator_id: str = "internal" + chat_id: Optional[str] = None + node_id: Optional[str] = None + agent_id: Optional[str] = None + status: str = "ok" + error_code: Optional[str] = None + duration_ms: Optional[int] = None + data: Dict[str, Any] = {} + + +@app.post("/api/audit/internal") +async def api_audit_internal_ingest( + request: Request, + payload: _InternalAuditPayload, +): + """ + Internal audit ingest for trusted services (e.g. matrix-bridge-dagi). + Auth: X-Internal-Service-Token header must match SOFIIA_INTERNAL_TOKEN. + Not accessible by team keys or Telegram — internal network only. + """ + token = request.headers.get("X-Internal-Service-Token", "") + if not _INTERNAL_TOKEN or not token or token != _INTERNAL_TOKEN: + raise HTTPException(status_code=401, detail="invalid internal token") + + await audit_log(AuditEvent( + event=payload.event, + operator_id=payload.operator_id, + chat_id=payload.chat_id, + node_id=payload.node_id, + agent_id=payload.agent_id, + status=payload.status, + error_code=payload.error_code, + duration_ms=payload.duration_ms, + data=payload.data, + )) + return {"ok": True, "event": payload.event} + + @app.get("/metrics") def metrics(): data, content_type = render_metrics()