--- phase: 02-users-authentication reviewed: 2026-06-01T00:00:00Z depth: standard files_reviewed: 6 files_reviewed_list: - frontend/src/components/settings/SettingsAccountTab.vue - frontend/src/App.vue - frontend/src/router/index.js - frontend/src/views/SettingsView.vue - frontend/src/components/auth/TotpEnrollment.vue - frontend/package.json findings: critical: 3 warning: 4 info: 1 total: 8 status: issues_found --- # Phase 02: Code Review Report **Reviewed:** 2026-06-01T00:00:00Z **Depth:** standard **Files Reviewed:** 6 **Status:** issues_found ## Summary The gap-closure plan (02-06) wires up layout switching, auth route guards, and the TOTP enrollment QR improvement. The layout-aware App.vue and the `meta.layout='auth'` pattern are sound. The navigation guard correctly uses the `!to.meta.public` predicate to cover all authenticated routes — including those that carry no explicit `meta.requiresAuth` flag (/, /topics, /settings, etc.) — and deduplicates concurrent refresh calls via `_refreshInFlight`. No XSS vectors were found; Vue text interpolation auto-escapes all user-controlled values throughout. Three blockers were found: the backend `change_password` and `disable_totp` endpoints do not revoke active sessions as required by CLAUDE.md line 153, and a dead `#confirm-button` slot in `SettingsAccountTab.vue` silently discards the spinner/disabled guard on "Sign out all devices," allowing double-invocation with no visual feedback. Four warnings cover a URIError crash path in `onMounted`, a TOTP re-submission window after successful verification, fragile string-matching for password error routing, and an unconditional authenticated API call on every auth page load. ## Critical Issues ### CR-01: Password change does not revoke active sessions (backend, auth.py) **File:** `backend/api/auth.py:520` **Issue:** CLAUDE.md line 153 is explicit: "Password change, TOTP enroll/revoke, and account deactivation immediately revoke all active sessions." The `change_password` endpoint updates `password_hash` and commits but performs no refresh-token revocation. An attacker who obtained a valid refresh cookie before the password change retains full access until their token naturally expires (up to 30 days). The frontend `SettingsAccountTab.vue` also makes no attempt to trigger session revocation after a successful password change. **Fix:** ```python # After session.commit() in change_password, revoke all refresh tokens for the user: await session.execute( delete(RefreshToken).where(RefreshToken.user_id == current_user.id) ) await session.commit() ``` Also add a `router.push('/login')` (or `authStore.logout()`) call in the frontend `changePassword()` success path so the current session is visibly invalidated. --- ### CR-02: TOTP disable does not revoke active sessions (backend, auth.py) **File:** `backend/api/auth.py:641` **Issue:** Same CLAUDE.md line 153 requirement applies to TOTP revoke. The `disable_totp` endpoint clears `totp_secret` / `totp_enabled` and deletes backup codes but does not revoke any refresh tokens. An attacker with a hijacked session can downgrade a victim's account security by removing 2FA, and both the attacker and any existing stolen sessions remain valid afterward. **Fix:** ```python # After deleting backup codes in disable_totp, revoke all refresh tokens: await session.execute( delete(RefreshToken).where(RefreshToken.user_id == current_user.id) ) await session.commit() ``` --- ### CR-03: Dead `#confirm-button` slot removes spinner and disabled guard from "Sign out all devices" **File:** `frontend/src/components/settings/SettingsAccountTab.vue:149-161` **Issue:** `ConfirmBlock.vue` defines no named slot — it has a single hard-coded `