""" Authentication dependencies for FastAPI. """ from typing import Optional from fastapi import Depends, HTTPException, status from fastapi.security import ( OAuth2PasswordBearer, HTTPBearer, HTTPAuthorizationCredentials, ) from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.database import get_db from app.core.security import decode_token from app.models.database_models import User # OAuth2 scheme for token authentication oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login", auto_error=False) http_bearer = HTTPBearer(auto_error=False) async def get_current_user( token: Optional[str] = Depends(oauth2_scheme), credentials: Optional[HTTPAuthorizationCredentials] = Depends(http_bearer), db: AsyncSession = Depends(get_db), ) -> User: """ Get current authenticated user from JWT token. Supports both OAuth2 password bearer and HTTP Bearer authentication. """ # Get token from either source auth_token = token or (credentials.credentials if credentials else None) if not auth_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"}, ) # Decode token payload = decode_token(auth_token) if not payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials", headers={"WWW-Authenticate": "Bearer"}, ) # Verify token type token_type = payload.get("type") if token_type != "access": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type", headers={"WWW-Authenticate": "Bearer"}, ) # Get user ID from token (sub claim is a string per JWT spec) sub: Optional[str] = payload.get("sub") if sub is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload", headers={"WWW-Authenticate": "Bearer"}, ) try: user_id = int(sub) except (ValueError, TypeError): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload", headers={"WWW-Authenticate": "Bearer"}, ) # Fetch user from database result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found", headers={"WWW-Authenticate": "Bearer"}, ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is inactive" ) return user async def get_current_active_user( current_user: User = Depends(get_current_user), ) -> User: """Get current active user""" if not current_user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user" ) return current_user async def get_current_superuser( current_user: User = Depends(get_current_user), ) -> User: """Get current superuser""" if not current_user.is_superuser: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) return current_user def check_subscription_tier(required_tier: str): """ Dependency factory to check if user has required subscription tier. Returns a dependency function. """ tier_hierarchy = {"free": 0, "basic": 1, "pro": 2, "enterprise": 3} async def check_tier(current_user: User = Depends(get_current_active_user)) -> User: user_tier_level = tier_hierarchy.get(current_user.subscription_tier.value, 0) required_tier_level = tier_hierarchy.get(required_tier, 0) if user_tier_level < required_tier_level: raise HTTPException( status_code=status.HTTP_402_PAYMENT_REQUIRED, detail=f"This feature requires {required_tier} subscription or higher", ) return current_user return check_tier