diff --git a/.planning/phases/02-users-authentication/02-VERIFICATION.md b/.planning/phases/02-users-authentication/02-VERIFICATION.md
index 370bd01..e321bff 100644
--- a/.planning/phases/02-users-authentication/02-VERIFICATION.md
+++ b/.planning/phases/02-users-authentication/02-VERIFICATION.md
@@ -1,76 +1,86 @@
---
phase: 02-users-authentication
-verified: 2026-05-22T18:18:52Z
-status: gaps_found
-score: 4/5
+verified: 2026-06-01T14:35:00Z
+status: human_needed
+score: 6/6
overrides_applied: 0
-re_verification: false
-gaps:
+re_verification:
+ previous_status: gaps_found
+ previous_score: 4/5
+ gaps_closed:
+ - "Admin can create a new user via POST /api/admin/users without HTTP 500 (session.flush() confirmed, regression test passes)"
+ - "Auth/login pages show AuthLayout only — App.vue now layout-aware via route.meta.layout conditional"
+ - "After logout the sidebar is gone — same App.vue v-if fix covers the logged-out state"
+ - "Non-admin user navigating to /admin is redirected to / — requiresAdmin guard in beforeEach wired"
+ - "TOTP enrollment shows scannable QR image — qrcode library installed, img tag renders QR from QRCode.toDataURL"
+ - "TOTP enrollment accessible from Account tab in /settings — SettingsAccountTab.vue created and wired"
+ gaps_remaining:
+ - "SC5 (admin JWT returns 403 on document content) — deferred to Phase 3 per D-07 CONTEXT.md decision"
+ open_findings:
+ - "CR-01: change_password does not revoke active sessions (CLAUDE.md line 153 — security invariant)"
+ - "CR-02: disable_totp does not revoke active sessions (CLAUDE.md line 153 — security invariant)"
+ - "CR-03: ConfirmBlock.vue has no named slot — #confirm-button in SettingsAccountTab is dead (spinner/guard never activates)"
+ - "WR-01: decodeURIComponent on query param in SettingsView.vue has no error handling — URIError on malformed %encoding"
+ - "WR-02: TOTP verify code button re-enables during 800ms success flash — double-submission possible"
+ - "WR-03: Password error routing uses fragile string-matching on raw API messages"
+ - "WR-04: topicsStore.fetchTopics() fires unconditionally on every page load including auth pages"
+ regressions: []
+deferred:
- truth: "Attempting to access document content via an admin JWT returns 403"
- status: partial
- reason: "The documents API (backend/api/documents.py) has no authentication enforcement at all — no get_current_user dependency, no JWT validation. Any request (with or without a JWT) accesses documents. An admin JWT does not receive a 403; it is simply ignored. Admin.py has no document-content endpoints (SEC-07's admin-response clause is met), but the documents API does not reject admin-role tokens or any tokens."
- artifacts:
- - path: "backend/api/documents.py"
- issue: "No auth dependency on any endpoint. get_current_user is not imported or used. This is the pre-Phase-3 single-user API state — per D-03 note in STATE.md, auth enforcement on documents is deferred to Phase 3."
- missing:
- - "Either: add get_current_user + role check to documents.py endpoints NOW to make admin-JWT return 403, OR explicitly scope SC5's 'admin JWT returns 403' clause as a Phase 3 deliverable in ROADMAP.md."
+ addressed_in: "Phase 3"
+ evidence: "Phase 3 goal: Document Migration and Multi-User Isolation. CONTEXT.md D-07: existing /api/documents stays public in Phase 2; gains get_current_user guards in Phase 3. REQUIREMENTS.md traceability: SEC-04 mapped to Phase 3."
human_verification:
- test: "TOTP enrollment end-to-end"
- expected: "User scans otpauth:// link in authenticator app, enters 6-digit code, sees 10 backup codes, checks acknowledgment checkbox, enables 2FA, and thereafter login requires TOTP code"
- why_human: "Multi-step UI flow with authenticator app interaction cannot be verified by grep or build"
+ expected: "User navigates to /settings, clicks Account tab, sees TotpEnrollment component. In setup step: QR image renders (not a text link). User scans QR with authenticator app. In verify step: user enters 6-digit code. In backup-codes step: 10 codes displayed in 2-column grid with Copy All button and acknowledgment checkbox gating Enable 2FA. After enabling: account shows 2FA active; next login requires TOTP code."
+ why_human: "Multi-step flow requires authenticator app; QR image rendering requires visual confirmation; backup-code acknowledgment gate requires UI interaction"
- test: "Password reset email delivery"
- expected: "User receives reset email at their address, link expires after 1 hour, following the link and setting a new password returns 200 with 'Please sign in' (no auto-login), user must pass TOTP gate on next login"
- why_human: "Requires SMTP/Celery infrastructure running and actual email receipt"
- - test: "Sign out all devices from account settings"
- expected: "Clicking 'Sign out all devices' in AccountView invalidates all active sessions; other browser tabs/devices lose access on next request"
- why_human: "Multi-session behavior requires multiple live browser sessions"
- - test: "Admin panel tab navigation"
- expected: "Admin user sees shield icon 'Admin' link in sidebar, can navigate Users / Quotas / AI Config tabs, non-admin user does not see the admin link"
- why_human: "UI rendering and role-conditional visibility require browser"
+ expected: "User triggers /password-reset for a real email account. Email arrives with correct signed link. Link expires after 1 hour. Following the link and submitting a new strong password returns success message with no auto-login. User must go to /login and pass TOTP gate if 2FA was enabled."
+ why_human: "Requires SMTP/Celery infrastructure running and actual email receipt; anti-enumeration 202 response cannot confirm dispatch"
+ - test: "Sign out all devices"
+ expected: "User clicks Sign out all devices in /settings Account tab. ConfirmBlock appears. On confirm: all sessions revoked, current browser redirected to /login. A second browser tab's next authenticated request fails with 401."
+ why_human: "Multi-session testing requires two live sessions; refresh token family invalidation requires browser-level verification"
+ - test: "Admin panel role visibility and CRUD"
+ expected: "Regular user does not see Admin link in sidebar and cannot navigate to /admin (redirected to /). Admin user sees Admin link with shield icon; can navigate Users/Quotas/AI Config tabs; can create a test user (no HTTP 500); can deactivate a user with inline confirmation showing correct email."
+ why_human: "Visual rendering, role-conditional DOM, and inline confirmation UX require browser interaction"
+ - test: "CR-01 / CR-02: Session revocation on password change and TOTP disable"
+ expected: "After successfully changing password in Account tab: current session is invalidated and user is redirected to /login (or receives a clear sign-out prompt). Any other active refresh tokens are revoked. Same behavior after disabling TOTP. A previously-valid refresh cookie must fail with 401 after the change."
+ why_human: "Requires confirming backend revocation behavior with live sessions; current code does NOT revoke sessions (CR-01/CR-02 are open code-review blockers — this test is expected to FAIL until the backend fix is applied)"
---
-# Phase 2: Users & Authentication — Verification Report
+# Phase 2: Users & Authentication — Verification Report (Re-Verification after Plan 06 Gap Closure)
**Phase Goal:** Users can register, log in (with optional TOTP 2FA), reset their password, and sign out all active sessions; admins can manage user accounts and assign AI providers — all enforced by a complete FastAPI dependency chain.
-**Verified:** 2026-05-22T18:18:52Z
-**Status:** GAPS FOUND
-**Re-verification:** No — initial verification
+**Verified:** 2026-06-01T14:35:00Z
+**Status:** HUMAN NEEDED (all automated checks pass; 5 items require human testing; 2 security invariants from CLAUDE.md require developer resolution)
+**Re-verification:** Yes — after Plan 06 gap closure (5 UAT gaps closed)
---
## Goal Achievement
-### Observable Truths (Success Criteria)
+### Observable Truths (Success Criteria from Plan 06 must_haves)
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
-| SC1 | New user can register with strength-validated password; HIBP-listed password rejected | VERIFIED | `check_hibp()` in services/auth.py uses k-anonymity SHA-1 prefix (5 chars); `_validate_password_strength()` enforces 12+ chars, upper, lower, digit, special; 4 tests covering register success, duplicate email, weak password, HIBP breach all pass |
-| SC2 | User can enroll TOTP authenticator, receive 10 backup codes with acknowledgment gate, TOTP required on every subsequent login, backup code invalidated on first use | VERIFIED | `provision_totp()`, `generate_backup_codes(10)`, `store_backup_codes()` in services/auth.py; `BackupCodesDisplay.vue` has acknowledgment checkbox gating "Enable 2FA" button; `verify_backup_code()` iterates all codes (constant-time) and sets `used_at=now()` on match; Redis replay prevention on `totp_used:{user_id}:{code}` TTL=90s |
-| SC3 | User can reset password via email link (1-hour token), no auto-login after reset, returns to TOTP gate | VERIFIED | `create_password_reset_token()` / `decode_password_reset_token()` uses `typ="password-reset"` claim; `/password-reset/confirm` explicitly does NOT return access_token (comment: "AUTH-05 — user must pass TOTP gate on next login"); anti-enumeration: `/password-reset` always returns 202; test `test_password_reset_confirm_valid_no_autologin` passes |
-| SC4 | User can trigger "sign out all devices"; other sessions immediately invalidated; reuse of rotated refresh token revokes entire family | VERIFIED | `revoke_all_refresh_tokens()` marks all user's tokens revoked; `rotate_refresh_token()` checks `row.revoked=True` → calls `revoke_all_refresh_tokens()` + `send_security_alert_email.delay()` + raises `ValueError("token_family_revoked")`; `logout_all` endpoint (lines 370-379 api/auth.py) calls `revoke_all_refresh_tokens()` |
-| SC5 | Admin can create/deactivate/reset user accounts and assign AI provider; **attempting to access document content via admin JWT returns 403** | PARTIAL — BLOCKER | Admin CRUD endpoints verified (7 endpoints, `get_current_admin` on all, `_user_to_dict()` whitelist excludes `password_hash`/`credentials_enc`). BUT: `backend/api/documents.py` has NO auth enforcement at all — any request (with or without JWT) accesses documents. An admin JWT is not rejected; it is simply ignored. The 403 clause of SC5 is not met. |
+| T1 | Admin can create a new user via POST /api/admin/users without HTTP 500 | VERIFIED | `await session.flush()` at admin.py:247 (before `write_audit_log()`); `test_create_user_writes_audit_log` passes (1 passed, 2.23s) |
+| T2 | Login, register, and password-reset pages show AuthLayout only — no sidebar, no user identity footer | VERIFIED | App.vue line 2: `` |
+| T6 | TOTP enrollment option is accessible from a tab within /settings (Account tab) | VERIFIED | SettingsView.vue:92 imports SettingsAccountTab; line 100 `{ id: 'account', label: 'Account' }` in tabs array; line 52 `