Files
service_manager/backend/app/middleware/tenant.py

105 lines
3.4 KiB
Python

"""
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)