trading/services/api_gateway/auth/middleware.py

81 lines
2.4 KiB
Python
Raw Normal View History

"""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
_DEV_USER = {
"sub": "00000000-0000-0000-0000-000000000000",
"username": "dev",
"type": "access",
}
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.
When ``config.dev_mode`` is ``True``, authentication is bypassed and a
synthetic dev user is returned.
"""
if config.dev_mode:
return _DEV_USER
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"},
)
if payload.get("type") != "access":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token type",
headers={"WWW-Authenticate": "Bearer"},
)
return payload