from fastapi import Depends, HTTPException, status from starlette.requests import Request from fastapi.security import OAuth2PasswordBearer from jose import jwt, JWTError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import ValidationError from app.core.database import get_db from app.core.security import security from app.core.config import get_settings from app.models.user import User, UserRole from app.models.tenant import Tenant settings = get_settings() # Esquema OAuth2 centralizado — auth.py importa desde aquí oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"/{settings.API_VERSION}/auth/login") async def get_current_user( request: Request, token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db) ) -> User: payload = security.verify_token(token) if payload is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) user_id: str = payload.get("sub") if user_id is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) result = await db.execute(select(User).where(User.id == user_id)) user = result.scalars().first() if user is None: raise HTTPException(status_code=404, detail="User not found") if not user.is_active: raise HTTPException(status_code=400, detail="Inactive user") # Enforce that tenant header (if present) matches the authenticated user's tenant. # Prevents cross-tenant header impersonation. request_tenant_id = getattr(getattr(request, "state", None), "tenant_id", None) if request_tenant_id and str(user.tenant_id) != str(request_tenant_id): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Tenant header does not match authenticated user", ) return user async def get_current_active_superuser( current_user: User = Depends(get_current_user), ) -> User: if current_user.role != UserRole.ADMIN: raise HTTPException( status_code=403, detail="The user doesn't have enough privileges" ) return current_user async def get_current_tenant( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ) -> Tenant: """Obtener el tenant del usuario actual.""" result = await db.execute(select(Tenant).where(Tenant.id == current_user.tenant_id)) tenant = result.scalar_one_or_none() if not tenant: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found" ) return tenant