Files
kite/.planning/phases/07.2-security-jti-claim-redis-access-token-revocation-inserted/07.2-03-SUMMARY.md
T

3.8 KiB

plan, phase, status, wave, completed, key-files
plan phase status wave completed key-files
07.2-03 07.2-security-jti-claim-redis-access-token-revocation-inserted complete 2 2026-06-06
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_docxModuleNotFoundError: 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)