from __future__ import annotations from typing import Annotated from fastapi import Depends, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy.orm import Session from app.core.database import get_db from app.core.security import InvalidTokenError, decode_token from app.models.user import User from app.repositories.user_repository import UserRepository security_scheme = HTTPBearer(auto_error=True) def get_current_user( credentials: Annotated[HTTPAuthorizationCredentials, Depends(security_scheme)], db: Annotated[Session, Depends(get_db)], ) -> User: """Extract and validate the current user from the JWT token.""" try: payload = decode_token(credentials.credentials) except InvalidTokenError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token", headers={"WWW-Authenticate": "Bearer"}, ) token_type = payload.get("type") if token_type != "access": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type. Access token required.", headers={"WWW-Authenticate": "Bearer"}, ) user_id: str | None = payload.get("sub") if user_id is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token payload missing subject", headers={"WWW-Authenticate": "Bearer"}, ) user_repo = UserRepository(db) user = user_repo.get_by_id(user_id) 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 deactivated", ) return user def get_current_active_user( current_user: Annotated[User, Depends(get_current_user)], ) -> User: """Ensure the current user is active.""" if not current_user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is deactivated", ) return current_user def require_role(required_roles: list[str]): # noqa: ANN201 """Dependency factory to require specific roles.""" def role_checker( current_user: Annotated[User, Depends(get_current_user)], ) -> User: user_roles = {role.name for role in current_user.roles} if not user_roles.intersection(required_roles): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"One of the following roles required: {', '.join(required_roles)}", ) return current_user return role_checker CurrentUser = Annotated[User, Depends(get_current_user)] ActiveUser = Annotated[User, Depends(get_current_active_user)] AdminUser = Annotated[User, Depends(require_role(["admin"]))] DBSession = Annotated[Session, Depends(get_db)]