""" Auth dependencies for the MealPlanner backend. Two flavors: - ``require_admin`` — bearer token for ``/api/admin/*`` routes; token compared to ``settings.ADMIN_TOKEN`` (must be set in env). - ``require_session`` — signed-cookie session (``itsdangerous``) gating mutations on the family-facing routers; reads stay open inside the trusted network. The per-voter approval-token flow on meal items is intentionally NOT covered here — it has its own short-lived single-use tokens elsewhere. """ from fastapi import HTTPException, Request, status from itsdangerous import BadSignature, SignatureExpired, TimestampSigner from app.config import settings bearer_header = "Authorization" SESSION_COOKIE = "mp_session" SESSION_MAX_AGE = 60 * 60 * 24 * 30 # 30 days def require_admin(request: Request) -> None: """Enforce a shared bearer token. 401 on bad/missing token, 503 if unset.""" expected = settings.ADMIN_TOKEN if not expected: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Admin auth not configured", ) auth = request.headers.get(bearer_header, "") if not auth.startswith("Bearer ") or auth[7:] != expected: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid admin token", ) def _signer() -> TimestampSigner: return TimestampSigner(settings.SECRET_KEY) def issue_session(family_profile_id: str) -> str: """Sign the family_profile_id and return the cookie value.""" return _signer().sign(family_profile_id.encode()).decode() def require_session(request: Request) -> str: """Return the family_profile_id stored in the signed session cookie.""" raw = request.cookies.get(SESSION_COOKIE) if not raw: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Session required" ) try: family_id = ( _signer().unsign(raw.encode(), max_age=SESSION_MAX_AGE).decode() ) except SignatureExpired: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Session expired" ) except BadSignature: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session" ) return family_id