Skip to content

feat(oauth): allow overriding providerId and extraParameters in built-in OAuthService providers - #80

Open
smorin wants to merge 3 commits into
raycast:mainfrom
smorin:feat/oauth-provider-options
Open

feat(oauth): allow overriding providerId and extraParameters in built-in OAuthService providers#80
smorin wants to merge 3 commits into
raycast:mainfrom
smorin:feat/oauth-provider-options

Conversation

@smorin

@smorin smorin commented Aug 17, 2026

Copy link
Copy Markdown

Context

I'm building multi-workspace support for the Linear extension — the long-standing request in raycast/extensions#24297 ("Unable to switch between organizations"): people who use Linear across two companies currently have to log out and back in to switch. A companion PR to extensions/linear in the extensions repo is in progress that adds the full feature (per-workspace logins, a Manage Workspaces command, workspace switchers on every command, a multi-workspace menu bar, workspace-routable AI tools).

Before writing any of it, I ran an empirical spike against the real Raycast OAuth proxy and Linear's API to verify what multi-login actually requires. The result: everything works with Raycast's existing OAuth machinery — token storage, the proxy, PKCE — except that OAuthService's built-in providers hardcode exactly two things a second login needs to vary. This PR makes those two things optional parameters. It is deliberately the smallest possible change: two optional fields, no behavior change for any existing caller, no new concepts.

The extension PR doesn't block on this — it currently constructs the service manually (copying the proxy URLs and clientId this library already ships) behind a switch, and flips to OAuthService.linear() once this lands in a release. This PR is the clean path that lets that copied plumbing be deleted.

What

Adds two optional fields to the built-in OAuthService provider options (BaseProviderOptions), threaded through all seven built-in services (asana, github, google, jira, linear, slack, zoom):

  • providerId?: string — passed to the internally-constructed OAuth.PKCEClient; defaults to today's hardcoded value (e.g. "linear"). providerId is purely a local token-storage namespace: it never appears in any request the OAuth flow constructs (verified: authorize URLs are byte-identical across providerIds, modulo the per-request PKCE values), so overriding it simply gives a second, independent login slot for the same provider.
  • extraParameters?: Record<string, string> — merged over the provider's defaults, caller wins (Linear: { actor: "user", ...options.extraParameters }; Slack: { user_scope: options.scope, ...options.extraParameters }; the other providers have no defaults).

Fully backward compatible: with both options omitted, every provider constructs exactly what it constructs today. Tests (covering all seven providers' defaults, overrides, and merge semantics) and docs included.

Why these two fields specifically

A Linear OAuth token is scoped to a single workspace, so "log into a second workspace" needs exactly two things OAuthService.linear() cannot currently express:

  1. A second token-storage slot — i.e. a second providerId. The provider hardcodes providerId: "linear", so a second login overwrites the first.
  2. prompt=consent on the authorize request. Without it, Linear silently skips the consent screen for an already-granted app, so the user can never complete a grant for another workspace. (Verified empirically: Linear's consent page has no in-flow workspace picker — the grant binds to whichever account/workspace is active at linear.app, which the user steers with linear.app's workspace switcher; prompt=consent reliably re-prompts on every run and is what makes a second-workspace grant reachable at all.)

Today, extensions that need either capability re-implement the provider by hand:

  • raycast/extensionsextensions/productlane/src/oauth.ts manually drives the same Linear proxy (https://linear.oauth.raycast.com) and clientId that this library already ships, just to control the client construction.
  • raycast/extensionsextensions/coze/src/services/api.tsx constructs per-workspace OAuthServices with dynamic providerIds.
  • 39 extensions in raycast/extensions construct OAuthService manually (the docs' own manual example: https://developers.raycast.com/utilities/oauth/oauthservice) rather than using a built-in provider.

References: Linear's OAuth documentation (workspace-scoped tokens, actor, prompt): https://linear.app/developers/oauth-2-0-authentication. Prior art in the extensions repo: raycast/extensions#24297 (the request), raycast/extensions#25646 (a PAT-based attempt, closed stale — this approach stays entirely on Raycast's OAuth proxy and token storage instead).

How it will be used

// The existing login keeps the default slot — nothing changes for current users.
const linear = OAuthService.linear({ scope: "read write" });

// Each additional workspace gets its own, independent login slot. Key the slot by
// workspace AND account — the same workspace can be connected under two accounts:
const workspace = OAuthService.linear({
  scope: "read write",
  providerId: `linear-ws-${organizationId}-${userId}`,
  extraParameters: { prompt: "consent" },
});

This generalizes beyond Linear to any provider where users hold several accounts (GitHub work/personal, multiple Slack workspaces, multiple Jira sites).

Open questions

  • Settings rows: Raycast Settings → Extensions shows one "Logged into " row per providerId holding tokens, all with identical generic labels — with N logins a user cannot tell which row logs out which account. Should the row derive a label from the client's providerName/description so extensions can disambiguate? (Out of scope here; noting the observed behavior.)
  • Merge semantics: caller-wins shallow spread over provider defaults (a caller may override actor for Linear). If you'd rather protect provider defaults from overrides, happy to change the spread order.

Copilot AI lite review requested due to automatic review settings August 17, 2026 04:31

Copilot AI 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.

Pull request overview

This PR extends the built-in OAuthService provider factory options to support multi-account / multi-workspace scenarios by allowing callers to (1) override the internal token-storage namespace (providerId) and (2) add/override authorization-request query parameters (extraParameters) across all seven built-in providers.

Changes:

  • Adds providerId?: string and extraParameters?: Record<string, string> to the built-in provider option types and wires them into each provider’s OAuth.PKCEClient construction / authorize request flow.
  • Merges extraParameters over built-in defaults where defaults exist (Linear actor=user, Slack user_scope=...) while preserving current behavior when omitted.
  • Adds unit coverage for providerId/default behavior and extraParameters merging, and updates docs with a multi-workspace Linear example plus updated option tables.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/raycast-api-mock.ts Expands the Raycast API mock to include OAuth.RedirectMethod and a PKCEClient that retains constructor options for assertions.
test/OAuthService.test.ts Adds tests verifying default/overridden providerId and extraParameters behavior across all built-in providers.
src/oauth/types.ts Extends provider option types to include providerId and extraParameters.
src/oauth/OAuthService.ts Threads providerId into the internally constructed OAuth.PKCEClient and passes/merges extraParameters into the authorize request.
docs/utils-reference/oauth/OAuthService.md Documents multi-login usage and adds providerId/extraParameters to provider option tables.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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