system-prompts-and-models-o.../dealix/api/security/rate_limit.py
2026-05-01 14:03:52 +03:00

84 lines
2.4 KiB
Python

"""
Rate limiting via slowapi.
تحديد المعدل عبر slowapi.
Default policy (per route):
POST /api/v1/leads → 10/min
POST /api/v1/sales/* → 30/min
POST /api/v1/webhooks/wa → 100/min
Other API routes → 60/min
Global (per IP, all paths) → 1000/min
"""
from __future__ import annotations
import os
from typing import Any
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
try:
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address
_HAS_SLOWAPI = True
except ImportError: # pragma: no cover
_HAS_SLOWAPI = False
Limiter = None # type: ignore
RateLimitExceeded = Exception # type: ignore
def _key_func(request: Request) -> str:
"""Prefer API key (authenticated callers) over IP."""
key = request.headers.get("X-API-Key")
if key:
return f"api:{key[:16]}"
if _HAS_SLOWAPI:
return get_remote_address(request)
return request.client.host if request.client else "anon"
DEFAULT_GLOBAL_LIMIT = os.getenv("RL_GLOBAL", "1000/minute")
limiter: Any = None
if _HAS_SLOWAPI:
limiter = Limiter(
key_func=_key_func,
default_limits=[DEFAULT_GLOBAL_LIMIT],
storage_uri=os.getenv("RL_STORAGE_URI", "memory://"),
strategy="fixed-window",
)
# Per-route limits (applied via decorators in routers)
LIMITS = {
"leads_create": os.getenv("RL_LEADS", "10/minute"),
"sales_any": os.getenv("RL_SALES", "30/minute"),
"whatsapp_webhook": os.getenv("RL_WA_WEBHOOK", "100/minute"),
"generic_api": os.getenv("RL_GENERIC", "60/minute"),
}
def setup_rate_limit(app: FastAPI) -> None:
"""Wire slowapi into the FastAPI app. No-op if slowapi is missing."""
if not _HAS_SLOWAPI or limiter is None:
return
app.state.limiter = limiter
async def _rate_limit_handler(request: Request, exc: RateLimitExceeded) -> JSONResponse:
return JSONResponse(
status_code=429,
content={
"error": "RateLimitExceeded",
"detail": f"Too many requests: {exc.detail}",
"ar": "تجاوزت الحد المسموح، يرجى المحاولة لاحقاً.",
},
)
app.add_exception_handler(RateLimitExceeded, _rate_limit_handler)
app.add_middleware(SlowAPIMiddleware)