"""current_operator dependency (TASK-03-04, D-057). Server-side auth enforcement: every `/api/operator/*` protected route uses `Depends(current_operator)`. The dependency NEVER trusts the client (D-057) — it reads the signed-cookie session, fetches the operator from Postgres, and 401s on any gap (missing/invalid/expired cookie, unknown id, inactive operator). The cookie is the authz *token*; the Postgres lookup is the authz *decision*. """ from __future__ import annotations from fastapi import HTTPException, Request, status from server.auth.models import Operator async def current_operator(request: Request) -> Operator: """Resolve the authenticated operator from the signed-cookie session. Raises 401 on: missing session, missing operator_id, no Postgres store (503 actually — operator tier unavailable), unknown operator id, or an inactive operator (session is cleared in the latter case so the client cookie is invalidated). """ pg_store = getattr(request.app.state, "pg_store", None) if pg_store is None: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="operator tier unavailable (no Postgres)", ) session = request.session op_id = session.get("operator_id") if session else None if not op_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="not authenticated", ) row = await pg_store.get_operator_by_id(op_id) if row is None or not row.get("is_active"): # Inactive/unknown → clear the session so the cookie is invalidated. if session: session.clear() raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="not authenticated", ) return Operator( id=str(row["id"]), username=row["username"], display_name=row.get("display_name"), role=row.get("role", "operator"), ) __all__ = ["current_operator"]