TradeFlowAI / src /auth /dependencies.py
muhammadghiffari's picture
Deploy TradeFlow API to HF
db4ba8d
Raw
History Blame Contribute Delete
2.78 kB
"""
TradeFlow AI — FastAPI Auth Dependencies (T-009)
Provides reusable dependency functions for role-based access control.
All protected endpoints must use one of these dependencies.
"""
from __future__ import annotations
from typing import Annotated, Any
from fastapi import Depends, HTTPException, Security, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from .keycloak import extract_roles, extract_user_id, verify_keycloak_token
bearer_scheme = HTTPBearer(auto_error=True)
async def get_current_token_payload(
credentials: Annotated[HTTPAuthorizationCredentials, Security(bearer_scheme)],
) -> dict[str, Any]:
"""Verify the Bearer JWT and return its decoded payload."""
return await verify_keycloak_token(credentials.credentials)
async def get_current_user_id(
payload: Annotated[dict[str, Any], Depends(get_current_token_payload)],
) -> str:
"""Returns the authenticated user's Keycloak sub (UUID)."""
return extract_user_id(payload)
async def get_current_roles(
payload: Annotated[dict[str, Any], Depends(get_current_token_payload)],
) -> list[str]:
"""Returns the list of Keycloak realm roles for the current user."""
return extract_roles(payload)
def require_roles(*allowed_roles: str):
"""
Dependency factory that enforces role-based access.
Usage:
@router.post("/submit")
async def submit(
_: None = Depends(require_roles("operator", "admin"))
):
"""
async def _check_roles(
roles: Annotated[list[str], Depends(get_current_roles)],
) -> None:
if not any(role in roles for role in allowed_roles):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Required roles: {list(allowed_roles)}",
)
return Depends(_check_roles)
# Convenience singletons for common role checks
RequireOperator = require_roles("operator", "admin")
RequireAdmin = require_roles("admin")
RequireSME = require_roles("sme", "operator", "admin")
RequireSupervisor = require_roles("supervisor", "admin")
class CurrentUser:
"""Dependency class bundling user_id + roles in one inject."""
def __init__(self, user_id: str, roles: list[str]) -> None:
self.user_id = user_id
self.roles = roles
def has_role(self, *roles: str) -> bool:
return any(r in self.roles for r in roles)
def is_admin(self) -> bool:
return "admin" in self.roles
async def get_current_user(
user_id: Annotated[str, Depends(get_current_user_id)],
roles: Annotated[list[str], Depends(get_current_roles)],
) -> CurrentUser:
"""Returns a CurrentUser object with id and roles."""
return CurrentUser(user_id=user_id, roles=roles)