feat(oauth): allow overriding providerId and extraParameters in built-in OAuthService providers - #80
feat(oauth): allow overriding providerId and extraParameters in built-in OAuthService providers#80smorin wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
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?: stringandextraParameters?: Record<string, string>to the built-in provider option types and wires them into each provider’sOAuth.PKCEClientconstruction / authorize request flow. - Merges
extraParametersover built-in defaults where defaults exist (Linearactor=user, Slackuser_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.
|
@0xdhrv can you check this one out too since it's relatives to the extension PR? |
Sure, taking a look today |
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/linearin 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
OAuthServiceprovider options (BaseProviderOptions), threaded through all seven built-in services (asana,github,google,jira,linear,slack,zoom):providerId?: string— passed to the internally-constructedOAuth.PKCEClient; defaults to today's hardcoded value (e.g."linear").providerIdis 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:providerId. The provider hardcodesproviderId: "linear", so a second login overwrites the first.prompt=consenton 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=consentreliably 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/extensions→extensions/productlane/src/oauth.tsmanually drives the same Linear proxy (https://linear.oauth.raycast.com) and clientId that this library already ships, just to control the client construction.raycast/extensions→extensions/coze/src/services/api.tsxconstructs per-workspaceOAuthServices with dynamic providerIds.raycast/extensionsconstructOAuthServicemanually (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
This generalizes beyond Linear to any provider where users hold several accounts (GitHub work/personal, multiple Slack workspaces, multiple Jira sites).
Open questions
providerName/descriptionso extensions can disambiguate? (Out of scope here; noting the observed behavior.)actorfor Linear). If you'd rather protect provider defaults from overrides, happy to change the spread order.