CliniqAl / app /api /middleware /audit.py
someonesphantom's picture
added foundaiton
ac6eeec
Raw
History Blame Contribute Delete
1.48 kB
"""Audit middleware — every state-mutating request gets a row in audit_log.
Owner: Person C. Read-only GET/HEAD requests are skipped to keep the table lean.
"""
from __future__ import annotations
import time
import uuid
from typing import Awaitable, Callable
import structlog
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
log = structlog.get_logger(__name__)
_READONLY = {"GET", "HEAD", "OPTIONS"}
class AuditMiddleware(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
rid = uuid.uuid4().hex[:12]
t0 = time.perf_counter()
request.state.request_id = rid
response = await call_next(request)
if request.method not in _READONLY:
dt_ms = round((time.perf_counter() - t0) * 1000, 1)
actor = request.headers.get("X-API-Key", "anonymous")[:8]
log.info(
"audit",
rid=rid,
actor=actor,
method=request.method,
path=request.url.path,
status=response.status_code,
ms=dt_ms,
)
# TODO(Person C): persist to AuditLog table — opening a session
# in middleware needs a bit of care; do it on Day 10.
response.headers["X-Request-ID"] = rid
return response