Files
praxis/server/auth/dependencies.py
T
Praxis CI 00e39a3f85 feat(milestone): merge phase/01 operator-foundation → milestone/v0.4-operator-tier
Phase 1 complete — Operator Foundation:
- Postgres 16 in Docker-in-LXC (asyncpg pool, 5-table schema, PgStore, migrations)
- Operator auth (argon2id, signed stateless cookies, slowapi 5/min rate limit)
- VC issuer key migration SQLite→Postgres (archive-before-active, R-VC-MIG-01)
- Operator bootstrap CLI (create-operator.py, idempotent)
- Backup cron script + G-008 restore drill
- Graceful degradation (server starts without Postgres)
- 272 tests pass, 33 skip (Postgres-requiring), 0 fail

---ci---
project: praxis
phase: 1
milestone: v0.4
status: complete
requirements:
  covered: [REQ-MT-01, REQ-AUTH-01, REQ-NFR-AUTH-01, REQ-NFR-MT-01, REQ-MT-02]
  partial: []
---/ci---
2026-08-04 01:41:06 +00:00

56 lines
2.0 KiB
Python

"""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"]