from typing import Annotated from anydi.ext.fastapi import Inject from fansifter_common.api.security import authenticate_identity from fansifter_common.auth.exceptions import NotAuthenticated from fansifter_common.auth.identity import Identity from fansifter_common.constants import ( DEFAULT_BRAND, HEADER_ORCHARD_IDENTITY_ID, PROD_ENVIRONMENT, ) from fastapi import Depends, Security from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer from jwtauth import JWTAuth from jwtauth.utils import get_default_issuer, get_default_jwks_url from starlette.requests import Request from dmp.config import Settings FAN_RESPONSE_QA_AUDIENCE = "https://qa-fan-response-jwt-authorizer" FAN_RESPONSE_PROD_AUDIENCE = "https://fan-response-jwt-authorizer" bearer_auth = HTTPBearer( scheme_name="JWT authorization", auto_error=False, ) orchard_identity_id_auth = APIKeyHeader( name=HEADER_ORCHARD_IDENTITY_ID, scheme_name="OrchardIdentityId", auto_error=False, ) def jwt_auth_enabled(settings: Annotated[Settings, Inject()]) -> bool: return settings.jwt_auth_enabled async def get_identity( request: Request, credentials: Annotated[HTTPAuthorizationCredentials | None, Security(bearer_auth)], orchard_identity_id: Annotated[str | None, Security(orchard_identity_id_auth)], jwt_auth_enabled: Annotated[bool, Depends(jwt_auth_enabled)], jwt_auth: Annotated[JWTAuth, Inject()], ) -> Identity: # Local/dev only header authentication if not jwt_auth_enabled: if not orchard_identity_id: raise NotAuthenticated return Identity( id=orchard_identity_id, brand=DEFAULT_BRAND, is_internal_employee=False, ) return await authenticate_identity( request, credentials=credentials, jwt_auth=jwt_auth ) async def get_identity_id(identity: Annotated[Identity, Depends(get_identity)]) -> str: return identity.id IdentityId = Annotated[str, Depends(get_identity_id)] def _get_fan_response_jwt_auth(settings: Settings) -> JWTAuth: if settings.environment == PROD_ENVIRONMENT: audience = FAN_RESPONSE_PROD_AUDIENCE else: audience = FAN_RESPONSE_QA_AUDIENCE return JWTAuth( jwks_url=get_default_jwks_url(settings.environment), audience=[audience], issuer=get_default_issuer(settings.environment), ) async def validate_fan_response_jwt( credentials: Annotated[HTTPAuthorizationCredentials | None, Security(bearer_auth)], jwt_auth_enabled: Annotated[bool, Depends(jwt_auth_enabled)], settings: Annotated[Settings, Inject()], ) -> None: if not jwt_auth_enabled: return if not credentials: raise NotAuthenticated("Missing Authorization header.") fan_response_jwt = _get_fan_response_jwt_auth(settings) try: await fan_response_jwt.aget_token(credentials.credentials) except Exception as exc: raise NotAuthenticated("Invalid JWT token.") from exc