docs(07.2-03): complete Wave 2 plan — user_nbf write sites in all 4 security-event handlers SUMMARY
This commit is contained in:
+69
@@ -0,0 +1,69 @@
|
||||
---
|
||||
plan: 07.2-03
|
||||
phase: 07.2-security-jti-claim-redis-access-token-revocation-inserted
|
||||
status: complete
|
||||
wave: 2
|
||||
completed: 2026-06-06
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- backend/api/auth.py
|
||||
- backend/api/admin.py
|
||||
- backend/tests/test_auth_api.py
|
||||
- backend/tests/test_admin_api.py
|
||||
---
|
||||
|
||||
# Plan 07.2-03 Summary — Wave 2: user_nbf Write Sites
|
||||
|
||||
## What Was Built
|
||||
|
||||
Wired the `user_nbf` Redis writes into all four security-event handlers that trigger
|
||||
access-token revocation, completing the full Phase 7.2 revocation pipeline.
|
||||
|
||||
**Task 1 — `backend/api/auth.py`:**
|
||||
- Added `import time` at module level (after `import hashlib`)
|
||||
- Added `await request.app.state.redis.set(f"user_nbf:{current_user.id}", int(time.time()), ex=settings.access_token_expire_minutes * 60)` immediately before `await session.commit()` in:
|
||||
- `change_password` handler (uses `request.app.state.redis` directly)
|
||||
- `enable_totp` handler (uses `redis_client` already in scope)
|
||||
- `disable_totp` handler (uses `request.app.state.redis` directly)
|
||||
- Promoted all 3 Wave 0 xfail stubs in `test_auth_api.py` to PASSED assertions
|
||||
|
||||
**Task 2 — `backend/api/admin.py`:**
|
||||
- Added `import time` at module level (after `import uuid`)
|
||||
- Added `from config import settings` at module level (was previously only locally imported)
|
||||
- Added `await request.app.state.redis.set(f"user_nbf:{user.id}", int(time.time()), ex=settings.access_token_expire_minutes * 60)` inside the `if not body.is_active:` block in the deactivation handler, after `await revoke_all_refresh_tokens(session, user.id)` — does NOT execute on activation path
|
||||
- Promoted both Wave 0 admin xfail stubs in `test_admin_api.py` to PASSED assertions
|
||||
|
||||
**Task 3 — Full regression run:**
|
||||
- Full backend suite: 387 passed, 1 failed (pre-existing `test_extract_docx` — `ModuleNotFoundError: No module named 'docx'`, unrelated to Phase 7.2), 7 xfailed (pre-existing from prior phases), 6 skipped
|
||||
|
||||
## Patterns Established
|
||||
|
||||
- **user_nbf write pattern**: `await redis.set(f"user_nbf:{user.id}", int(time.time()), ex=settings.access_token_expire_minutes * 60)` before `session.commit()`
|
||||
- **Conditional deactivation-only write**: write is strictly inside `if not body.is_active:` — reactivation does NOT write user_nbf (invariant verified by `test_activate_user_does_not_write_user_nbf`)
|
||||
- **TTL derived from settings**: `settings.access_token_expire_minutes * 60` — stays synchronized if TTL changes in config, not hardcoded 900
|
||||
|
||||
## Patterns to Avoid
|
||||
|
||||
- Do NOT write `user_nbf` on activation/reactivation (is_active=True) — would force legitimate users to re-login immediately
|
||||
- Do NOT hardcode TTL as 900 — use `settings.access_token_expire_minutes * 60`
|
||||
|
||||
## What This Enables
|
||||
|
||||
Full Phase 7.2 revocation pipeline is now complete:
|
||||
1. Every access token contains a `jti` UUID claim (Plan 02)
|
||||
2. `get_current_user` reads `user_nbf:{user_id}` from Redis and rejects tokens with `iat < nbf` (Plan 02)
|
||||
3. All four security events write `user_nbf:{user_id}` to Redis: change_password, enable_totp, disable_totp (this plan), and admin deactivation (this plan)
|
||||
|
||||
Any token issued before a security event is blocked on the next request — the 15-minute access-token window is closed.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- All 9 VALIDATION.md behaviors: PASSED
|
||||
- `grep -c "user_nbf" backend/api/auth.py` → 3 (change_password, enable_totp, disable_totp)
|
||||
- `grep -c "user_nbf" backend/api/admin.py` → 1 (deactivation only)
|
||||
- `grep -c "user_nbf" backend/deps/auth.py` → 5 (reader: multiple log/comparison lines)
|
||||
- `except HTTPException: raise` at line 81 precedes `except Exception` at line 83 in `deps/auth.py` — Pitfall 1 guard confirmed
|
||||
- `grep -E "^import time" backend/api/auth.py backend/api/admin.py` → 2 lines
|
||||
- Zero new test failures introduced by Phase 7.2 (pre-existing `test_extract_docx` failure is a missing `python-docx` module, unrelated)
|
||||
Reference in New Issue
Block a user