feat: Funcion de sistema tenants
This commit is contained in:
@@ -9,8 +9,9 @@ from starlette.requests import Request
|
||||
from starlette.responses import Response, JSONResponse
|
||||
from sqlalchemy import select
|
||||
import structlog
|
||||
import uuid
|
||||
|
||||
from app.core.database import AsyncSessionLocal
|
||||
from app.core.database import AsyncSessionLocal, get_db
|
||||
from app.core.config import get_settings
|
||||
from app.models.tenant import Tenant, TenantStatus
|
||||
|
||||
@@ -30,11 +31,17 @@ class TenantMiddleware(BaseHTTPMiddleware):
|
||||
# Rutas que no requieren tenant
|
||||
EXCLUDED_PATHS = {
|
||||
"/health",
|
||||
"/api/v1/health",
|
||||
"/v1/health",
|
||||
"/api/v1/health/detailed",
|
||||
"/v1/health/detailed",
|
||||
"/",
|
||||
"/api/v1/auth/login",
|
||||
"/v1/auth/login",
|
||||
"/api/v1/auth/refresh",
|
||||
"/v1/auth/refresh",
|
||||
"/api/v1/auth/logout",
|
||||
"/v1/auth/logout",
|
||||
"/api/v1/auth/forgot-password",
|
||||
"/v1/auth/forgot-password",
|
||||
"/api/v1/auth/reset-password",
|
||||
@@ -66,33 +73,58 @@ class TenantMiddleware(BaseHTTPMiddleware):
|
||||
tenant_id = request.headers.get("X-Tenant-ID")
|
||||
tenant_slug = request.headers.get("X-Tenant-Slug")
|
||||
|
||||
# Si no hay headers de tenant
|
||||
if not tenant_id and not tenant_slug:
|
||||
if settings.ENVIRONMENT == "production":
|
||||
tenant_uuid: uuid.UUID | None = None
|
||||
if tenant_id:
|
||||
try:
|
||||
tenant_uuid = uuid.UUID(tenant_id)
|
||||
except ValueError:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"detail": "Tenant information required (X-Tenant-ID or X-Tenant-Slug header)"}
|
||||
content={"detail": "Invalid X-Tenant-ID header (must be UUID)"},
|
||||
)
|
||||
# En desarrollo, continuar sin tenant con advertencia
|
||||
logger.warning(
|
||||
"Request without tenant information",
|
||||
path=request.url.path,
|
||||
method=request.method,
|
||||
|
||||
# Si no hay headers de tenant (requerido para aislamiento multi-tenant)
|
||||
if not tenant_id and not tenant_slug:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"detail": "Tenant information required (X-Tenant-ID or X-Tenant-Slug header)"},
|
||||
)
|
||||
return await call_next(request)
|
||||
|
||||
# Validar tenant contra la base de datos
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
if tenant_id:
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.id == tenant_id)
|
||||
)
|
||||
else:
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.slug == tenant_slug)
|
||||
)
|
||||
tenant = result.scalars().first()
|
||||
# Prefer DB session coming from dependency overrides (tests) when available.
|
||||
# Guard: in unit tests request.app may be a MagicMock, not a real FastAPI app.
|
||||
dependency_overrides = getattr(request.app, "dependency_overrides", None)
|
||||
override_get_db = None
|
||||
if isinstance(dependency_overrides, dict):
|
||||
override_get_db = dependency_overrides.get(get_db)
|
||||
|
||||
if override_get_db is not None:
|
||||
agen = override_get_db()
|
||||
session = await agen.__anext__()
|
||||
try:
|
||||
if tenant_uuid is not None:
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.id == tenant_uuid)
|
||||
)
|
||||
else:
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.slug == tenant_slug)
|
||||
)
|
||||
tenant = result.scalars().first()
|
||||
finally:
|
||||
await agen.aclose()
|
||||
else:
|
||||
async with AsyncSessionLocal() as session:
|
||||
if tenant_uuid is not None:
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.id == tenant_uuid)
|
||||
)
|
||||
else:
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.slug == tenant_slug)
|
||||
)
|
||||
tenant = result.scalars().first()
|
||||
|
||||
if tenant is None:
|
||||
logger.warning(
|
||||
|
||||
Reference in New Issue
Block a user