# Plan 4 — Security **Items covered:** #1 (no authentication), #11 (no rate limiting), #14 (unbounded limit parameter) --- ## Context You are working on a FastAPI backend for a baby monitor / sleep tracker app. The codebase lives under `app/`. This plan adds three layers of security enforcement. Do them in order — auth first, then rate limiting, then the parameter bound. After each change, re-read the modified files to verify correctness, and run the tests. --- ## Tasks ### 1. Add API key authentication to all endpoints (`app/`) #### Step 1 — Read the relevant files first Read `app/config.py`, `app/main.py`, and all router files under `app/routers/` before making any changes. Understand how the app is structured and how dependencies are currently shared. #### Step 2 — Add `API_KEY` to settings In `app/config.py`, add an `API_KEY` field to the `Settings` model: ```python API_KEY: str = "" ``` The empty default means auth is effectively disabled unless the env var is set. This allows existing tests to keep working without modification (see Step 5 for the alternative if you prefer strict-by-default). #### Step 3 — Create the auth dependency Create a new file `app/dependencies.py`: ```python from fastapi import Security, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from app.config import settings bearer = HTTPBearer(auto_error=False) def verify_api_key(creds: HTTPAuthorizationCredentials = Security(bearer)): if not settings.API_KEY: # API_KEY not configured — auth is disabled (development mode) return if creds is None or creds.credentials != settings.API_KEY: raise HTTPException(status_code=401, detail="Unauthorized") ``` #### Step 4 — Apply the dependency to all routers In each file under `app/routers/`, add `verify_api_key` as a dependency on the router so every endpoint in that file is protected: ```python from fastapi import APIRouter, Depends from app.dependencies import verify_api_key router = APIRouter(dependencies=[Depends(verify_api_key)]) ``` Apply this to every router file. Do not add it per-endpoint — the router-level dependency is cleaner and ensures nothing is accidentally missed. #### Step 5 — Update the tests Any test that calls a protected endpoint now needs an `Authorization: Bearer ` header — OR the `API_KEY` setting can be left empty (the default), which disables the check. Verify which approach your test setup uses. If tests use a test client with a settings override, ensure `API_KEY` is either empty (disables check) or consistently set to a test value and the header is passed. Run the tests: ```bash pytest ``` Fix any 401 failures before proceeding. --- ### 2. Add rate limiting (`app/main.py` and `app/routers/`) #### Step 1 — Install slowapi ```bash uv add slowapi ``` Or add `slowapi` to `pyproject.toml` dependencies and run `uv sync`. #### Step 2 — Configure the limiter in `main.py` ```python from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) ``` #### Step 3 — Apply limits to endpoints Add rate limit decorators to the endpoints most at risk: - All write endpoints (`POST`, `PUT`, `PATCH`, `DELETE`): `@limiter.limit("10/minute")` - The timetable endpoint (expensive): `@limiter.limit("30/minute")` - Other read endpoints: `@limiter.limit("60/minute")` Example: ```python from app.main import limiter from fastapi import Request @router.post("/sessions") @limiter.limit("10/minute") async def create_session(request: Request, ...): ... ``` Note: `slowapi` requires the `request: Request` parameter to be present in the function signature for the decorator to work. Add it to any endpoint that doesn't already have it. It does not need to be used inside the function body. #### Step 4 — Verify Run the tests: ```bash pytest ``` Rate limiting may interfere with tests that call endpoints in a tight loop. If so, configure the limiter to be disabled in the test environment (e.g. check for a `TESTING=true` env var and set `enabled=False` on the limiter). --- ### 3. Bound the `limit` query parameter (`app/routers/sessions.py`) #### Step 1 — Read the file first Find the `get_sessions` endpoint (or equivalent) and locate the `limit` parameter. #### Step 2 — Add the bound Replace the bare `limit: int = 200` with a constrained `Query`: ```python from fastapi import Query async def get_sessions( limit: int = Query(default=200, ge=1, le=1000), ... ): ``` This rejects any `limit` value below 1 or above 1000 with a FastAPI 422 validation error automatically — no extra code needed. #### Step 3 — Verify Run the full test suite one final time: ```bash pytest ``` All tests must pass before declaring this plan complete. Do not move on to Plan 5 until the suite is green.