import hmac from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from ..agent_auth import credential_from_token from ..errors import error_response from ..settings import get_settings NO_AUTH_PATHS = {"/api/v1/health", "/api/v1/ready", "/metrics"} AGENT_PATHS = {"/api/v1/heartbeat", "/api/v1/events"} def _match_token(presented: str, accepted: list[str]) -> bool: matched = False # Constant-time across all accepted tokens to avoid timing oracle. for t in accepted: if hmac.compare_digest(presented, t): matched = True return matched class BearerAuthMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): path = request.url.path if path in NO_AUTH_PATHS: return await call_next(request) header = request.headers.get("authorization") if not header or not header.lower().startswith("bearer "): return error_response(request, 401, "unauthorized", "authentication required") token = header.split(" ", 1)[1].strip() if path in AGENT_PATHS: if len(token) < 32 or len(token) > 512: return error_response(request, 401, "unauthorized", "authentication required") request.state.agent_credential = credential_from_token(token) else: if not _match_token(token, get_settings().auth_tokens): return error_response(request, 401, "unauthorized", "authentication required") return await call_next(request)