perf(auth): extender TTL de token_cache y cachear licencia válida por tenant
Cada navegación al dashboard disparaba verify-license y auth/me contra el Hub sin ningún caché, sumando 1-6s por llamada del lado del Hub a cada request. - security.py: token_cache TTL 60s→300s (el access token vive más que el TTL anterior; cachear su verificación es seguro). - middleware.py: nuevo _license_ok_cache (TTLCache, 300s) en LicenseValidationMiddleware — SOLO cachea el camino "licencia válida" por tenant_override; inválida/expirada/error nunca se cachea y siempre revalida contra el Hub (fail-closed: el peor caso es que un tenant recién revocado siga pasando hasta 5 min más, nunca al revés).
This commit is contained in:
@@ -6,12 +6,20 @@ from typing import Callable, Optional
|
|||||||
from fastapi import Request, Response
|
from fastapi import Request, Response
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
from cachetools import TTLCache
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .security import get_tenant_from_token, verify_token, get_active_system
|
from .security import get_tenant_from_token, verify_token, get_active_system
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
def _normalize_text(value: str | None) -> str:
|
||||||
if not value:
|
if not value:
|
||||||
@@ -160,6 +168,11 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware):
|
|||||||
if tid is not None and str(tid).strip() != "":
|
if tid is not None and str(tid).strip() != "":
|
||||||
tenant_override = str(tid)
|
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}"}
|
hub_headers = {"Authorization": f"Bearer {token}"}
|
||||||
if tenant_override:
|
if tenant_override:
|
||||||
hub_headers["X-Tenant-Override"] = str(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
|
pass # Si no se puede parsear, dejamos pasar — el Hub es la fuente de verdad
|
||||||
|
|
||||||
request.state.license_info = data
|
request.state.license_info = data
|
||||||
|
if tenant_override:
|
||||||
|
_license_ok_cache[tenant_override] = True
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ from .database import get_core_db
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Cache para tokens verificados (1 minuto de TTL, máximo 1000 tokens)
|
# Cache para tokens verificados (5 minutos de TTL, máximo 1000 tokens).
|
||||||
token_cache = TTLCache(maxsize=1000, ttl=60)
|
# 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)
|
# IDs de tenants ya sincronizados en este proceso (evita consultas repetidas)
|
||||||
_synced_tenant_ids: Set[int] = set()
|
_synced_tenant_ids: Set[int] = set()
|
||||||
|
|||||||
Reference in New Issue
Block a user