Skip to content

fix(oauth): request the scopes the extension and CLI actually need - #1138

Merged
EhabY merged 1 commit into
mainfrom
fix/oauth-request-scopes
Oct 9, 2026
Merged

EhabY merged 1 commit into
mainfrom
fix/oauth-request-scopes

Conversation

@EhabY

@EhabY EhabY commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes VSC-24

Split out of #1128, which holds the live-server scope suite and is stacked on this PR.

Problem

Servers that enforce OAuth scopes (from 2.38) refuse the extension right after sign-in: GET /users/me needs user:read. An audit against a live server (#1128) found more gaps with the same cause:

  • Resolving a workspace by owner/name needs user:read. Without it, remote SSH, ping, speedtest, support bundle, coder start and coder update break.
  • Start builds need user:read (owner lookup), and stop builds need workspace:stop.
  • Workspaces shared by another user need organization_member:read, which only the coder:workspaces.* composites grant.
  • coder start dry-runs a build when a workspace must update first (workspace:create).
  • Inbox notifications need inbox_notification:read (fix: let scoped tokens list and mark inbox notifications read coder#30176).

Fix

The extension requests:

coder:workspaces.operate coder:workspaces.access workspace:create user:read user:read_personal
  • inbox_notification:read is requested only when the server lists it in scopes_supported (IF_SUPPORTED_OAUTH_SCOPES). Servers reject the whole request over an unknown scope, and release/2.38 doesn't offer it yet. Stored sessions don't need it.
  • When the server returns no scope, the extension stores coder:all. Only servers that ignore scopes do that, and they grant everything, so later scope changes don't sign their users out. Only hasRequiredScopes reads it; it is never sent to the server.
  • Logout revokes tokens even when they lack a required scope, since they still work on the server. getStoredTokens no longer checks scopes; getTokensWithRequiredScopes does, and every caller except revokeTokens uses it.

Everyone signed in with OAuth signs in again once, because their stored sessions lack the new scopes. The CHANGELOG says so.

Size

+113/−35 in 9 files:

  • src/oauth: +49/−27.
  • Unit tests: +58/−8.
  • CHANGELOG: +6.

Tests

Unit tests cover:

  • requesting IF_SUPPORTED_OAUTH_SCOPES only when the server supports them
  • accepting stored sessions granted coder:all
  • revoking tokens with outdated scopes
  • storing coder:all when the server returns no scope

#1128 runs every request against a live server.


🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Oct 6, 2026

Copy link
Copy Markdown

VSC-24

Servers that enforce OAuth scopes (from 2.38) refused the extension right
after sign-in: `/users/me` needs `user:read`. Request what the extension
and the CLI it runs need: the `coder:workspaces.operate` and
`coder:workspaces.access` composites, `workspace:create`, `user:read` and
`user:read_personal`.

Request `inbox_notification:read` only when the server lists it in
`scopes_supported`, since servers reject unknown scopes and 2.38 does not
offer it yet.

Store `coder:all` when the server returns no scope, as servers that ignore
scopes grant everything, so later scope changes do not sign their users
out. Sessions stored with the old list sign in again once.

Revoke tokens on logout even when their scopes are outdated, and accept
sessions granted `coder:all`.
@EhabY
EhabY force-pushed the fix/oauth-request-scopes branch from e40f20e to 5b913e9 Compare October 9, 2026 14:49

@code-asher code-asher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tested it out, looks good!

@EhabY
EhabY merged commit 0414193 into main Oct 9, 2026
13 checks passed
@EhabY
EhabY deleted the fix/oauth-request-scopes branch October 9, 2026 20:55
@code-asher

code-asher commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Ah one thing I realized, if the scopes change, we make them log in again, but I think we are not revoking the old token with the old scopes when this happens? Because the code treats it more like the token never existed at all rather than a logout/login. We probably should revoke the old token?

Also isLoggedInWithOauth would return false when the scopes change even though technically they are logged in with oauth, just with old scopes. It might be OK in practice since all that means is we would stop trying to refresh it which is fine since it lacks the scopes we want anyway, but it does seem to make the function name technically a lie. But it might also tie in with the previous paragraph.

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