from fastapi import Depends, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.auth.jwt import decode_token from app.config import settings from app.db import get_db_session from app.models.user import User bearer_scheme = HTTPBearer(auto_error=False) async def _get_or_create_dev_user(session: AsyncSession) -> User: """Return or create the fixed development user.""" result = await session.execute( select(User).where(User.authentik_sub == "dev-user") ) user = result.scalar_one_or_none() if user is None: user = User( authentik_sub="dev-user", email="dev@localhost", display_name="Dev User", is_active=True, ) session.add(user) await session.commit() await session.refresh(user) return user async def get_current_user( token: HTTPAuthorizationCredentials | None = Depends(bearer_scheme), session: AsyncSession = Depends(get_db_session), ) -> User: if token is None: if settings.debug and settings.auth_dev_bypass: return await _get_or_create_dev_user(session) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"}, ) try: claims = decode_token(token.credentials) except Exception as exc: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Invalid token: {exc}", headers={"WWW-Authenticate": "Bearer"}, ) from exc authentik_sub = claims.get("sub") email = claims.get("email", "") display_name = claims.get("name") or claims.get("preferred_username") or email if not authentik_sub: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token missing 'sub' claim", headers={"WWW-Authenticate": "Bearer"}, ) result = await session.execute( select(User).where(User.authentik_sub == authentik_sub) ) user = result.scalar_one_or_none() if user is None: user = User( authentik_sub=authentik_sub, email=email, display_name=display_name, is_active=True, ) session.add(user) await session.commit() await session.refresh(user) return user async def get_current_active_user( current_user: User = Depends(get_current_user), ) -> User: if not current_user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user", ) return current_user async def validate_traefik_auth( token: HTTPAuthorizationCredentials | None = Depends(bearer_scheme), session: AsyncSession = Depends(get_db_session), ) -> User: if token is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"}, ) try: claims = decode_token(token.credentials) except Exception as exc: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Invalid token: {exc}", headers={"WWW-Authenticate": "Bearer"}, ) from exc authentik_sub = claims.get("sub") if not authentik_sub: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token missing 'sub' claim", headers={"WWW-Authenticate": "Bearer"}, ) result = await session.execute( select(User).where(User.authentik_sub == authentik_sub) ) user = result.scalar_one_or_none() if user is None or not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or inactive", headers={"WWW-Authenticate": "Bearer"}, ) return user