Implement global rate limiter and refactor auth middleware
- Add global rate limiter utility with configurable limits and cleanup - Move rate limiting logic to middleware for consistent application - Update auth routes to use new rate limiter - Add comprehensive tests for rate limiter functionality - Update documentation with backend development guidelines and tasks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -44,6 +44,7 @@ from app.exceptions import (
|
||||
)
|
||||
from app.middleware.correlation import CorrelationIdMiddleware
|
||||
from app.middleware.csrf import CsrfMiddleware
|
||||
from app.middleware.rate_limit import RateLimitMiddleware
|
||||
from app.models.response import ErrorResponse
|
||||
from app.routers import (
|
||||
auth,
|
||||
@@ -60,7 +61,7 @@ from app.routers import (
|
||||
setup,
|
||||
)
|
||||
from app.startup import startup_shared_resources
|
||||
from app.utils.rate_limiter import RateLimiter
|
||||
from app.utils.rate_limiter import GlobalRateLimiter, RateLimiter
|
||||
from app.utils.runtime_state import ApplicationState, RuntimeState
|
||||
from app.utils.scheduler_lock import release_scheduler_lock
|
||||
from app.utils.session_cache import InMemorySessionCache, NoOpSessionCache
|
||||
@@ -158,6 +159,10 @@ async def _lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
# each worker has independent counters, limiting the blast radius of attacks.
|
||||
app.state.login_rate_limiter = RateLimiter(max_attempts=5, window_seconds=60)
|
||||
|
||||
# Initialize the global rate limiter (200 requests per 60 seconds per IP).
|
||||
# Applied to all endpoints via middleware. Process-local implementation.
|
||||
app.state.global_rate_limiter = GlobalRateLimiter(max_requests=200, window_seconds=60)
|
||||
|
||||
log.info("bangui_started")
|
||||
|
||||
try:
|
||||
@@ -535,6 +540,8 @@ async def _rate_limit_error_handler(
|
||||
) -> JSONResponse:
|
||||
"""Return a ``429 Too Many Requests`` response for rate limit exceeded errors.
|
||||
|
||||
Uses dynamic Retry-After header based on the actual rate limit configuration.
|
||||
|
||||
Args:
|
||||
request: The incoming FastAPI request.
|
||||
exc: The :class:`~app.exceptions.RateLimitError`.
|
||||
@@ -547,6 +554,7 @@ async def _rate_limit_error_handler(
|
||||
path=request.url.path,
|
||||
method=request.method,
|
||||
error=str(exc),
|
||||
retry_after_seconds=exc.retry_after_seconds,
|
||||
)
|
||||
error_response = ErrorResponse(
|
||||
code=_get_error_code(exc),
|
||||
@@ -557,7 +565,7 @@ async def _rate_limit_error_handler(
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
content=error_response.model_dump(),
|
||||
headers={"Retry-After": "60"},
|
||||
headers={"Retry-After": str(int(exc.retry_after_seconds))},
|
||||
)
|
||||
|
||||
|
||||
@@ -752,6 +760,12 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
||||
# This is also re-initialized in the lifespan, but must be present here
|
||||
# for tests that bypass the lifespan via ASGITransport.
|
||||
app.state.login_rate_limiter = RateLimiter(max_attempts=5, window_seconds=60)
|
||||
|
||||
# Initialize the global rate limiter (200 requests per 60 seconds per IP).
|
||||
# This is also re-initialized in the lifespan, but must be present here
|
||||
# for tests that bypass the lifespan via ASGITransport.
|
||||
app.state.global_rate_limiter = GlobalRateLimiter(max_requests=200, window_seconds=60)
|
||||
|
||||
set_setup_complete_cache(app, False)
|
||||
|
||||
# --- CORS ---
|
||||
@@ -771,15 +785,21 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
||||
# Note: middleware is applied in reverse order of registration.
|
||||
# The setup-redirect must run *after* CSRF, so it is added last.
|
||||
# CSRF middleware protects cookie-authenticated state-mutating requests.
|
||||
# RateLimitMiddleware checks per-IP request limits and must run early.
|
||||
# CorrelationIdMiddleware must run first (added last) so correlation ID
|
||||
# is available to all downstream handlers and loggers.
|
||||
app.add_middleware(CorrelationIdMiddleware)
|
||||
app.add_middleware(SetupRedirectMiddleware)
|
||||
app.add_middleware(CsrfMiddleware)
|
||||
app.add_middleware(
|
||||
RateLimitMiddleware,
|
||||
rate_limiter=app.state.global_rate_limiter,
|
||||
settings=resolved_settings,
|
||||
)
|
||||
|
||||
|
||||
# --- Exception handlers ---
|
||||
#
|
||||
#
|
||||
# Exception handlers are registered from most specific to least specific. FastAPI evaluates
|
||||
# them in registration order, allowing specific handlers to match before fallback handlers.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user