"""FastAPI dependencies for authentication, authorization, and tenant filtering.""" from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import JWTError, ExpiredSignatureError from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import joinedload from app.core.security import verify_token from app.db.session import get_async_session from app.models import User security_scheme = HTTPBearer() async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security_scheme), session: AsyncSession = Depends(get_async_session), ) -> User: """Extract and validate JWT from Authorization header, return the current user.""" token = credentials.credentials try: payload = verify_token(token) except ExpiredSignatureError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired", ) except JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", ) user_id = payload.get("sub") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload", ) # Load user with role (and its permissions) eagerly result = await session.execute( select(User).options(joinedload(User.role)).where(User.id == user_id) ) user = result.unique().scalars().first() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found", ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account is deactivated", ) return user async def get_current_active_user( current_user: User = Depends(get_current_user), ) -> User: """Return the current user if they are active (redundant check, kept for clarity).""" if not current_user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account is deactivated", ) return current_user def require_permission(permission: str): """Factory that returns a dependency checking for a specific permission.""" async def permission_checker( current_user: User = Depends(get_current_user), ) -> User: if not current_user.role: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="No role assigned", ) user_permissions: list[str] = current_user.role.permissions or [] if permission not in user_permissions: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"Missing required permission: {permission}", ) return current_user return permission_checker