Spaces:
Sleeping
Sleeping
| """ | |
| 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) | |