Files
curo1305 3d487b82ef docs(02-02): execution summary — auth API endpoints + frontend auth wall complete
Requirements completed: AUTH-01, AUTH-02, AUTH-04, SEC-01, SEC-02, SEC-03, SEC-05

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 19:48:33 +02:00

13 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions duration completed
02-users-authentication 02 auth
fastapi
auth
jwt
refresh-token
totp
backup-codes
rate-limiting
csp
cors
vue3
pinia
router-guard
phase plan provides
02-users-authentication 01 services/auth.py (hash_password, verify_password, create_access_token, create_refresh_token, rotate_refresh_token, verify_backup_code, check_hibp, bootstrap_admin), deps/auth.py (get_current_user, get_current_admin), BackupCode model, email_tasks.py
backend/api/auth.py: POST /api/auth/register, POST /api/auth/login, POST /api/auth/refresh, POST /api/auth/logout, POST /api/auth/logout-all, GET /api/auth/me, POST /api/auth/change-password
backend/main.py: OriginValidationMiddleware (403 on cross-origin state-changing requests), SecurityHeadersMiddleware (CSP + X-Frame-Options + X-Content-Type-Options), CORSMiddleware locked to settings.cors_origins, Redis lifespan (app.state.redis), admin bootstrap in lifespan, auth router included
frontend/src/stores/auth.js: useAuthStore with accessToken in memory only; login() accepts options.backupCode
frontend/src/api/client.js: Bearer header injection, 401 auto-refresh retry, full auth/admin API exports including changePassword
frontend/src/router/index.js: /login, /register, /password-reset, /account, /admin routes; beforeEach guard preserving redirect URL
frontend/src/views/auth/LoginView.vue: three-step login (password → TOTP → backup code)
frontend/src/views/auth/RegisterView.vue: registration with PasswordStrengthBar
frontend/src/layouts/AuthLayout.vue: centered bare layout for auth pages
frontend/src/components/auth/PasswordStrengthBar.vue: 4-segment strength indicator
frontend/src/components/ui/AppSpinner.vue: animate-spin inline spinner
02-03
02-04
02-05
02-06
03-user-document-isolation
added patterns
slowapi 0.1.9 — IP-level rate limiting with SlowAPIMiddleware on all auth endpoints
aioredis 2.0.1 — async Redis client for per-account login rate limiting (app.state.redis)
OriginValidationMiddleware: BaseHTTPMiddleware checks Origin header on all non-GET/HEAD/OPTIONS requests
SecurityHeadersMiddleware: BaseHTTPMiddleware adds CSP, X-Frame-Options, X-Content-Type-Options
Per-account rate limit: Redis counter keyed login_attempts:{email}, cap=10 in 15 min window
httpOnly Secure SameSite=Strict refresh cookie on path=/api/auth/refresh
TDD RED/GREEN: test file committed first (1d425d4), implementation second (1882edf)
auth store lazy import in client.js to avoid circular deps: await import('../stores/auth.js')
router beforeEach guard: redirect to /login with ?redirect= query param preserved
created modified
backend/api/auth.py — register, login (TOTP+backup), refresh, logout, logout-all, me, change-password
backend/tests/test_auth_api.py — 17 tests covering all endpoint behaviors and security properties
frontend/src/stores/auth.js — useAuthStore (memory-only accessToken, login with backupCode support)
frontend/src/layouts/AuthLayout.vue — centered bare layout for auth pages
frontend/src/views/auth/LoginView.vue — three-step login flow with backup code support
frontend/src/views/auth/RegisterView.vue — registration with strength bar
frontend/src/views/auth/PasswordResetView.vue — password reset request stub
frontend/src/views/auth/NewPasswordView.vue — new password form stub
frontend/src/views/AccountView.vue — account settings with change-password and sign-out-all
frontend/src/views/AdminView.vue — admin panel shell with tab navigation
frontend/src/components/auth/PasswordStrengthBar.vue — 4-segment strength bar
frontend/src/components/ui/AppSpinner.vue — inline animate-spin SVG
backend/main.py — OriginValidationMiddleware, SecurityHeadersMiddleware, CORS locked, Redis lifespan, admin bootstrap, auth router included, slowapi SlowAPIMiddleware
frontend/src/api/client.js — Bearer token injection, 401 auto-refresh retry, full auth exports including changePassword, admin exports
frontend/src/router/index.js — auth routes added, beforeEach guard
backend/ai/__init__.py — Python 3.9 compat: match → if/elif (deviation fix)
backend/ai/openai_provider.py — Python 3.9 compat: str|None → no-annotation (deviation fix)
backend/api/documents.py — Python 3.9 compat: str|None → Optional[str] (deviation fix)
backend/api/topics.py — Python 3.9 compat: str|None → Optional[str] (deviation fix)
backend/api/settings.py — Python 3.9 compat: str|None → Optional[str] (deviation fix)
backend/services/classifier.py — Python 3.9 compat: added from __future__ import annotations (deviation fix)
.gitignore — added frontend/node_modules, dist, package-lock.json
Starlette middleware insertion order: SecurityHeaders added first (runs last), then CORS, then OriginValidation added last (runs first) — ensures Origin check fires before CORS and route handlers
Per-account rate limit uses FakeRedis in tests with limiter storage reset between tests via auth_limiter._storage internal API — prevents IP counter bleed between test cases
Dynamic import of useAuthStore in client.js avoids circular dependency (stores/auth → client → stores/auth)
TOTP path takes precedence over backup_code: if both fields provided, verify_totp is called exclusively
~60min 2026-05-22

