from dataclasses import dataclass from uuid import UUID import logging from fastapi import Depends, HTTPException, status from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession from app.auth.keycloak_dependencies import get_current_keycloak_payload from app.infra.db.metadb.database import get_metadata_session from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository logger = logging.getLogger(__name__) async def get_metadata_repository( session: AsyncSession = Depends(get_metadata_session), ) -> MetadataRepository: return MetadataRepository(session) def _keycloak_sub_from_payload(payload: dict) -> UUID: sub = payload.get("sub") if not sub: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing subject claim", headers={"WWW-Authenticate": "Bearer"}, ) try: return UUID(str(sub)) except ValueError as exc: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid subject claim", headers={"WWW-Authenticate": "Bearer"}, ) from exc def _username_from_payload(payload: dict) -> str | None: username = payload.get("preferred_username") or payload.get("username") return str(username) if username else None def _email_from_payload(payload: dict) -> str | None: email = payload.get("email") return str(email) if email else None async def get_current_metadata_user( keycloak_payload: dict = Depends(get_current_keycloak_payload), metadata_repo: MetadataRepository = Depends(get_metadata_repository), ): keycloak_sub = _keycloak_sub_from_payload(keycloak_payload) try: user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub) except SQLAlchemyError as exc: logger.error( "Metadata DB error while resolving current user", exc_info=True, ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Metadata database error: {exc}", ) from exc if not user or not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user" ) try: user = await metadata_repo.refresh_user_keycloak_snapshot( user, username=_username_from_payload(keycloak_payload), email=_email_from_payload(keycloak_payload), ) except SQLAlchemyError as exc: logger.error( "Metadata DB error while refreshing current user snapshot", exc_info=True, ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Metadata database error: {exc}", ) from exc return user async def get_current_metadata_admin( user=Depends(get_current_metadata_user), ): if user.is_superuser or user.role == "admin": return user raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required" ) @dataclass(frozen=True) class _AuthBypassUser: id: UUID = UUID(int=0) role: str = "admin" is_superuser: bool = True is_active: bool = True