Skip to content

refactor: unify shared and per-user OAuth refresh under a single RefreshAccessToken(tokenID) path, dropping oauth_configs.token_id FK shortcut - #5710

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken
Aug 8, 2026
Merged

Pratham-Mishra04 merged 1 commit into
devfrom
07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

This PR removes the token_id FK shortcut column from oauth_configs and unifies the two separate OAuth token refresh paths (one keyed by oauth_config_id for shared tokens, one keyed by token ID for per-user tokens) into a single RefreshAccessToken(ctx, tokenID) method. Credential health tracking moves entirely onto the token row's own Status field (active / needs_reauth), replacing the previous pattern of flipping oauth_configs.status to expired or revoked on permanent refresh failures.

Changes

  • Retired TableOauthConfig.TokenID: The FK shortcut that pointed from an oauth config to its single shared-mode token row is dropped. All callers that previously read oauthConfig.TokenID now resolve the token via GetSharedOauthTokenByConfigID(ctx, oauthConfigID), which queries mcp_oauth_tokens on (oauth_config_id, auth_mode='shared').
  • Unified RefreshAccessToken: RefreshUserAccessToken (per-identity, keyed by token ID) and the old RefreshAccessToken (shared, keyed by oauth_config_id) are merged into one method keyed by token ID. Both GetAccessToken and GetUserAccessTokenByMode funnel their lazy pre-flight refresh through this single path.
  • Credential health on the token row: Permanent refresh rejections (HTTP 401, invalid_grant, unauthorized_client) now flip mcp_oauth_tokens.status to needs_reauth via MarkOauthUserTokenNeedsReauthByID, which is no longer scoped away from auth_mode='shared'. The oauth_configs.status column is now a one-time bootstrap lifecycle field only (pending / authorized / failed) and is never written by the refresh path.
  • GetExpiringOauthTokens filter change: The query previously excluded tokens whose owning oauth_config had a terminal status (expired / revoked). It now filters directly on mcp_oauth_tokens.status = 'active', and the join to oauth_configs for the enabled-client check uses oauth_configs.id = mcp_oauth_tokens.oauth_config_id instead of the retired token_id column.
  • TokenRefreshWorker simplification: The worker no longer looks up the owning oauth_config for each expiring token before calling refresh. It calls RefreshAccessToken(ctx, token.ID) directly; permanent-failure handling is entirely inside RefreshAccessToken.
  • GetOauthUserSessionByID flow-mode filter: Added flow_mode IN (perUserOauthFlowModes) to prevent an admin-mode flow row from being reachable through the per-user-facing ID lookup endpoint.
  • DB migration: A new migrationDropOauthConfigTokenIDColumn migration drops oauth_configs.token_id. The existing migrationMergeOauthTokenTables backfill is guarded with a HasColumn check so it skips the token_id-referencing SQL on fresh installs where the column never existed.
  • CompleteOAuthFlow expiry branch: Changed the terminal bootstrap status written on flow expiry from the since-retired "expired" to "failed", consistent with the other bootstrap-failure branch.
  • GetOauthConfigByTokenID removed: No longer needed; replaced by GetSharedOauthTokenByConfigID.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./core/mcp/credstore/... ./framework/configstore/... ./framework/oauth2/... ./transports/bifrost-http/...
  • Verify that a shared OAuth config whose token has been permanently rejected shows status = needs_reauth on the mcp_oauth_tokens row and that oauth_configs.status remains authorized.
  • Verify that the TokenRefreshWorker does not retry a token already marked needs_reauth.
  • Verify that a fresh install (no prior token_id column) runs all migrations without error.
  • Verify that an upgrade from a schema that has token_id populated correctly backfills mcp_oauth_tokens.oauth_config_id and then drops the column.

Breaking changes

  • Yes
  • No

GetOauthConfigByTokenID is removed from the ConfigStore interface. Any external implementation of ConfigStore must add GetSharedOauthTokenByConfigID and remove GetOauthConfigByTokenID. The OAuth2Provider interface loses RefreshAccessToken(ctx, oauthConfigID) and RefreshUserAccessToken(ctx, tokenID) and gains a single RefreshAccessToken(ctx, tokenID). The oauth_configs.token_id column is dropped by migration; any raw SQL or tooling that references it will need updating.

Related issues

N/A

Security considerations

GetOauthUserSessionByID now filters by flow_mode IN (perUserOauthFlowModes), preventing an admin-mode flow row from being fetched through a per-user-facing endpoint by ID. MarkOauthUserTokenNeedsReauthByID is intentionally not scoped by auth_mode because the token ID it receives always comes from a trusted internal lookup, never an arbitrary caller-supplied value.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Pratham-Mishra04 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

