Skip to content

fix(oauth): make the token-status route's canRefresh match the real predicate - #6160

Open
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/oauth-token-status-canrefresh-w3
Open

fix(oauth): make the token-status route's canRefresh match the real predicate#6160
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/oauth-token-status-canrefresh-w3

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Source: bug found while auditing the per-user OAuth flow (lazy-client/virtual-mcp/mcp-oauth) — GET /api/:org/connections/:connectionId/oauth-token/status in apps/api/src/api/routes/downstream-token.ts.

Payoff: the status route computed canRefresh inline as !!token.refreshToken && !!token.tokenEndpoint, but the actual refresh path (oauth/token-refresh.ts's exported canRefresh(), used by getValidDownstreamAccessToken) additionally requires clientId — and refresh-access-token.ts fails immediately with "No client ID available" when it's missing. A token saved with a refresh token + endpoint but no clientId would report canRefresh: true from this endpoint while a real refresh attempt is guaranteed to fail. Any caller of this status endpoint (current or future UI, or an external app integrating per the documented API) gets a wrong signal about whether the connection can self-heal.

Fix: import and reuse the canonical canRefresh() from oauth/token-refresh.ts instead of a second, drifted copy of the same predicate.

Reviewer check: grep -n canRefresh apps/api/src/api/routes/downstream-token.ts apps/api/src/oauth/token-refresh.ts — one definition, one call site.

Verified locally: bun run fmt, cd apps/api && bunx tsc --noEmit (clean), bunx oxlint apps/api/src/api/routes/downstream-token.ts (0 warnings/errors). No local Postgres available to run the route's integration test tier; full CI covers that.


Summary by cubic

Aligns the OAuth token status route with the real refresh predicate so canRefresh no longer returns a false positive when clientId is missing. Previously, the route reported canRefresh: true if refreshToken and tokenEndpoint existed; now it uses the canonical canRefresh(token) that also requires clientId, matching the actual refresh path.

  • Affects GET /api/:org/connections/:connectionId/oauth-token/status only; response shape is unchanged, but tokens without clientId now return canRefresh: false.
  • Replaces the inlined predicate with canRefresh from oauth/token-refresh to keep a single source of truth.

Written for commit 480efe4. Summary will update on new commits.

Review in cubic

…redicate

The /oauth-token/status route hand-rolled canRefresh as
refreshToken && tokenEndpoint, omitting the clientId check that
oauth/token-refresh.ts's canRefresh() (the predicate the actual refresh
path uses) requires. A token saved with a refresh token and endpoint but
no clientId would report canRefresh: true here while a real refresh
attempt fails immediately in refresh-access-token.ts with "No client ID
available" — the status endpoint lies about whether the token is
actually refreshable.

Reuse the canonical canRefresh() instead of a second, drifted copy.
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.

1 participant