From 86b89bf0ebb6b0277b1a8f5993509a5b523ff4aa Mon Sep 17 00:00:00 2001 From: The-AarushiSingh <175547726+The-AarushiSingh@users.noreply.github.com> Date: Mon, 31 Aug 2026 01:22:27 +0530 Subject: [PATCH] fix(oauth): reject user-scoped clients on single-workspace hosts - Reject owner: 'user' when deps.subject === 'local' - Keep user-owned OAuth clients working for other subjects - Update mismatch error to avoid 'Workspace' terminology Closes #1850 --- packages/core/sdk/src/oauth-flow.test.ts | 2 +- packages/core/sdk/src/oauth-service.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core/sdk/src/oauth-flow.test.ts b/packages/core/sdk/src/oauth-flow.test.ts index a4f7aff41..58bf745c4 100644 --- a/packages/core/sdk/src/oauth-flow.test.ts +++ b/packages/core/sdk/src/oauth-flow.test.ts @@ -874,7 +874,7 @@ describe("oauth.start / oauth.complete", () => { ); expect(Predicate.isTagged("OAuthStartError")(error)).toBe(true); const startError = error as OAuthStartError; - expect(startError.message).toContain("must use a Workspace app"); + expect(startError.message).toContain("must use an org-owned OAuth client"); }), ), ); diff --git a/packages/core/sdk/src/oauth-service.ts b/packages/core/sdk/src/oauth-service.ts index 5d7b0ef8a..a70c6c035 100644 --- a/packages/core/sdk/src/oauth-service.ts +++ b/packages/core/sdk/src/oauth-service.ts @@ -847,6 +847,13 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { input: CreateOAuthClientInput, ): Effect.Effect => Effect.gen(function* () { + if (input.owner === "user" && deps.subject === "local") { + return yield* new StorageError({ + message: + 'User-owned OAuth clients are not supported on single-workspace hosts. Use owner "org" instead.', + cause: undefined, + }); + } // The `first-party:` namespace is reserved for config-declared apps — a // stored row under it would be shadowed by (or worse, impersonate) the // host's own app. @@ -1423,7 +1430,7 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { }); if (!firstPartyFlow && input.owner === "org" && input.clientOwner === "user") { return yield* new OAuthStartError({ - message: "A Workspace connection must use a Workspace app.", + message: "An org connection must use an org-owned OAuth client.", }); } // Load the app by its EXPLICIT owner (the caller knows it — no derivation).