This was referenced Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Unified OAuth token refresh for shared and per-user credentials.
    • Improved token status tracking, expiration handling, and reauthentication support.
    • OAuth revocation now preserves reusable authorization while removing associated tokens.
    • OAuth authorization flows replace outdated shared tokens cleanly.
  • Bug Fixes

    • Improved handling of legacy OAuth records during migration.
    • Prevented admin-mode flows from appearing as user sessions.
    • Improved filtering of inactive, expired, and disabled credentials.
    • Ensured concurrent refresh requests remain reliable when one request is canceled.

Walkthrough

OAuth credential ownership moves from oauth_configs.token_id to token rows linked by oauth_config_id. Shared and per-user credentials use one token-ID refresh path with token-level reauthentication status.

Changes

Unified OAuth token lifecycle

Layer / File(s) Summary
OAuth token contract and data model
core/schemas/oauth.go, framework/configstore/tables/mcpoauth2.go, core/mcp/credstore/per_user_oauth_test.go
OAuth interfaces and tables remove the configuration token reference and define unified token refresh semantics.
OAuth schema migration
framework/configstore/migrations.go, framework/configstore/migrations_perf_test.go
Migrations backfill shared token fields and conditionally remove the legacy token column.
Configstore token access and filtering
framework/configstore/store.go, framework/configstore/rdb.go, framework/configstore/rdb_oauth2_test.go, framework/configstore/rdb_mcp_sessions_test.go
Shared tokens are resolved by configuration ID. Token and flow filters support active credentials, user-mode flows, and token-level reauthentication.
Unified OAuth refresh flow
framework/oauth2/main.go, framework/oauth2/sync.go, framework/oauth2/sync_test.go
Access, refresh, validation, revocation, completion, and synchronization use token IDs and token-level status.
HTTP shared-token metadata integration
transports/bifrost-http/handlers/mcpoauth2.go, transports/bifrost-http/lib/config_test.go
HTTP OAuth metadata handling and its mock use shared-token lookup by OAuth configuration ID.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OAuth2Service
  participant ConfigStore
  participant OAuthProvider
  OAuth2Service->>ConfigStore: Load token by token ID
  OAuth2Service->>ConfigStore: Load template OAuth config
  OAuth2Service->>OAuthProvider: Refresh access token
  OAuthProvider-->>OAuth2Service: Return refreshed token or error
  OAuth2Service->>ConfigStore: Mark token needs_reauth on permanent rejection
Loading

Possibly related PRs

Suggested reviewers: akshaydeo, bearts, danpiths

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary OAuth refresh unification and removal of the oauth_configs.token_id shortcut.
Description check ✅ Passed The description covers the required sections, design changes, breaking impacts, tests, security considerations, and checklist items.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/configstore/tables/mcpoauth2.go`:
- Around line 22-25: Expand the token lookup documentation near the existing
holder-token comment to specify shared lookups use (oauth_config_id, auth_mode =
"shared") but are not unique, while per-identity lookups additionally require
mcp_client_id and the matching user_id, virtual_key_id, or session_id, plus
status = "active". Document that refresh operations use the token row ID and
FlowMode = "admin" produces AuthMode = "shared".
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 04f5c7fb-454d-4e26-9cc8-f71802e0a681

📥 Commits

Reviewing files that changed from the base of the PR and between 404348f and 53a270a.

📒 Files selected for processing (14)
  • core/mcp/credstore/per_user_oauth_test.go
  • core/schemas/oauth.go
  • framework/configstore/migrations.go
  • framework/configstore/migrations_perf_test.go
  • framework/configstore/rdb.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/configstore/store.go
  • framework/configstore/tables/mcpoauth2.go
  • framework/oauth2/main.go
  • framework/oauth2/sync.go
  • framework/oauth2/sync_test.go
  • transports/bifrost-http/handlers/mcpoauth2.go
  • transports/bifrost-http/lib/config_test.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • transports/bifrost-http/handlers/mcpoauth2.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/migrations_perf_test.go
  • core/mcp/credstore/per_user_oauth_test.go
  • framework/oauth2/sync.go
  • framework/configstore/store.go
  • framework/configstore/rdb_oauth2_test.go
  • transports/bifrost-http/lib/config_test.go
  • framework/oauth2/sync_test.go
  • framework/oauth2/main.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • core/schemas/oauth.go

Comment thread framework/configstore/tables/mcpoauth2.go Outdated
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken branch from 53a270a to c57aa1e Compare August 6, 2026 21:53
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-29-feat_unify_oauth_flow_storage_into_tablemcpoauthflow branch from 404348f to 83be929 Compare August 6, 2026 21:53
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
framework/oauth2/main.go (2)

225-229: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename refreshAccessTokenLocked to match its new synchronization model.

The Locked suffix normally means the caller holds a mutex. This function now runs inside singleflight.Group.DoChan, and no lock is held. Consider refreshAccessTokenOnce or doRefreshAccessToken so future readers do not assume mutual exclusion beyond per-token deduplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/oauth2/main.go` around lines 225 - 229, Rename
OAuth2Provider.refreshAccessTokenLocked to reflect singleflight-based per-token
deduplication rather than mutex protection, using a name such as
refreshAccessTokenOnce or doRefreshAccessToken. Update every declaration,
invocation, and related comment consistently while preserving the existing
DoChan synchronization behavior.

