"""SlowAPI rate-limiter wiring for auth endpoints. Only one limiter is used process-wide; it's built in :func:`create_limiter` and stored on ``app.state.limiter`` so the ``@limiter.limit`` decorator can pick it up from the request. Storage is ``memory://``. At this scale (single-digit requests/second, single container) a persistent backend is not worth the operational cost, and the consequences of losing limiter state on restart are acceptable — the DB-side per-email check (in :mod:`app.services.auth`) catches sustained abuse across restarts. """ from __future__ import annotations from slowapi import Limiter from slowapi.util import get_remote_address # Process-wide singleton. Module-level because SlowAPI's ``@limiter.limit`` # decorator has to be applied at endpoint-definition time (before the # router is wired into the FastAPI app), and that has to reference the # same limiter instance that the request path consults via # ``request.app.state.limiter``. # # Storage is ``memory://``: in-process, single-container scale. Restarts # drop in-flight counters — acceptable because the DB-side per-email # check in :mod:`app.services.auth` backs this up for longer-lived abuse # patterns. limiter: Limiter = Limiter( key_func=get_remote_address, storage_uri="memory://", ) def create_limiter() -> Limiter: """Return the process-wide :class:`slowapi.Limiter` singleton. Kept as a function (rather than exposing the module-level ``limiter`` directly) to match the service-factory pattern used by :class:`AuditService` / :class:`EmailService` / ... and to give tests a hook to monkeypatch if they ever need a per-test limiter. """ return limiter