"""API dependencies.""" from typing import Annotated, Optional from fastapi import Depends, HTTPException, Request, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.services.auth import AuthService from app.models.user import User from app.security import AUTH_COOKIE_NAME # Security scheme security_optional = HTTPBearer(auto_error=False) async def get_current_user( request: Request, credentials: Annotated[Optional[HTTPAuthorizationCredentials], Depends(security_optional)], db: Annotated[AsyncSession, Depends(get_db)], ) -> User: """Get current authenticated user from JWT token.""" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) token: Optional[str] = None if credentials is not None: token = credentials.credentials if not token: token = request.cookies.get(AUTH_COOKIE_NAME) if not token: raise credentials_exception payload = AuthService.decode_token(token) if payload is None: raise credentials_exception user_id_str = payload.get("sub") if user_id_str is None: raise credentials_exception try: user_id = int(user_id_str) except (ValueError, TypeError): raise credentials_exception user = await AuthService.get_user_by_id(db, user_id) if user is None: raise credentials_exception if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is disabled", ) return user async def get_current_active_user( current_user: Annotated[User, Depends(get_current_user)], ) -> User: """Ensure user is active.""" if not current_user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user", ) return current_user async def get_current_user_optional( request: Request, credentials: Annotated[Optional[HTTPAuthorizationCredentials], Depends(security_optional)], db: Annotated[AsyncSession, Depends(get_db)], ) -> Optional[User]: """Get current user if authenticated, otherwise return None. This allows endpoints to work for both authenticated and anonymous users, potentially showing different content based on auth status. """ token: Optional[str] = None if credentials is not None: token = credentials.credentials if not token: token = request.cookies.get(AUTH_COOKIE_NAME) if not token: return None payload = AuthService.decode_token(token) if payload is None: return None user_id_str = payload.get("sub") if user_id_str is None: return None try: user_id = int(user_id_str) except (ValueError, TypeError): return None user = await AuthService.get_user_by_id(db, user_id) if user is None or not user.is_active: return None return user # Type aliases for cleaner annotations CurrentUser = Annotated[User, Depends(get_current_user)] ActiveUser = Annotated[User, Depends(get_current_active_user)] OptionalUser = Annotated[Optional[User], Depends(get_current_user_optional)] CurrentUserOptional = OptionalUser # Alias for backward compatibility Database = Annotated[AsyncSession, Depends(get_db)]