Migrate JWT token blacklist to database for persistence across restarts - #267
Migrate JWT token blacklist to database for persistence across restarts#267itsmiso-ai wants to merge 1 commit into
Conversation
Replace the in-memory `_TOKEN_BLACKLIST` dict with a `BlacklistedToken` SQLAlchemy model backed by a `blacklisted_tokens` table. The `blacklist_token()` and `is_blacklisted()` functions now accept an optional db session parameter for proper integration with dependency injection, falling back to creating their own session when called standalone. - Add BlacklistedToken model with jti (unique, indexed), expires_at, and created_at columns - Update blacklist_token() to insert into the database - Update is_blacklisted() to query the database and clean up expired entries - Handle naive datetimes from SQLite by assuming UTC - Export BlacklistedToken from main.py for test access - Add TestBlacklistedTokenPersistence test class with 4 tests Fixes #257 Signed-off-by: Saffron <263493777+itsmiso-ai@users.noreply.github.com>
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: auth_changes)
Recommendation: Approve
This PR correctly migrates the JWT token blacklist from an in-memory dict to a database-backed BlacklistedToken table, resolving issue PR 257.
Change-by-Change Findings
kubetix-api/kubetix_api/auth/__init__.py
- Removed the module-level
_TOKEN_BLACKLIST: dict[str, datetime]dict. blacklist_token(): Now writes to theBlacklistedTokentable. When called withdb=None(the default), it creates and closes its own session (session ownership pattern). When called with an external session, it uses it without committing — the caller owns the transaction. This is a clean pattern that prevents double-commits.is_blacklisted(): Reads from the DB, handles naive datetime conversion to UTC (important for SQLite compatibility), and auto-deletes expired entries on read. This prevents unbounded table growth._decode_payload(): Now accepts an optionaldbsession and passes it tois_blacklisted(). Backwards-compatible viadb=Nonedefault.get_current_user(): Updated to pass itsdbsession to_decode_payload().
kubetix-api/kubetix_api/models/__init__.py
BlacklistedTokenmodel: Usesjtias a unique, indexed column for fast lookups. Storesexpires_atfor TTL checks and auto-cleanup. Theidfield usessecrets.token_urlsafe(16)as the primary key (21-char URL-safe string), providing ample uniqueness.created_atenables auditability.
kubetix-api/main.py
logout()endpoint: Updated to create its own session, pass it toblacklist_token(), and handle commit/rollback/close. This correctly integrates with the new database-backed function.
tests/unit/test_api_auth.py
TestBlacklistedTokenPersistence: Four tests covering persistence, non-expired lookup, unknown token, and expired-entry cleanup. Theautouse=Truefixture ensures the test DB is initialized before each test.
Must-Check Items
- review auth flow for regression: ✅ Verified. The only callers of
_decode_payload()andget_current_user()that passdbare FastAPI route dependencies using the standardget_dboverride (via conftest). Standalone callers likedecode_token()usedb=None(backwards-compatible). The auth cookie test (test_auth_cookie.py) is unaffected as it tests cookie handling, not the blacklist internals. - verify session token handling is correct: ✅ Verified. Session ownership is correctly managed:
blacklist_token()commits only when it owns the session (own_session = True), avoiding double-commits inlogout(). Thelogout()endpoint correctly extractsjtiandexpfrom the token payload and converts the Unix timestamp to a timezone-awaredatetimebefore persisting.
Standards Compliance
- AGENTS.md conventions: Followed. New model added to
kubetix_api/models/__init__.py, auth logic stays inkubetix_api/auth/__init__.py, integration point is inmain.py. Tests added to the existing test file with descriptive class and method names. - Repository patterns: Uses SQLAlchemy ORM consistently. Session management follows the existing pattern (commit/rollback/close in caller for externally-provided sessions).
Linked Issue Fit
Issue PR 257 acceptance criteria: "Migrate to a database table so logouts survive restarts. Store (jti, expires_at) and check in is_blacklisted()." — ✅ Fully satisfied. The BlacklistedToken table stores jti and expires_at, and is_blacklisted() now reads from it.
Tool Harness Findings
No tool harness output was included in the corpus.
CI Check Results
All CI checks passed (test, lint, security, helm-check, e2e-tests, frontend-build, build-and-publish, directory-check). This PR is clean to merge.
Unknowns / Needs Verification
No unknown items. The implementation is complete and internally consistent.
What
Migrated the JWT token blacklist from an in-memory
dictto a database-backedBlacklistedTokentable, so logouts survive process restarts.Why
Issue #257: the previous in-memory
_TOKEN_BLACKLISTdict was lost on restart, causing previously-logged-out JWTs to b…Fixes #257
Opened by foreman on review GO (workload wl-misospace-kubetix-257).