""" Dependency FastAPI – get_db, get_current_user, require_admin, RLS middleware. """ import uuid from typing import Annotated from fastapi import Depends, Request from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from jose import JWTError from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from app.core.exceptions import ForbiddenError, TokenInvalidError from app.core.security import decode_token from app.database import get_db from app.models.user import User from sqlalchemy import select security = HTTPBearer() # ─── Database con RLS ───────────────────────────────────────────────────────── async def get_db_with_rls( tenant_id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> AsyncSession: """ Imposta la variabile di sessione PostgreSQL per RLS. Da usare dopo aver estratto il tenant_id dall'utente autenticato. """ await db.execute( text("SET LOCAL app.current_tenant_id = :tenant_id"), {"tenant_id": str(tenant_id)}, ) return db # ─── Utente corrente ────────────────────────────────────────────────────────── async def get_current_user( credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)], db: AsyncSession = Depends(get_db), ) -> User: """ Estrae e valida il JWT dall'header Authorization: Bearer . Carica l'utente dal DB e imposta RLS. """ token = credentials.credentials try: payload = decode_token(token) except JWTError: raise TokenInvalidError() if payload.get("type") != "access": raise TokenInvalidError() user_id_str = payload.get("sub") tenant_id_str = payload.get("tid") if not user_id_str or not tenant_id_str: raise TokenInvalidError() try: user_id = uuid.UUID(user_id_str) tenant_id = uuid.UUID(tenant_id_str) except ValueError: raise TokenInvalidError() # Imposta RLS per questo tenant # SET LOCAL non supporta parametri $1, usiamo text() con valore inline await db.execute( text(f"SET LOCAL app.current_tenant_id = '{tenant_id!s}'") ) # Carica utente result = await db.execute( select(User).where(User.id == user_id, User.tenant_id == tenant_id) ) user = result.scalar_one_or_none() if not user: raise TokenInvalidError() if not user.is_active: from app.core.exceptions import AccountDisabledError raise AccountDisabledError() return user # ─── Role guards ────────────────────────────────────────────────────────────── async def require_admin( current_user: Annotated[User, Depends(get_current_user)], ) -> User: """Richiede ruolo admin o super_admin.""" if not current_user.is_admin: raise ForbiddenError("Richiesto ruolo amministratore") return current_user async def require_super_admin( current_user: Annotated[User, Depends(get_current_user)], ) -> User: """Richiede ruolo super_admin.""" if not current_user.is_super_admin: raise ForbiddenError("Richiesto ruolo super_admin") return current_user # ─── Tipo annotato per ridurre boilerplate negli endpoint ───────────────────── CurrentUser = Annotated[User, Depends(get_current_user)] AdminUser = Annotated[User, Depends(require_admin)] SuperAdminUser = Annotated[User, Depends(require_super_admin)] DB = Annotated[AsyncSession, Depends(get_db)]