Phase 2 Plan 02: Auth API Endpoints and Frontend Auth Wall Summary

Auth API (register/login/refresh/logout/me/change-password) with per-account Redis rate limiting, Origin validation, CSP headers, CORS lockdown, and Vue 3 auth store + router guard + Login/Register views — full auth wall live

Performance

  • Duration: ~60 min
  • Started: 2026-05-22T17:30:00Z
  • Completed: 2026-05-22T17:45:52Z
  • Tasks: 2 (Task 1 TDD, Task 2 standard)
  • Files created: 15, Files modified: 9

Accomplishments

  • Full auth API endpoints in backend/api/auth.py: register (HIBP + strength check), login (TOTP + backup code paths), refresh (token rotation), logout, logout-all, me, change-password
  • Security hardening in backend/main.py: Origin validation middleware (403 on cross-origin non-GET), CSP headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options), CORS locked to settings.cors_origins (wildcard removed), Redis lifespan wiring, admin bootstrap
  • Per-account rate limiting: Redis counter login_attempts:{email} capped at 10 in 15 min; IP-level slowapi 10/minute on register/login/refresh
  • httpOnly Secure SameSite=Strict refresh cookie on path=/api/auth/refresh (CLAUDE.md constraint)
  • 17 TDD tests all passing: covers all endpoint behaviors including backup code issuance/invalidation, password_must_change flow, TOTP precedence over backup_code, Origin validation, and per-account rate limit
  • Vue 3 auth store (useAuthStore): accessToken in ref() memory only — never localStorage/sessionStorage; login() accepts options.backupCode
  • API client extended: Bearer token injection, 401 auto-refresh retry, full set of auth/admin exports including changePassword
  • Router beforeEach guard: redirects unauthenticated users to /login?redirect=<original-path>
  • Login view: three-step flow (password → TOTP → backup code); "Use a backup code instead" toggle; UI-SPEC copywriting
  • Register view: PasswordStrengthBar component (4-segment, score 0-4), HIBP error inline
  • AppSpinner.vue: lightweight animate-spin SVG inheriting button text color
  • AuthLayout.vue: centered bare layout with DocuVault brand
  • npm run build exits 0

Task Commits

  1. Task 1 RED — test file: 1d425d4 (test)
  2. Task 1 GREEN — implementation: 1882edf (feat)
  3. Task 2 — frontend: 3b7d362 (feat)

Files Created/Modified

See key-files in frontmatter.

Decisions Made

  • Middleware insertion order: SecurityHeaders first, CORS second, OriginValidation last — Starlette applies in reverse, so OriginValidation runs first on every request
  • Per-account Redis counter: TTL set only on first incr() call (count == 1) to ensure the 15-minute window starts from the first attempt, not the last
  • Dynamic import in client.js: await import('../stores/auth.js') inside request() avoids the circular import between stores/auth → api/client → stores/auth
  • Slowapi storage reset in tests: auth_limiter._storage.reset() called in authed_client fixture setup to prevent IP counters from one test bleeding into the next

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Python 3.9 incompatibility: match statement in ai/__init__.py

  • Found during: Task 1 (RED phase — test setup import chain)
  • Issue: ai/__init__.py used Python 3.10 match statement; failed with SyntaxError on Python 3.9
  • Fix: Replaced match with if/elif chain (equivalent behavior, Python 3.9 compatible)
  • Files modified: backend/ai/__init__.py

