(
input: OAuthStartInput,
)
| 931 | // start — begin a flow through a client to mint a connection. |
| 932 | // ----------------------------------------------------------------------- |
| 933 | const start = ( |
| 934 | input: OAuthStartInput, |
| 935 | ): Effect.Effect<ConnectResult, OAuthStartError | StorageFailure> => |
| 936 | Effect.gen(function* () { |
| 937 | const keys = yield* Effect.try({ |
| 938 | try: () => deps.ownedKeys(input.owner), |
| 939 | catch: (cause) => |
| 940 | new StorageError({ |
| 941 | message: "Cannot start OAuth flow for owner without a subject", |
| 942 | cause, |
| 943 | }), |
| 944 | }); |
| 945 | // Sharing is one-directional (org → members): a Workspace (org) connection |
| 946 | // cannot be backed by a member's private (user) app. The connection owner |
| 947 | // and the app owner are otherwise independent — a Personal connection |
| 948 | // through a shared Workspace app is the supported cross-owner case. |
| 949 | if (input.owner === "org" && input.clientOwner === "user") { |
| 950 | return yield* new OAuthStartError({ |
| 951 | message: "A Workspace connection must use a Workspace app.", |
| 952 | }); |
| 953 | } |
| 954 | // Load the app by its EXPLICIT owner (the caller knows it — no derivation). |
| 955 | // The connection is still minted under `input.owner`. Storage visibility |
| 956 | // policy hides apps the actor cannot see, so a wrong owner yields null. |
| 957 | const client = yield* loadClient(input.clientOwner, input.client); |
| 958 | if (!client) { |
| 959 | return yield* new OAuthStartError({ |
| 960 | message: `OAuth client not found: ${input.client}`, |
| 961 | }); |
| 962 | } |
| 963 | |
| 964 | // Declared scopes win (driven by the selected auth template). MCP-style |
| 965 | // integrations declare none and discover them from the client's protected |
| 966 | // resource / authorization server metadata at connect. |
| 967 | const scopePolicy = yield* deps |
| 968 | .resolveOAuthScopePolicy(input.integration, input.template) |
| 969 | .pipe( |
| 970 | Effect.mapError( |
| 971 | (cause) => |
| 972 | new OAuthStartError({ |
| 973 | // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: StorageFailure carries a typed `message` field |
| 974 | message: `Failed to resolve OAuth scope policy: ${cause.message}`, |
| 975 | }), |
| 976 | ), |
| 977 | ); |
| 978 | const requestedScopes = |
| 979 | scopePolicy.kind === "discover" |
| 980 | ? yield* discoverScopesForResource(client.resource).pipe( |
| 981 | Effect.mapError( |
| 982 | (cause) => |
| 983 | new OAuthStartError({ |
| 984 | // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: OAuthDiscoveryError carries a typed `message` field |
| 985 | message: `Failed to discover OAuth scopes: ${cause.message}`, |
| 986 | }), |
| 987 | ), |
| 988 | ) |
| 989 | : dedupeScopes(scopePolicy.scopes); |
| 990 |
nothing calls this directly
no test coverage detected