105 lines
3.4 KiB
Python
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) |