Task 0.2: Backend modularization + security fix
Task 0.2: Backend modularization + security fix
Section titled “Task 0.2: Backend modularization + security fix”Phase: 0 — Foundation
Priority: Critical
Depends on: nothing
Effort: ~4h
Context
Section titled “Context”Read these files before starting:
app/backend/app/main.py(1057 lines) — ALL routes, middleware, auth, rate limiting in one file insidecreate_app()closureapp/backend/app/config.py(184 lines) — settingsapp/backend/app/repository.py— used by route handlersapp/backend/tests/— all test files (they useTestClient(create_app()))
Current structure: one giant create_app() function in main.py registers all 19 endpoints inline. This makes the file unmaintainable and hard to review.
- Split
main.pyinto focused APIRouter modules — one per domain - Fix timing-unsafe token comparison (security bug —
==operator is vulnerable to timing oracle attacks) - Keep
create_app()as the composition root — all tests continue to work unchanged
Step 1: Create routes directory
Section titled “Step 1: Create routes directory”Create app/backend/app/routes/__init__.py (empty).
Step 2: Extract routes/analysis.py
Section titled “Step 2: Extract routes/analysis.py”Move these endpoints from main.py into app/backend/app/routes/analysis.py:
POST /api/v1/calculatePOST /api/v1/designPOST /api/v1/analyzePOST /api/v1/llm/adviceGET /api/v1/sensitivity(will be added in Phase 2.1 — create placeholder for it now)
The router needs access to: settings, repository, rate_limiter, auth_dependency.
Pass these via a factory function:
from fastapi import APIRouter
def create_analysis_router(settings, repository, rate_limiter, require_auth, require_write_auth) -> APIRouter: router = APIRouter() # move route handlers here return routerStep 3: Extract routes/projects.py
Section titled “Step 3: Extract routes/projects.py”Move these endpoints:
GET /api/v1/projectsPOST /api/v1/projectsGET /api/v1/projects/{project_id}PUT /api/v1/projects/{project_id}DELETE /api/v1/projects/{project_id}(archive)GET /api/v1/projects/{project_id}/historyGET /api/v1/projects/{project_id}/revisionsPOST /api/v1/projects/compareGET /api/v1/projects/{project_id}/snapshots/{run_id}
Factory: create_projects_router(settings, repository, rate_limiter, require_auth, require_write_auth) -> APIRouter
Step 4: Extract routes/workspace.py
Section titled “Step 4: Extract routes/workspace.py”Move these endpoints:
POST /api/v1/workspace/exportPOST /api/v1/workspace/importPOST /api/v1/workspace/validate
Factory: create_workspace_router(settings, repository, rate_limiter, require_auth, require_write_auth) -> APIRouter
Step 5: Extract routes/export.py
Section titled “Step 5: Extract routes/export.py”Move these endpoints:
POST /api/v1/export/markdownPOST /api/v1/export/htmlPOST /api/v1/export/html-standalone(placeholder for Phase 3.4)
Factory: create_export_router(settings, repository, rate_limiter, require_auth) -> APIRouter
Step 6: Extract routes/system.py
Section titled “Step 6: Extract routes/system.py”Move these endpoints:
GET /healthGET /readyzGET /api/v1/diagnostics
Factory: create_system_router(settings, repository, runtime_counters, start_time) -> APIRouter
Step 7: Fix timing-safe token comparison (SECURITY)
Section titled “Step 7: Fix timing-safe token comparison (SECURITY)”In main.py, find all places where API tokens are compared:
# BEFORE (vulnerable):if presented_token == settings.api_token:if presented_token == settings.readonly_api_token:
# AFTER (timing-safe):import hmacif hmac.compare_digest(presented_token, settings.api_token):if hmac.compare_digest(presented_token, settings.readonly_api_token):Apply to ALL token comparison locations — typically in the auth dependency function and middleware.
Also check repository.py for any token/signature comparisons and apply the same fix.
Step 8: Slim down main.py
Section titled “Step 8: Slim down main.py”After extraction, main.py should contain only:
create_app()factory — creates FastAPI instance, creates all dependencies, includes all routers- Middleware registration (CORS, rate limiter middleware, request ID / process time headers, security headers)
- Exception handlers (400, 500 global handlers)
if __name__ == "__main__": uvicorn.run(...)block
Target: main.py ≤ 200 lines after extraction.
Include routers in create_app():
analysis_router = create_analysis_router(settings, repository, rate_limiter, require_auth, require_write_auth)projects_router = create_projects_router(settings, repository, rate_limiter, require_auth, require_write_auth)workspace_router = create_workspace_router(settings, repository, rate_limiter, require_auth, require_write_auth)export_router = create_export_router(settings, repository, rate_limiter, require_auth)system_router = create_system_router(settings, repository, runtime_counters, start_time)
app.include_router(analysis_router)app.include_router(projects_router)app.include_router(workspace_router)app.include_router(export_router)app.include_router(system_router)Verify
Section titled “Verify”-
cd app/backend && python -m pytest tests/ -x -q— all 100 tests pass -
python -c "from app.main import create_app; app = create_app(); print(len(app.routes), 'routes')"— same count as before -
main.pyis ≤ 200 lines - Each
routes/*.pyexists:analysis.py,projects.py,workspace.py,export.py,system.py -
hmac.compare_digestis used for all token comparisons — grep:grep -r "== settings.api_token" app/backend/returns nothing -
python scripts/verify_all.pyexits 0 (or all checks that don’t require e2e pass)
Constraints
Section titled “Constraints”- Do NOT change any endpoint URLs, request schemas, or response schemas
- Do NOT change test files — they must pass without modification
- The
create_app()factory pattern MUST be preserved (tests rely on it) - Do NOT add new dependencies to
requirements.txt - Each route module MUST be independently importable (no circular imports)
- Keep all middleware in
main.py— do not move it to route modules