"""Audit middleware: log all mutating requests to AuditLog.""" import json from fastapi import Request from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import Response from app.database import async_session from app.models.audit import AuditLog from app.services.auth_service import decode_access_token, get_user_by_id class AuditMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next) -> Response: if request.method in ("POST", "PUT", "PATCH", "DELETE"): response = await call_next(request) await self._log(request, response) return response return await call_next(request) async def _log(self, request: Request, response: Response): try: user_id = "anonymous" username = "anonymous" auth_header = request.headers.get("authorization") if auth_header and auth_header.startswith("Bearer "): try: payload = decode_access_token(auth_header[7:]) async with async_session() as db: user = await get_user_by_id(db, payload["sub"]) if user: user_id = user.id username = user.username except Exception: pass path = request.url.path action = f"{request.method} {path}" detail = {} if response.status_code >= 400: body = b"" try: body = await request.body() if body: detail["request_body"] = json.loads(body) except Exception: pass async with async_session() as db: log = AuditLog( user_id=user_id, username=username, action=action, target=path, detail=json.dumps(detail) if detail else None, ip_address=request.client.host if request.client else None, ) db.add(log) await db.commit() except Exception: pass