From 81486fc4f81dc733ade04d8aa2cb25b2a5ed42cb Mon Sep 17 00:00:00 2001 From: Mikkel Georgsen Date: Sun, 25 Jan 2026 20:17:49 +0000 Subject: [PATCH] feat(01-03): configure rate limiting and CSRF protection - Add slowapi limiter with 100/minute default limit - Create CsrfSettings Pydantic model for fastapi-csrf-protect - Add deps.py with get_db re-export and validate_csrf dependency - Configure secure cookie settings (httponly, samesite=lax) --- backend/app/api/deps.py | 41 ++++++++++++++++++++++++++++++++++++ backend/app/core/security.py | 26 +++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 backend/app/api/deps.py create mode 100644 backend/app/core/security.py diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py new file mode 100644 index 0000000..f3587a9 --- /dev/null +++ b/backend/app/api/deps.py @@ -0,0 +1,41 @@ +"""FastAPI dependency injection utilities.""" + +from typing import Annotated + +from fastapi import Depends, Request +from fastapi_csrf_protect import CsrfProtect +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.app.core.security import CsrfSettings +from backend.app.db.session import get_db as _get_db + +# Re-export get_db for cleaner imports in endpoints +get_db = _get_db + + +# Type alias for common dependency +DbSession = Annotated[AsyncSession, Depends(get_db)] + + +@CsrfProtect.load_config +def get_csrf_config() -> CsrfSettings: + """Load CSRF configuration for fastapi-csrf-protect.""" + return CsrfSettings() + + +async def validate_csrf( + request: Request, + csrf_protect: CsrfProtect = Depends(), +) -> None: + """Validate CSRF token for state-changing requests. + + Use as dependency on POST/PUT/DELETE endpoints that need CSRF protection: + + @router.post("/items") + async def create_item( + _: None = Depends(validate_csrf), + db: AsyncSession = Depends(get_db), + ): + ... + """ + await csrf_protect.validate_csrf(request) diff --git a/backend/app/core/security.py b/backend/app/core/security.py new file mode 100644 index 0000000..f21e109 --- /dev/null +++ b/backend/app/core/security.py @@ -0,0 +1,26 @@ +"""Security configuration for rate limiting and CSRF protection.""" + +from pydantic import BaseModel +from slowapi import Limiter +from slowapi.util import get_remote_address + +from backend.app.core.config import settings + +# Rate limiter configuration +# See: 01-RESEARCH.md Pattern 3: FastAPI Security Middleware Stack +limiter = Limiter( + key_func=get_remote_address, + default_limits=["100/minute"], + # For production, use Redis: storage_uri="redis://localhost:6379" + # For development, uses in-memory storage by default +) + + +class CsrfSettings(BaseModel): + """CSRF protection settings for fastapi-csrf-protect.""" + + secret_key: str = settings.csrf_secret_key + cookie_samesite: str = "lax" + cookie_secure: bool = True # HTTPS only + cookie_httponly: bool = True + cookie_domain: str = settings.cookie_domain