Files
rentman-clone/backend/app/api/deps.py
T

98 lines
3.0 KiB
Python
Raw Normal View History

"""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