diff --git a/backend/core/middleware.py b/backend/core/middleware.py index 89d70014..2219cc3e 100644 --- a/backend/core/middleware.py +++ b/backend/core/middleware.py @@ -6,12 +6,20 @@ from typing import Callable, Optional from fastapi import Request, Response from fastapi.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware +from cachetools import TTLCache from .config import settings from .security import get_tenant_from_token, verify_token, get_active_system logger = logging.getLogger(__name__) +# Cache de licencia VÁLIDA por tenant (evita golpear verify-license del Hub en cada +# request). Solo el camino "válida" se cachea — inválida/expirada/error nunca se +# cachean y siempre revalidan contra el Hub (fail-closed: lo peor que puede pasar es +# que un tenant recién revocado siga pasando hasta 5 min más, nunca al revés). +_LICENSE_OK_TTL_SECONDS = 300 +_license_ok_cache: TTLCache = TTLCache(maxsize=1000, ttl=_LICENSE_OK_TTL_SECONDS) + def _normalize_text(value: str | None) -> str: if not value: @@ -160,6 +168,11 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware): if tid is not None and str(tid).strip() != "": tenant_override = str(tid) + # Licencia ya confirmada válida hace poco para este tenant — no volver a + # golpear al Hub (ver definición de _license_ok_cache arriba). + if tenant_override and tenant_override in _license_ok_cache: + return await call_next(request) + hub_headers = {"Authorization": f"Bearer {token}"} if tenant_override: hub_headers["X-Tenant-Override"] = str(tenant_override) @@ -274,6 +287,8 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware): pass # Si no se puede parsear, dejamos pasar — el Hub es la fuente de verdad request.state.license_info = data + if tenant_override: + _license_ok_cache[tenant_override] = True return await call_next(request) if response.status_code == 401: diff --git a/backend/core/security.py b/backend/core/security.py index cb188c99..3a22ddf8 100644 --- a/backend/core/security.py +++ b/backend/core/security.py @@ -18,8 +18,10 @@ from .database import get_core_db logger = logging.getLogger(__name__) -# Cache para tokens verificados (1 minuto de TTL, máximo 1000 tokens) -token_cache = TTLCache(maxsize=1000, ttl=60) +# Cache para tokens verificados (5 minutos de TTL, máximo 1000 tokens). +# El access token vive más que este TTL, así que cachear su verificación es seguro +# y evita golpear /auth/me del Hub en cada request de navegación. +token_cache = TTLCache(maxsize=1000, ttl=300) # IDs de tenants ya sincronizados en este proceso (evita consultas repetidas) _synced_tenant_ids: Set[int] = set()