325-333: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Distinguish a store failure from an invalid token.

Line 326 collapses a GetSharedOauthTokenByConfigID error into (false, nil). A transient database failure then reports the credential as invalid, and callers cannot retry or alert. Return the error for the failure case, and keep (false, nil) for the missing-token case.

As per coding guidelines: "explicit error handling and wrapping".

♻️ Proposed change
 	token, err := p.configStore.GetSharedOauthTokenByConfigID(ctx, oauthConfigID)
-	if err != nil || token == nil {
+	if err != nil {
+		return false, fmt.Errorf("failed to load shared oauth token: %w", err)
+	}
+	if token == nil {
 		return false, nil
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/oauth2/main.go` around lines 325 - 333, Update the token lookup
handling in the surrounding OAuth validation method: return a wrapped error when
GetSharedOauthTokenByConfigID fails, while continuing to return (false, nil)
when the token is absent. Preserve the existing status-based validity checks for
present tokens.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@framework/oauth2/main.go`:
- Around line 225-229: Rename OAuth2Provider.refreshAccessTokenLocked to reflect
singleflight-based per-token deduplication rather than mutex protection, using a
name such as refreshAccessTokenOnce or doRefreshAccessToken. Update every
declaration, invocation, and related comment consistently while preserving the
existing DoChan synchronization behavior.
- Around line 325-333: Update the token lookup handling in the surrounding OAuth
validation method: return a wrapped error when GetSharedOauthTokenByConfigID
fails, while continuing to return (false, nil) when the token is absent.
Preserve the existing status-based validity checks for present tokens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d5344fd8-4d89-4784-9f00-3522241f193d

📥 Commits

Reviewing files that changed from the base of the PR and between 83be929 and c57aa1e.

📒 Files selected for processing (14)
  • core/mcp/credstore/per_user_oauth_test.go
  • core/schemas/oauth.go
  • framework/configstore/migrations.go
  • framework/configstore/migrations_perf_test.go
  • framework/configstore/rdb.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/configstore/store.go
  • framework/configstore/tables/mcpoauth2.go
  • framework/oauth2/main.go
  • framework/oauth2/sync.go
  • framework/oauth2/sync_test.go
  • transports/bifrost-http/handlers/mcpoauth2.go
  • transports/bifrost-http/lib/config_test.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • core/schemas/oauth.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/oauth2/sync.go
  • core/mcp/credstore/per_user_oauth_test.go
  • transports/bifrost-http/handlers/mcpoauth2.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/configstore/migrations_perf_test.go
  • framework/configstore/rdb.go
  • framework/oauth2/sync_test.go
  • transports/bifrost-http/lib/config_test.go
  • framework/configstore/store.go
  • framework/configstore/migrations.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 6, 2026
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-29-feat_unify_oauth_flow_storage_into_tablemcpoauthflow branch from 83be929 to f2c9723 Compare August 8, 2026 08:43
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken branch from c57aa1e to c50fb76 Compare August 8, 2026 08:43

Pratham-Mishra04 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Aug 8, 8:47 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 8, 9:06 AM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 8, 9:07 AM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from 07-29-feat_unify_oauth_flow_storage_into_tablemcpoauthflow to graphite-base/5710 August 8, 2026 09:03
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/5710 to dev August 8, 2026 09:05
@Pratham-Mishra04
Pratham-Mishra04 dismissed coderabbitai[bot]’s stale review August 8, 2026 09:05

The base branch was changed.

@Pratham-Mishra04
Pratham-Mishra04 requested a review from a team as a code owner August 8, 2026 09:05
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken branch from c50fb76 to f30b500 Compare August 8, 2026 09:05
@Pratham-Mishra04
Pratham-Mishra04 merged commit 0f10fb5 into dev Aug 8, 2026
14 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 07-29-feat_unify_oauth_token_refresh_into_refreshaccesstoken branch August 8, 2026 09:07
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.

2 participants