"""Auth guards for ``/internal/*``. Active layer: Bearer token, presented by the operator's CLI and matched against the value in the secrets dir. Token rotation = update the file, restart the container. :func:`require_localhost` is preserved but unused by default — it fights the Docker bridge network model (the container sees the gateway IP, not 127.0.0.1, regardless of where traffic originated). Re-enable it only if the API runs in ``network_mode: host``. """ from __future__ import annotations import hmac from typing import Optional from fastapi import HTTPException, Request, status from app.config import get_settings def require_localhost(request: Request) -> None: """Reject the request unless the connecting peer is loopback. ``request.client.host`` reflects the actual TCP peer (the nginx upstream connecting from 127.0.0.1) when ``proxy_set_header`` is used appropriately. We deliberately do NOT trust ``X-Forwarded-For`` here — we want the raw peer. """ peer = request.client.host if request.client else None if peer not in {"127.0.0.1", "::1"}: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Not found", ) def require_bearer_token(request: Request) -> None: """Verify ``Authorization: Bearer ``. Uses constant-time comparison so timing leaks don't reveal token prefixes. The 401 deliberately doesn't echo the supplied token or leak whether a token is configured at all — clients should treat "no token configured" the same as "wrong token". """ settings = get_settings() expected: Optional[str] = settings.resolve_admin_token() if not expected: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Server not configured for internal access.", ) auth = request.headers.get("Authorization", "") if not auth.startswith("Bearer "): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Bearer token required.", ) presented = auth.removeprefix("Bearer ").strip() if not hmac.compare_digest(presented, expected): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token.", )