"""Shared slowapi limiters and key functions. We run two parallel limiters: - per-IP (catches a single abuser even if they have the key) - per-app-key (catches mass abuse from a leaked extension key) """ from __future__ import annotations from fastapi import Request from slowapi import Limiter from slowapi.util import get_remote_address from app.config import settings def _key_from_header(request: Request) -> str: # Use the app API key as the limiter key. Falls back to client IP # so unauthenticated requests still bucket somewhere sane. key = request.headers.get("x-api-key", "") return key or get_remote_address(request) ip_limiter = Limiter(key_func=get_remote_address) key_limiter = Limiter(key_func=_key_from_header) def per_ip_limit() -> str: return settings.rate_limit_per_ip def per_key_limit() -> str: return settings.rate_limit_per_key