chore: snapshot before development sync

This commit is contained in:
2026-04-24 10:29:16 -05:00
parent d3676aa8ed
commit 5ac757dbc1
22 changed files with 658 additions and 128 deletions

View File

@@ -1,6 +1,7 @@
import logging
import time
import httpx
from datetime import datetime, timezone
from typing import Callable
from fastapi import Request, Response
from fastapi.responses import JSONResponse
@@ -75,7 +76,7 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware):
exempt_paths = [
"/api/docs", "/api/redoc", "/openapi.json",
"/api/v1/auth", "/api/v1/status", "/api/health",
"/api/", "/api/v1/core/help-center",
"/api/v1/core/help-center",
]
is_exempt = any(
@@ -97,23 +98,54 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware):
# Validación contra el Hub Central
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(
f"{settings.HUB_URL}/api/v1/auth/verify-license",
f"{settings.HUB_URL}api/v1/auth/verify-license",
headers={"Authorization": f"Bearer {token}"}
)
logger.info(f"🔑 verify-license → status={response.status_code} body={response.text[:300]}")
if response.status_code == 404:
# Endpoint no existe en este Hub — dejar pasar
return await call_next(request)
if response.status_code == 200:
data = response.json()
# Escenario 1: sin licencia asignada o licencia inactiva
if not data.get("valid", False):
message = data.get("message", "Sin licencia asignada para este tenant")
logger.warning(f"🚫 License invalid for tenant: {data.get('tenant_slug')}{message}")
return JSONResponse(
status_code=402,
content={
"error": "LICENSE_ERROR",
"message": f"Licencia inválida: {data.get('message', 'Sin suscripción activa')}",
"message": message,
"status_code": 402,
}
)
# Escenario 2: licencia vencida (verificación local de expires_at)
expires_at_str = data.get("expires_at")
if expires_at_str:
try:
expires_at = datetime.fromisoformat(expires_at_str.replace("Z", "+00:00"))
if expires_at.tzinfo is None:
expires_at = expires_at.replace(tzinfo=timezone.utc)
if expires_at < datetime.now(timezone.utc):
logger.warning(f"🚫 License expired for tenant: {data.get('tenant_slug')} — expired at {expires_at_str}")
return JSONResponse(
status_code=402,
content={
"error": "LICENSE_EXPIRED",
"message": f"La licencia venció el {expires_at.strftime('%d/%m/%Y')}. Renueva tu suscripción.",
"status_code": 402,
}
)
except (ValueError, TypeError):
pass # Si no se puede parsear, dejamos pasar — el Hub es la fuente de verdad
request.state.license_info = data
return await call_next(request) # <--- Único camino al éxito
return await call_next(request) # <--- Único camino al éxito
elif response.status_code == 403:
return JSONResponse(