--- phase: 02 slug: users-authentication status: validated nyquist_compliant: true wave_0_complete: true created: 2026-05-31 --- # Phase 02 — Validation Strategy > Nyquist validation audit — reconstructed from PLAN/SUMMARY artifacts (State B). --- ## Test Infrastructure | Property | Value | |----------|-------| | **Backend framework** | pytest (pytest-asyncio, httpx.AsyncClient) | | **Backend config** | `backend/pytest.ini` | | **Backend quick run** | `cd backend && python -m pytest tests/test_auth_api.py tests/test_auth_totp.py tests/test_admin_api.py -v` | | **Backend full suite** | `cd backend && python -m pytest -v` | | **Frontend framework** | Vitest | | **Frontend config** | `frontend/vitest.config.js` | | **Frontend run** | `cd frontend && npx vitest run` | --- ## Per-Task Verification Map | Task ID | Plan | Wave | Requirement | Secure Behavior | Test Type | Automated Command | File Exists | Status | |---------|------|------|-------------|-----------------|-----------|-------------------|-------------|--------| | 02-01-T1 | 01 | 1 | AUTH-01, AUTH-02 | Argon2 hash, JWT lifecycle, BackupCode model | Unit | `pytest tests/test_task1_models_config.py tests/test_task2_auth_service.py -v` | ✅ | ✅ green | | 02-01-T2 | 01 | 1 | AUTH-07, SEC-06 | Refresh family revocation, constant-time backup code verify | Integration | `pytest tests/test_task2_auth_service.py -v` | ✅ | ✅ green | | 02-01-T3 | 01 | 1 | AUTH-01, AUTH-02 | get_current_user raises 401 on bad token; get_current_admin raises 403 | Integration | `pytest tests/test_auth_deps.py -v` | ✅ | ✅ green | | 02-02-T1 | 02 | 2 | AUTH-01, AUTH-02, AUTH-04 | Register/login/refresh/logout/me/change-password endpoints | Integration | `pytest tests/test_auth_api.py -v` | ✅ | ✅ green | | 02-02-T1 | 02 | 2 | SEC-01 | Origin validation middleware rejects cross-origin POST with 403 | Integration | `pytest tests/test_auth_api.py::test_origin_rejected -v` | ✅ | ✅ green | | 02-02-T1 | 02 | 2 | SEC-02 | Per-account rate limit: 11th login attempt returns 429 | Integration | `pytest tests/test_auth_api.py::test_per_account_rate_limit -v` | ✅ | ✅ green | | 02-02-T1 | 02 | 2 | **SEC-05** | CSP + X-Frame-Options + X-Content-Type-Options on all responses | Integration | `pytest tests/test_security_headers.py -v` | ✅ | ✅ green | | 02-02-T2 | 02 | 2 | AUTH-01, AUTH-04 | useAuthStore never writes to localStorage; login() passes backup_code | Unit (Vitest) | `cd frontend && npx vitest run src/stores/__tests__/auth.test.js` | ✅ | ✅ green | | 02-03-T1 | 03 | 3 | AUTH-03 | TOTP setup returns provisioning_uri; enable rate-limited 10/min | Integration | `pytest tests/test_auth_totp.py -v` | ✅ | ✅ green | | 02-03-T1 | 03 | 3 | AUTH-05 | Password reset confirm returns 200 with no access_token (no auto-login) | Integration | `pytest tests/test_auth_totp.py::test_password_reset_confirm_valid_no_autologin -v` | ✅ | ✅ green | | 02-03-T1 | 03 | 3 | AUTH-06 | logout-all revokes all refresh tokens | Integration | `pytest tests/test_auth_totp.py::test_logout_all_revokes_tokens -v` | ✅ | ✅ green | | 02-03-T1 | 03 | 3 | **AUTH-08** | TOTP replay: same code rejected within 90-second window | Integration | `pytest tests/test_totp_replay.py -v` | ✅ | ✅ green | | 02-03-T1 | 03 | 3 | **SEC-03** | Constant-time comparison: hmac.compare_digest used; verify_password/backup_code reject wrong inputs | Unit | `pytest tests/test_constant_time_auth.py -v` | ✅ | ✅ green | | 02-03-T2 | 03 | 3 | AUTH-01 | PasswordStrengthBar: correct 0-4 score (length + char types) | Unit (Vitest) | `cd frontend && npx vitest run src/components/auth/__tests__/PasswordStrengthBar.test.js` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | ADMIN-01 | Admin-created users have password_must_change=True | Integration | `pytest tests/test_admin_api.py::test_create_user_sets_password_must_change -v` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | ADMIN-02 | Deactivate/reactivate user + sole-admin guard | Integration | `pytest tests/test_admin_api.py::test_deactivate_user tests/test_admin_api.py::test_cannot_deactivate_only_admin -v` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | ADMIN-03 | Admin password reset sends email via Celery; no impersonation | Integration | `pytest tests/test_admin_api.py::test_password_reset_initiates_email -v` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | ADMIN-04 | Quota update with warning when limit < used_bytes | Integration | `pytest tests/test_admin_api.py::test_quota_below_usage_warning -v` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | ADMIN-05 | AI provider/model assignment per user | Integration | `pytest tests/test_admin_api.py::test_update_ai_config -v` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | ADMIN-07 | No impersonation endpoint exists (404/422) | Integration | `pytest tests/test_admin_api.py::test_admin_impersonation_not_found -v` | ✅ | ✅ green | | 02-04-T1 | 04 | 4 | SEC-07 | get_current_admin enforced: non-admin gets 403 | Integration | `pytest tests/test_admin_api.py::test_list_users_requires_admin -v` | ✅ | ✅ green | | 02-04-T2 | 04 | 4 | SEC-07 | Admin responses never include password_hash | Integration | `pytest tests/test_admin_api.py::test_admin_response_no_password_hash -v` | ✅ | ✅ green | | 02-05-T2 | 05 | 5 | ADMIN-01..03 | AdminUsersTab: onMount fetch, deactivate call, empty state | Unit (Vitest) | `cd frontend && npx vitest run src/components/admin/__tests__/AdminUsersTab.test.js` | ✅ | ✅ green | | 02-05-T2 | 05 | 5 | ADMIN-04 | AdminQuotasTab: save call, below-usage warning displayed | Unit (Vitest) | `cd frontend && npx vitest run src/components/admin/__tests__/AdminQuotasTab.test.js` | ✅ | ✅ green | | 02-05-T2 | 05 | 5 | ADMIN-05 | AdminAiConfigTab: save call, 1.5s Saved confirmation | Unit (Vitest) | `cd frontend && npx vitest run src/components/admin/__tests__/AdminAiConfigTab.test.js` | ✅ | ✅ green | | 02-06-T2 | 06 | 1 | SEC-07, AUTH-01 | `requiresAdmin` guard redirects non-admin to `/`; all 4 auth routes carry `meta.layout: 'auth'` | Unit (Vitest) | `cd frontend && npx vitest run src/router/__tests__/router.guard.test.js` | ✅ | ✅ green | | 02-06-T3a | 06 | 1 | AUTH-03 | TotpEnrollment renders `` QR code in verify step; no `otpauth://` link | Unit (Vitest) | `cd frontend && npx vitest run src/components/auth/__tests__/TotpEnrollment.test.js` | ✅ | ✅ green | | 02-06-T3b | 06 | 1 | AUTH-03, AUTH-04 | SettingsAccountTab mounts all 4 sections; totp_enabled toggle shows correct 2FA state | Unit (Vitest) | `cd frontend && npx vitest run src/components/settings/__tests__/SettingsAccountTab.test.js` | ✅ | ✅ green | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* --- ## Manual-Only Verifications | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| | TOTP QR code renders and scans correctly | AUTH-03 | Requires a physical authenticator app (Google Auth / Authy) | 1. Register + login; 2. GET /api/auth/totp/setup; 3. Open provisioning_uri in authenticator app; 4. Verify 6-digit code is accepted by POST /api/auth/totp/enable | | Email delivery (SMTP) | AUTH-05, ADMIN-03 | Requires live SMTP server | Configure SMTP_* env vars; trigger password reset; verify email arrives with correct reset link | | Admin panel browser rendering | ADMIN-01..05 | Vue component visual contract | Start dev server; log in as admin; verify all three tabs render correctly per UI-SPEC | | httpOnly cookie SameSite=Strict | SEC-01 | Browser DevTools required | Log in via browser; open DevTools → Application → Cookies; verify refresh_token is HttpOnly + SameSite=Strict | --- ## Validation Audit 2026-05-31 | Metric | Count | |--------|-------| | Gaps found | 6 (1 MISSING backend, 2 PARTIAL backend, 3 MISSING frontend) | | Resolved | 6 | | Escalated | 0 | | New test files | 8 | | Total tests added | 60 (14 backend + 46 frontend) | ## Validation Audit 2026-06-01 | Metric | Count | |--------|-------| | Gaps found | 3 (3 MISSING frontend — plan 02-06 not covered in prior audit) | | Resolved | 3 | | Escalated | 0 | | New test files | 3 | | Total tests added | 16 (router guard × 10, TotpEnrollment × 2, SettingsAccountTab × 4) | --- ## Validation Sign-Off - [x] All tasks have automated verify or Manual-Only justification - [x] Sampling continuity: no 3 consecutive tasks without automated verify - [x] Wave 0 covers all MISSING references (gaps filled by audit) - [x] No watch-mode flags - [x] `nyquist_compliant: true` set in frontmatter **Approval:** 2026-05-31