Wave 0: CR-01/02/03 test stubs + api/schemas.py (CloudConnectionOut migration) Wave 1: useToastStore stub + Phase 7.1 frontend completion Wave 2 (parallel): api/admin/, api/documents/, api/auth/ package splits + client.js decomposition + PERF-01 deps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
130 lines
8.7 KiB
Markdown
130 lines
8.7 KiB
Markdown
---
|
|
phase: 08-stack-upgrade-backend-decomposition
|
|
plan: 01
|
|
type: execute
|
|
wave: 0
|
|
depends_on: []
|
|
files_modified:
|
|
- backend/tests/test_auth.py
|
|
autonomous: true
|
|
requirements: [CR-01, CR-02, CR-03]
|
|
tags: [tests, session-revocation, wave-0]
|
|
must_haves:
|
|
truths:
|
|
- "Three new pytest tests exist for CR-01, CR-02, CR-03 as xfail stubs"
|
|
- "Stubs name the exact behavior they cover so executor of plan 08-03 can promote them"
|
|
- "Running pytest -v shows three new tests with status xfail (not error, not pass)"
|
|
artifacts:
|
|
- path: "backend/tests/test_auth.py"
|
|
provides: "Three new xfail test stubs for session revocation on privilege change"
|
|
contains: "test_change_password_revokes_other_sessions"
|
|
key_links:
|
|
- from: "backend/tests/test_auth.py"
|
|
to: "backend/api/auth.py change_password / enable_totp / disable_totp"
|
|
via: "test exercises real handler via httpx.AsyncClient"
|
|
pattern: "client.post\\(\"/api/auth/(change-password|totp/enable|totp)\""
|
|
---
|
|
|
|
<objective>
|
|
Create Wave 0 test stubs (xfail) for CR-01, CR-02, CR-03. These tests will be promoted to passing in plan 08-03 after the `useToastStore` stub and frontend wiring are complete. The backend implementation for all three is ALREADY in place (verified in RESEARCH.md §"Wave 1: Session Revocation"); these stubs lock the expected behavior contract before any refactoring touches `api/auth.py`.
|
|
|
|
Purpose: Anti-regression Nyquist scaffold — when plan 08-06 splits `api/auth.py` into `api/auth/` package, these promoted tests guarantee the session revocation logic still works through the new module structure.
|
|
|
|
Output: Three xfail-marked tests added to `backend/tests/test_auth.py`.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/08-stack-upgrade-backend-decomposition/08-CONTEXT.md
|
|
@.planning/phases/08-stack-upgrade-backend-decomposition/08-RESEARCH.md
|
|
@.planning/phases/08-stack-upgrade-backend-decomposition/08-VALIDATION.md
|
|
@backend/api/auth.py
|
|
@backend/services/auth.py
|
|
@backend/tests/test_auth.py
|
|
@backend/tests/conftest.py
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Add three xfail stubs for CR-01/CR-02/CR-03 to test_auth.py</name>
|
|
<files>backend/tests/test_auth.py</files>
|
|
<read_first>
|
|
- backend/tests/test_auth.py (read the full file to discover existing fixtures, login helper pattern, and how authenticated requests are issued)
|
|
- backend/tests/conftest.py (locate `auth_user` fixture, `client` fixture, and `auth_limiter` reset hook)
|
|
- backend/api/auth.py lines 478-540 (change_password — confirm response shape includes `sessions_revoked`)
|
|
- backend/api/auth.py lines 579-636 (enable_totp — confirm response shape)
|
|
- backend/api/auth.py lines 641-682 (disable_totp — confirm response shape)
|
|
- backend/services/auth.py lines 250-273 (revoke_all_refresh_tokens signature with skip_token_hash)
|
|
</read_first>
|
|
<behavior>
|
|
- Test `test_change_password_revokes_other_sessions`: register user, log in twice to obtain TWO refresh-token rows in DB (call them session A and session B). Using session A's access token, POST `/api/auth/change-password` with the correct current password and a new valid password. Assert: response 200, body contains `"sessions_revoked": 1` (session B revoked, session A preserved via `skip_token_hash`); session A's refresh token is still usable on POST `/api/auth/refresh`; session B's refresh token fails on POST `/api/auth/refresh` with 401.
|
|
- Test `test_enable_totp_revokes_other_sessions`: register user, log in twice (sessions A and B). Using session A, POST `/api/auth/totp/setup` to obtain a `provisioning_uri`, derive a valid TOTP code via `pyotp.TOTP(secret).now()`, POST `/api/auth/totp/enable` with that code. Assert: response 200, body contains `"sessions_revoked": 1`, session B refresh fails 401, session A refresh succeeds.
|
|
- Test `test_disable_totp_revokes_other_sessions`: register user, enable TOTP, then log in twice with TOTP (sessions A and B). Using session A, DELETE `/api/auth/totp` with the current TOTP code. Assert: response 200, body contains `"sessions_revoked": 1`, session B refresh fails 401, session A refresh succeeds.
|
|
</behavior>
|
|
<action>
|
|
Append three test functions to `backend/tests/test_auth.py`, each decorated with `@pytest.mark.xfail(reason="Wave 0 stub — promoted to passing in 08-03", strict=False)`. Use the existing test patterns in the file (httpx.AsyncClient async fixtures, `auth_user` fixture from conftest, `await client.post(...)`). The three function names MUST be exactly:
|
|
- `async def test_change_password_revokes_other_sessions(client, db_session)`
|
|
- `async def test_enable_totp_revokes_other_sessions(client, db_session)`
|
|
- `async def test_disable_totp_revokes_other_sessions(client, db_session)`
|
|
Inside each test body, write the full assertion logic per the `<behavior>` block — do NOT leave them as bare `pass` stubs. The tests SHOULD pass right now (backend already implemented per RESEARCH.md), but `strict=False` xfail allows either xpassed or xfailed without erroring the suite. Plan 08-03 will remove the `@pytest.mark.xfail` decorator and confirm they pass cleanly. Use `pyotp.TOTP(secret).now()` for TOTP code generation; import pyotp at the top of the test file if not already imported. For the login-twice pattern, use distinct `User-Agent` headers on each login to ensure separate refresh-token rows; capture `response.cookies['refresh_token']` for each session.
|
|
</action>
|
|
<verify>
|
|
<automated>cd backend && pytest tests/test_auth.py::test_change_password_revokes_other_sessions tests/test_auth.py::test_enable_totp_revokes_other_sessions tests/test_auth.py::test_disable_totp_revokes_other_sessions --tb=no -q</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `grep -c "def test_change_password_revokes_other_sessions" backend/tests/test_auth.py` returns 1
|
|
- `grep -c "def test_enable_totp_revokes_other_sessions" backend/tests/test_auth.py` returns 1
|
|
- `grep -c "def test_disable_totp_revokes_other_sessions" backend/tests/test_auth.py` returns 1
|
|
- `grep -c "pytest.mark.xfail" backend/tests/test_auth.py` increased by at least 3 vs. pre-change baseline
|
|
- Pytest output for these three test IDs shows status `XPASS` or `XFAIL` — never `ERROR` and never `FAILED`
|
|
- Body of each test asserts `data["sessions_revoked"] == 1` (not `>= 1`, not `is not None`)
|
|
- Body of each test verifies the OTHER session's refresh token returns 401 on `/api/auth/refresh`
|
|
- Body of each test verifies the CURRENT session's refresh token returns 200 on `/api/auth/refresh`
|
|
</acceptance_criteria>
|
|
<done>Three xfail-marked tests appended to test_auth.py with full assertion logic exercising the documented CR-01/02/03 contracts; pytest collects and runs them without error.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| client → POST /api/auth/change-password | Authenticated user requests password change; backend must revoke all OTHER refresh tokens |
|
|
| client → POST /api/auth/totp/enable | Authenticated user enables TOTP; backend must revoke all OTHER refresh tokens |
|
|
| client → DELETE /api/auth/totp | Authenticated user disables TOTP; backend must revoke all OTHER refresh tokens |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-08-01-01 | Tampering | Test fixture isolation | mitigate | Each test creates its own user via `auth_user` fixture; tests do not share session state |
|
|
| T-08-01-02 | Repudiation | xfail strict mode | mitigate | `strict=False` permits XPASS without erroring; plan 08-03 removes the marker and runs strict |
|
|
| T-08-01-SC | Supply Chain | pytest, pyotp | accept | Already pinned in requirements.txt; this plan adds no new packages |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- `cd backend && pytest tests/test_auth.py --tb=no -q` shows three new tests with xpassed/xfailed status (no errors)
|
|
- Full backend suite still green: `cd backend && pytest -v` — zero new failures vs. baseline
|
|
- File diff shows only additions to `backend/tests/test_auth.py` (no other files touched)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Three new xfail tests exist with the exact names listed
|
|
- Each test body contains real assertion logic (not `pass` or `pytest.skip`)
|
|
- Pytest collects all three without error
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/08-stack-upgrade-backend-decomposition/08-01-SUMMARY.md` when done. Include: which fixtures were used, any helper functions added, the exact xfail decorator reason string, and the pre-/post-stub `pytest --co` count.
|
|
</output>
|