Skip to content

Migrate JWT token blacklist to database for persistence across restarts - #267

Open
itsmiso-ai wants to merge 1 commit into
mainfrom
foreman/wl-misospace-kubetix-257/issue-257
Open

Migrate JWT token blacklist to database for persistence across restarts#267
itsmiso-ai wants to merge 1 commit into
mainfrom
foreman/wl-misospace-kubetix-257/issue-257

Conversation

@itsmiso-ai

Copy link
Copy Markdown
Contributor

What

Migrated the JWT token blacklist from an in-memory dict to a database-backed BlacklistedToken table, so logouts survive process restarts.

Why

Issue #257: the previous in-memory _TOKEN_BLACKLIST dict was lost on restart, causing previously-logged-out JWTs to b…

Fixes #257

Opened by foreman on review GO (workload wl-misospace-kubetix-257).

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>
@itsmiso-ai
itsmiso-ai requested a review from joryirving as a code owner July 25, 2026 16:14

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the BlacklistedToken table. When called with db=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 optional db session and passes it to is_blacklisted(). Backwards-compatible via db=None default.
  • get_current_user(): Updated to pass its db session to _decode_payload().

kubetix-api/kubetix_api/models/__init__.py

  • BlacklistedToken model: Uses jti as a unique, indexed column for fast lookups. Stores expires_at for TTL checks and auto-cleanup. The id field uses secrets.token_urlsafe(16) as the primary key (21-char URL-safe string), providing ample uniqueness. created_at enables auditability.

kubetix-api/main.py

  • logout() endpoint: Updated to create its own session, pass it to blacklist_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. The autouse=True fixture ensures the test DB is initialized before each test.

Must-Check Items

  • review auth flow for regression: ✅ Verified. The only callers of _decode_payload() and get_current_user() that pass db are FastAPI route dependencies using the standard get_db override (via conftest). Standalone callers like decode_token() use db=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 in logout(). The logout() endpoint correctly extracts jti and exp from the token payload and converts the Unix timestamp to a timezone-aware datetime before persisting.

Standards Compliance

  • AGENTS.md conventions: Followed. New model added to kubetix_api/models/__init__.py, auth logic stays in kubetix_api/auth/__init__.py, integration point is in main.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P3] JWT token blacklist is in-memory only, lost on process restart

1 participant