admin / Bid-Sentinel
publicBid Scrape and Tracking Application with AI Capability
Bid-Sentinel / bid-sentinel-v2 / backend / app / audit.py
2383 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | """Audit-trail capture: a writer + middleware that records app activity.""" from __future__ import annotations import logging from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from app.database import AsyncSessionLocal from app.models import AuditLog from app.security import decode_access_token logger = logging.getLogger("audit") _MUTATING = {"POST", "PATCH", "PUT", "DELETE"} # Paths we don't audit (noise / handled explicitly elsewhere). _SKIP_PREFIXES = ("/health", "/docs", "/openapi", "/auth/login") async def write_audit( actor: str, action: str, detail: str = "", status_code: int | None = None, ip: str | None = None, ) -> None: """Persist one audit entry. Never raises (logging must not break requests).""" try: async with AsyncSessionLocal() as db: db.add( AuditLog( actor=actor or "anonymous", action=action[:255], detail=(detail or "")[:512], status_code=status_code, ip=ip, ) ) await db.commit() except Exception as exc: # noqa: BLE001 logger.warning("Failed to write audit entry: %s", exc) def actor_from_request(request: Request) -> str: auth = request.headers.get("authorization", "") if auth.lower().startswith("bearer "): payload = decode_access_token(auth[7:]) if payload and payload.get("sub"): return payload["sub"] return "anonymous" class AuditMiddleware(BaseHTTPMiddleware): """Logs every mutating request (POST/PATCH/PUT/DELETE) with actor + status.""" async def dispatch(self, request: Request, call_next): response = await call_next(request) try: path = request.url.path if request.method in _MUTATING and not path.startswith(_SKIP_PREFIXES): ip = request.client.host if request.client else None await write_audit( actor=actor_from_request(request), action=f"{request.method} {path}", status_code=response.status_code, ip=ip, ) except Exception as exc: # noqa: BLE001 logger.warning("Audit middleware error: %s", exc) return response |