Implement login endpoint rate limiting (TASK-007)
- Add in-memory rate limiter with per-IP deque tracking of attempt timestamps - Limit login attempts to 5 per 60 seconds per IP, return 429 on excess - Add Retry-After header to rate limit responses - Implement IP extraction utility with proxy trust validation (prevent X-Forwarded-For spoofing) - Integrate rate limiter into auth router and dependencies - Add 10-second asyncio.sleep on failed login attempts to further slow brute-force - Add comprehensive tests for rate limiting (9 new tests, all passing) - Update Features.md to document login rate limiting - Update Backend-Development.md with rate limiting conventions and design patterns - Fix test infrastructure issues: update password to meet complexity requirements - Fix TestValidateSession tests to use Bearer token authentication - All tests passing: 23 auth tests + full test suite coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -32,6 +32,7 @@ from app.repositories.protocols import (
|
||||
)
|
||||
from app.services.geo_cache import GeoCache
|
||||
from app.utils.constants import SESSION_COOKIE_NAME
|
||||
from app.utils.rate_limiter import RateLimiter
|
||||
from app.utils.runtime_state import ApplicationState, RuntimeState
|
||||
from app.utils.session_cache import NoOpSessionCache, SessionCache
|
||||
|
||||
@@ -51,6 +52,7 @@ class ApplicationContext:
|
||||
runtime_settings: Settings | None
|
||||
runtime_state: RuntimeState
|
||||
session_cache: SessionCache | None
|
||||
login_rate_limiter: RateLimiter
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -76,6 +78,10 @@ def _build_app_context(request: Request) -> ApplicationContext:
|
||||
if session_cache is None:
|
||||
session_cache = NoOpSessionCache()
|
||||
|
||||
login_rate_limiter: RateLimiter = getattr(state, "login_rate_limiter", None)
|
||||
if login_rate_limiter is None:
|
||||
login_rate_limiter = RateLimiter()
|
||||
|
||||
return ApplicationContext(
|
||||
settings=state.settings,
|
||||
http_session=getattr(state, "http_session", None),
|
||||
@@ -86,6 +92,7 @@ def _build_app_context(request: Request) -> ApplicationContext:
|
||||
runtime_settings=getattr(state, "runtime_settings", None),
|
||||
runtime_state=state.runtime_state,
|
||||
session_cache=session_cache,
|
||||
login_rate_limiter=login_rate_limiter,
|
||||
)
|
||||
|
||||
|
||||
@@ -210,6 +217,13 @@ async def get_session_cache(app_context: Annotated[ApplicationContext, Depends(g
|
||||
return app_context.session_cache
|
||||
|
||||
|
||||
async def get_login_rate_limiter(
|
||||
app_context: Annotated[ApplicationContext, Depends(get_app_context)],
|
||||
) -> RateLimiter:
|
||||
"""Provide the login endpoint rate limiter from application context."""
|
||||
return app_context.login_rate_limiter
|
||||
|
||||
|
||||
async def get_session_repo() -> SessionRepository:
|
||||
"""Provide the concrete session repository implementation.
|
||||
|
||||
@@ -410,3 +424,4 @@ Fail2BanDbRepositoryDep = Annotated[Fail2BanDbRepository, Depends(get_fail2ban_d
|
||||
AppStateDep = Annotated[ApplicationContext, Depends(get_app_state)]
|
||||
AppDep = Annotated[FastAPI, Depends(get_app)]
|
||||
AuthDep = Annotated[Session, Depends(require_auth)]
|
||||
LoginRateLimiterDep = Annotated[RateLimiter, Depends(get_login_rate_limiter)]
|
||||
|
||||
Reference in New Issue
Block a user