fa2de55abe
- Added state parameter to exchangeOrcidCode function for better state management during OAuth. - Implemented storage event listener in AuthContext to handle token updates when postMessage fails. - Updated AuthCallbackPage to ensure proper handling of OAuth popup closure and state updates.
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""
|
|
Rate limiting basado en SlowAPI.
|
|
|
|
- Usa Redis como backend si `REDIS_URL` está definido (compartido entre workers).
|
|
- Cae a memoria local en desarrollo si Redis no está disponible.
|
|
- Identifica al cliente por IP y, cuando hay JWT, también por `sub` (orcid_id),
|
|
para que un atacante autenticado no comparta cupo con su IP.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from slowapi import Limiter
|
|
from slowapi.errors import RateLimitExceeded
|
|
from slowapi.util import get_remote_address
|
|
from starlette.requests import Request
|
|
from starlette.responses import JSONResponse
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
def _key_func(request: Request) -> str:
|
|
"""
|
|
Devuelve la clave de rate limit para el request.
|
|
|
|
- Si hay un investigador autenticado en el state, usa su orcid_id.
|
|
- Si hay cabecera X-Forwarded-For (ngrok, nginx, cualquier proxy inverso),
|
|
usa la primera IP de la cadena (la del cliente real).
|
|
- En caso contrario, usa la IP remota del socket.
|
|
"""
|
|
researcher = getattr(request.state, "researcher", None)
|
|
if researcher is not None:
|
|
return f"user:{getattr(researcher, 'orcid_id', None) or researcher.id}"
|
|
forwarded_for = request.headers.get("x-forwarded-for")
|
|
if forwarded_for:
|
|
client_ip = forwarded_for.split(",")[0].strip()
|
|
return f"ip:{client_ip}"
|
|
return f"ip:{get_remote_address(request)}"
|
|
|
|
|
|
def _build_limiter() -> Limiter:
|
|
storage_uri: Optional[str] = settings.REDIS_URL
|
|
return Limiter(
|
|
key_func=_key_func,
|
|
default_limits=[settings.RATE_LIMIT_DEFAULT],
|
|
storage_uri=storage_uri,
|
|
headers_enabled=False,
|
|
strategy="fixed-window",
|
|
)
|
|
|
|
|
|
limiter = _build_limiter()
|
|
|
|
|
|
def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded) -> JSONResponse:
|
|
"""
|
|
Respuesta uniforme cuando se supera el límite.
|
|
|
|
No revela límites internos exactos para reducir oráculo a atacantes.
|
|
"""
|
|
return JSONResponse(
|
|
status_code=429,
|
|
content={"detail": "Too many requests, slow down."},
|
|
headers={"Retry-After": "60"},
|
|
)
|