""" Tenant Middleware - ServiceManagerWeb Middleware para manejo de multi-tenancy """ from fastapi import Request, HTTPException, status from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import Response import structlog from contextlib import asynccontextmanager from sqlalchemy.sql import text logger = structlog.get_logger(__name__) # Correct the usage of async for by wrapping it in an async function @asynccontextmanager async def get_db_context(): from app.core.database import get_db async for db in get_db(): yield db class TenantMiddleware(BaseHTTPMiddleware): """ Middleware para extraer y validar información del tenant. Extrae el tenant_id del header X-Tenant-ID y lo almacena en el estado de la request para uso posterior. """ # Rutas que no requieren tenant EXCLUDED_PATHS = { "/health", "/", "/v1/auth/login", "/docs", "/openapi.json", "/redoc" } async def dispatch(self, request: Request, call_next) -> Response: """Process request and add tenant information.""" # Skip tenant validation for excluded paths if request.url.path in self.EXCLUDED_PATHS or any(request.url.path.startswith(path) for path in self.EXCLUDED_PATHS): return await call_next(request) # Extract tenant from header tenant_id = request.headers.get("X-Tenant-ID") tenant_slug = request.headers.get("X-Tenant-Slug") # Revertir cambios para requerir encabezados de tenant if not tenant_id and not tenant_slug: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Tenant information required (X-Tenant-ID or X-Tenant-Slug header)" ) # Store tenant info in request state request.state.tenant_id = tenant_id request.state.tenant_slug = tenant_slug # Validate tenant exists and is active if tenant_id or tenant_slug: from app.core.database import get_db from app.models.tenant import Tenant # Update the middleware to use the new context manager async with get_db_context() as db: tenant = await db.execute( text( """ SELECT * FROM tenants WHERE id = :tenant_id OR slug = :tenant_slug AND status = 'active' """ ), {"tenant_id": tenant_id, "tenant_slug": tenant_slug} ) tenant = tenant.fetchone() if not tenant: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found or inactive" ) # Store validated tenant info request.state.tenant = tenant else: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Tenant information required (X-Tenant-ID or X-Tenant-Slug header)" ) logger.debug( "Tenant middleware processed", tenant_id=tenant_id, tenant_slug=tenant_slug, path=request.url.path ) return await call_next(request)