--- phase: 07.1 plan: 02 type: execute wave: 2 depends_on: - 07.1-01 files_modified: - backend/tests/test_auth_api.py - frontend/src/components/settings/SettingsAccountTab.vue - frontend/src/components/auth/TotpEnrollment.vue autonomous: true requirements: - CR-01 - CR-02 - CR-03 must_haves: truths: - "Tests verify sessions_revoked > 0 when other sessions exist for change_password" - "Tests verify sessions_revoked > 0 when other sessions exist for enable_totp" - "Tests verify sessions_revoked > 0 when other sessions exist for disable_totp" - "Tests verify current session is NOT revoked (skip behavior)" - "Frontend shows a toast notification when sessions_revoked > 0 after password change" - "Frontend shows a toast notification when sessions_revoked > 0 after TOTP enable" - "Frontend shows a toast notification when sessions_revoked > 0 after TOTP disable" artifacts: - path: "backend/tests/test_auth_api.py" provides: "3 new tests covering sessions_revoked behavior + skip for all three endpoints" contains: "sessions_revoked" - path: "frontend/src/components/settings/SettingsAccountTab.vue" provides: "toast notification on sessions_revoked > 0 for changePassword and disableTotp" contains: "sessions_revoked" - path: "frontend/src/components/auth/TotpEnrollment.vue" provides: "emit or callback for sessions_revoked > 0 after enable_totp" contains: "sessions_revoked" key_links: - from: "SettingsAccountTab.vue (changePassword)" to: "SettingsView.vue toast pattern" via: "local sessionRevokedToast ref, same inline HTML pattern as oauthSuccessProvider toast" pattern: "sessions_revoked" - from: "TotpEnrollment.vue (confirmEnrollment)" to: "SettingsAccountTab.vue parent" via: "emit('enrolled', { sessions_revoked }) OR local toast inside TotpEnrollment" pattern: "sessions_revoked" --- Add tests proving the skip-current-session behavior for all three privilege-change endpoints, and add a brief frontend toast notification triggered when `sessions_revoked > 0` is returned from `change_password`, `enable_totp`, or `disable_totp`. Purpose: Closes the test coverage gap (CLAUDE.md testing protocol: every new behavior must have at least one test) and delivers the user-facing UX signal described in D-05. Output: 3 new pytest tests in `test_auth_api.py`; toast in `SettingsAccountTab.vue` and `TotpEnrollment.vue`. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/phases/07.1-security-session-revocation-on-privilege-change/07.1-CONTEXT.md @.planning/phases/07.1-security-session-revocation-on-privilege-change/07.1-01-SUMMARY.md Task 1: Add tests for sessions_revoked behavior on all three endpoints backend/tests/test_auth_api.py backend/tests/test_auth_api.py — read lines 1-50 (imports, fixture helpers `_register` and `_login`) and lines 240-258 (`test_change_password_success` — the pattern to extend). These show how to register a user, obtain a token, and call change-password. backend/tests/conftest.py — check the `authed_client`, `auth_user`, and `db_session` fixture definitions to understand what is available. backend/api/auth.py — check the updated `change_password`, `enable_totp`, and `disable_totp` response shapes from Plan 01 (to match `sessions_revoked` key name exactly). - test_change_password_revokes_other_sessions: Register user, log in on two different clients (simulate two sessions by inserting a second RefreshToken row directly in the DB or calling create_refresh_token twice), then call POST /api/auth/change-password with the first session's token. Assert response contains `sessions_revoked >= 1`. Assert the second refresh token row is now `revoked=True` in the DB. Assert the first (calling) session's refresh token row is still `revoked=False` (current session was skipped). Note: `authed_client` does not set a refresh_token cookie by default — insert a second RefreshToken row via `auth_service.create_refresh_token(db_session, user.id)` to create a revocable "other session", then assert its row is revoked after the call. - test_enable_totp_revokes_other_sessions: Register user, set `user.totp_secret` via DB fixture (same pattern as existing `test_login_backup_code_success` at line ~308), insert a second RefreshToken row, call POST /api/auth/totp/enable with a patched `verify_totp` returning True. Assert `sessions_revoked >= 1` in response and the second token row is revoked. - test_disable_totp_revokes_other_sessions: Register user, set `user.totp_enabled=True` in DB, insert a second RefreshToken row, call DELETE /api/auth/totp. Assert `sessions_revoked >= 1` in response and the second token row is revoked. Append three new `@pytest.mark.asyncio` test functions to `test_auth_api.py` after the existing `test_change_password_success` test. For each test, import pattern: use `from services import auth as auth_service` (already available in the test module via existing imports — verify before adding) and the `db_session: AsyncSession` fixture. To simulate "other sessions": call `await auth_service.create_refresh_token(db_session, user.id)` to insert an additional token row. This is the canonical way to create a second session without running a full login flow (which requires the refresh cookie roundtrip). After the privilege-change API call, query `select(RefreshToken).where(RefreshToken.user_id == user.id)` and inspect the rows: - The row matching the token created by `create_refresh_token` (the "other session") must have `revoked=True`. - If the `authed_client` fixture set a real refresh_token cookie, the calling session's row must have `revoked=False`. If the fixture does NOT set a cookie (verify by reading conftest), then `skip_token_hash` will be `None` and all tokens including the "other" one will be revoked — the test still asserts `sessions_revoked >= 1`. For `test_enable_totp_revokes_other_sessions`: patch `services.auth.verify_totp` to return `True` so the TOTP code check is bypassed (same pattern as existing TOTP tests). Also patch `services.auth.store_backup_codes` to avoid real Argon2 hashing overhead. Import `from sqlalchemy import select` and `from models import RefreshToken` for the DB assertion queries — check existing imports in the test file first to avoid duplicates. Use `with patch(...)` context managers exactly as in the existing breach-check tests. cd /Users/nik/Documents/Progamming/document_scanner/backend && python -m pytest tests/test_auth_api.py -k "revokes_other_sessions" -v 2>&1 | tail -20 - `pytest tests/test_auth_api.py -k "revokes_other_sessions" -v` reports 3 PASSED tests with no failures - Each test asserts `resp.json()["sessions_revoked"] >= 1` - Each test queries the DB and asserts the "other session" RefreshToken row has `revoked=True` - Full test suite `pytest -x -q` still passes with no regressions Task 2: Add sessions-revoked toast to SettingsAccountTab and TotpEnrollment frontend/src/components/settings/SettingsAccountTab.vue frontend/src/components/auth/TotpEnrollment.vue frontend/src/components/settings/SettingsAccountTab.vue — read the full file (254 lines). Observe: `changePassword()` at line 193 calls `api.changePassword(...)` and sets `passwordSuccess`; `disableTotp()` at line 226 calls `api.totpDisable()`. Note there is no existing toast in this component — the toast pattern to follow is in `SettingsView.vue` (the `oauthSuccessProvider` inline HTML block at lines 6-28). frontend/src/views/SettingsView.vue — read lines 1-30 (the OAuth success toast block). This is the exact inline HTML pattern to replicate in SettingsAccountTab. frontend/src/components/auth/TotpEnrollment.vue — read lines 145-166 (`confirmEnrollment()` function that calls `api.totpEnable()`). Note the `emit('enrolled')` call at line 165 and the `backupCodes` data returned at line 150. ### SettingsAccountTab.vue changes Add a new reactive ref `sessionRevokedToast = ref(false)` alongside the existing refs in the `