2. [Rule 1 - Bug] Python 3.9 incompatibility: str | None union syntax in multiple files

  • Found during: Task 1 (RED phase — test setup import chain)
  • Issue: str | None union syntax requires Python 3.10+; FastAPI/Pydantic evaluate route annotations at runtime
  • Fix: Changed to Optional[str] in all affected files; added from __future__ import annotations to classifier.py (annotation-only, no runtime evaluation)
  • Files modified: backend/ai/openai_provider.py, backend/api/documents.py, backend/api/topics.py, backend/api/settings.py, backend/services/classifier.py
  • Note: This is a pre-existing issue in the codebase (also noted in 02-01 summary) that was blocking all tests

3. [Rule 1 - Bug] Slowapi IP-level counter bleed between tests

  • Found during: Task 1 GREEN phase (test suite)
  • Issue: 10 tests calling /api/auth/register or /api/auth/login accumulated against IP 127.0.0.1, causing test_per_account_rate_limit to fail when run in sequence (IP limit hit before per-account counter)
  • Fix: Added slowapi storage reset (auth_limiter._storage.reset()) to authed_client fixture before each test
  • Files modified: backend/tests/test_auth_api.py

4. [Rule 2 - Missing] email-validator package not installed locally

  • Found during: Task 1 GREEN phase (import test)
  • Issue: Pydantic EmailStr requires email-validator package; not installed in local Python 3.9 env
  • Fix: pip install 'pydantic[email]' locally (already in requirements for Docker env)
  • Note: No file changes needed; package is implicitly included via pydantic[email] in Docker container

Known Stubs

  • AdminView.vue: tab navigation shell only — user/quota/AI config management deferred to Plan 02-03 (admin API endpoints)
  • AccountView.vue: change-password and sign-out-all wired; TOTP enrollment UI deferred to Plan 02-04
  • PasswordResetView.vue, NewPasswordView.vue: forms wired to API but password-reset confirm endpoint not yet implemented in backend (deferred to Plan 02-05)
  • None of these stubs prevent Plan 02's goal (auth wall live) from being achieved

Threat Flags

None — all new endpoints follow STRIDE threat model mitigations:

  • T-02-09: Identical 401 message for non-existent email and wrong password (anti-enumeration)
  • T-02-11: SameSite=Strict + Origin middleware for CSRF prevention
  • T-02-13: Dual rate limiting (IP-level slowapi + per-account Redis)
  • T-02-14: CSP, X-Frame-Options, X-Content-Type-Options on all responses
  • T-02-15: CORS wildcard removed
  • T-02-16: password_must_change=True returns 200 with flag only, no tokens
  • T-02-26: verify_backup_code marks used_at on first use; constant-time comparison
  • T-02-27: per-account rate limit applies to all login paths including backup code

Self-Check

See section below.


Self-Check: PASSED

Files verified:

  • backend/api/auth.py — FOUND
  • backend/main.py — contains app.state.redis (FOUND), cors_origins (FOUND), Content-Security-Policy (FOUND)
  • backend/tests/test_auth_api.py — FOUND (17 tests, all passing)
  • frontend/src/stores/auth.js — FOUND, no localStorage
  • frontend/src/router/index.js — FOUND, contains beforeEach and /login
  • frontend/src/layouts/AuthLayout.vue — FOUND
  • frontend/src/views/auth/LoginView.vue — FOUND, contains "Sign in to DocuVault" and backupCode
  • frontend/src/views/auth/RegisterView.vue — FOUND, contains "Create your account"
  • frontend/src/components/auth/PasswordStrengthBar.vue — FOUND
  • frontend/src/components/ui/AppSpinner.vue — FOUND, contains "animate-spin"
  • frontend/src/api/client.js — FOUND, contains "Authorization", "Bearer", "_retry", "refresh", "changePassword"

Commits verified:

  • 1d425d4 (test: RED phase — test_auth_api.py) — FOUND
  • 1882edf (feat: GREEN phase — auth.py + main.py + Python 3.9 fixes) — FOUND
  • 3b7d362 (feat: frontend auth store, router, views) — FOUND

Verification results:

  • pytest tests/test_auth_api.py: 17 passed
  • npm run build: exits 0
  • grep -c allow_origins=["*"] backend/main.py: 0
  • grep -c localStorage frontend/src/stores/auth.js: 0
  • grep -c backup_code backend/api/auth.py: 7

Phase: 02-users-authentication Completed: 2026-05-22