feat(web): JWT session versioning and credential revocation on org removal#1168
feat(web): JWT session versioning and credential revocation on org removal#1168brendan-kellam wants to merge 3 commits intomainfrom
Conversation
…moval Adds a per-user `sessionVersion` integer to the `User` model. The version is baked into every newly-minted JWT cookie via the `jwt` callback, copied onto the session via the `session` callback, and verified on every read by a wrapped `auth()` function that compares the cookie's claim against the current DB value — mismatch returns null, treating the session as logged out on the very next request. Backwards compatible: pre-migration cookies have no claim and fall back to 0, which matches the default User.sessionVersion of 0, so existing sessions keep working until something explicitly bumps the user's version. The `auth()` wrapper is memoized per-request via React `cache()` so the extra DB read happens at most once per request even though `auth()` is called from many places (layout, page, withAuth, getAuthenticatedUser). `removeMemberFromOrg` and `leaveOrg` now run three credential-revocation helpers inside the existing serializable transaction: - `invalidateAllSessionsForUser` — bumps the version, killing every active JWT cookie for the user on their next request. - `revokeUserOAuthTokens` — deletes their `OAuthToken`, `OAuthRefreshToken`, and `OAuthAuthorizationCode` rows. Not org-scoped because OAuthClient has no `orgId`. - `revokeUserApiKeysInOrg` — deletes their `ApiKey` rows scoped to the current org (ApiKey.orgId). Net effect: when an admin removes a member (or a member leaves), the user's JWT cookie, personal API keys for that org, and OAuth tokens all stop working atomically. A failed transaction rolls back all four changes.
This comment has been minimized.
This comment has been minimized.
WalkthroughAdds per-user sessionVersion (DB + Prisma) used in JWTs; NextAuth propagates and validates the claim via a cached auth() that returns null on mismatch. removeMemberFromOrg/leaveOrg now increment sessionVersion and revoke a user’s OAuth tokens and org API keys inside the same Prisma transaction. CHANGELOG and mocks updated. ChangesPer-User JWT Session Versioning & Membership Revocation
Sequence DiagramsequenceDiagram
participant Client
participant NextAuth as NextAuth<br/>(middleware)
participant Auth as auth()<br/>(cache)
participant DB as Database
participant API as Protected<br/>Route
Client->>NextAuth: Request with JWT cookie (sessionVersion)
NextAuth->>Auth: call auth()
Auth->>DB: fetch user by sub
DB-->>Auth: user (sessionVersion = X)
alt X == JWT.sessionVersion
Auth-->>NextAuth: session object
NextAuth->>API: forward request (authorized)
API-->>Client: 200 OK
else mismatch
Auth-->>NextAuth: null
NextAuth-->>Client: 401 Unauthorized
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly Related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CHANGELOG.md`:
- Around line 10-11: The changelog entry currently under "### Added" describing
per-user JWT session versioning is a fix, not a new feature; move the entire
bullet ("Added per-user JWT session versioning so admin-driven member
removals... [`#1168`](https://github.com/sourcebot-dev/sourcebot/pull/1168)") out
of the "### Added" section and append it to the bottom of the "### Fixed"
section, preserving the exact text and PR link and leaving other entries/order
unchanged.
In `@packages/web/src/features/userManagement/actions.ts`:
- Around line 130-161: The new revocation functions revokeUserApiKeysInOrg and
revokeUserOAuthTokens perform unindexed deleteMany queries; add appropriate
indexes to avoid full-table scans by updating the Prisma schema: add a composite
index on ApiKey for (createdById, orgId) and add single-column indexes on
OAuthToken.userId, OAuthRefreshToken.userId, and OAuthAuthorizationCode.userId
(or composite if you prefer specific access patterns), then generate and apply a
migration so the deleteMany calls run against indexed columns within the
transaction.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 21dca91d-f46d-4355-a889-59c628f6d5d5
📒 Files selected for processing (6)
CHANGELOG.mdpackages/db/prisma/migrations/20260501170139_add_user_session_version/migration.sqlpackages/db/prisma/schema.prismapackages/web/src/__mocks__/prisma.tspackages/web/src/auth.tspackages/web/src/features/userManagement/actions.ts
This PR adds a
sessionVersioncolumn to the User tableTest plan
/api/repos, confirm 200.auth()returns null,withAuthrejects, page redirects to/login.ApiKeyrow is gone).sessionVersion.leaveOrg— same cascade behavior when a non-owner leaves voluntarily.leaveOrg— last-owner guard still rejects the action.sessionVersionclaim and fall back to 0).🤖 Generated with Claude Code
Summary by CodeRabbit