"""Auth middleware — FastAPI dependency for JWT-based authentication.""" from __future__ import annotations from fastapi import Depends, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer import jwt as pyjwt from services.api_gateway.auth.jwt import decode_token from services.api_gateway.config import ApiGatewayConfig # Shared config instance — injected via FastAPI dependency override in tests. _config: ApiGatewayConfig | None = None security = HTTPBearer(auto_error=False) def get_config() -> ApiGatewayConfig: """Return the singleton config. Overridden in tests.""" global _config if _config is None: _config = ApiGatewayConfig() return _config async def get_current_user( credentials: HTTPAuthorizationCredentials | None = Depends(security), config: ApiGatewayConfig = Depends(get_config), ) -> dict: """FastAPI dependency that extracts and validates a Bearer JWT. Returns the decoded token payload (contains ``sub``, ``username``, etc.) on success. Raises a 401 ``HTTPException`` for missing, expired, or invalid tokens. """ if credentials is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing authorization header", headers={"WWW-Authenticate": "Bearer"}, ) token = credentials.credentials try: payload = decode_token(token, config) except pyjwt.ExpiredSignatureError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired", headers={"WWW-Authenticate": "Bearer"}, ) except pyjwt.InvalidTokenError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", headers={"WWW-Authenticate": "Bearer"}, ) # Ensure it is an access token, not a refresh token if payload.get("type") != "access": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type", headers={"WWW-Authenticate": "Bearer"}, ) return payload