""" 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=f"/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 user_id: Optional[int] = payload.get("sub") if user_id is None: 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