(
input: OAuthStartInput,
)
| 1677 | // start — begin a flow through a client to mint a connection. |
| 1678 | // ----------------------------------------------------------------------- |
| 1679 | const start = ( |
| 1680 | input: OAuthStartInput, |
| 1681 | ): Effect.Effect<ConnectResult, OAuthStartError | OrgWriteDeniedError | StorageFailure> => |
| 1682 | Effect.gen(function* () { |
| 1683 | // Gate before any session row or upstream exchange: minting a Workspace |
| 1684 | // connection (including a reconnect that would replace its credential) |
| 1685 | // is a workspace-level change. Personal connections remain member-owned. |
| 1686 | yield* deps.guardOrgWrite(input.owner); |
| 1687 | const keys = yield* Effect.try({ |
| 1688 | try: () => deps.ownedKeys(input.owner), |
| 1689 | catch: (cause) => |
| 1690 | new StorageError({ |
| 1691 | message: "Cannot start OAuth flow for owner without a subject", |
| 1692 | cause, |
| 1693 | }), |
| 1694 | }); |
| 1695 | // The integration must exist BEFORE any session or provider round trip. |
| 1696 | // A stale reference (a connection whose integration was removed, or an |
| 1697 | // agent replaying an old slug) would otherwise complete authorization at |
| 1698 | // the provider and fail only at the mint. |
| 1699 | if (!(yield* deps.integrationExists(input.integration))) { |
| 1700 | return yield* new OAuthStartError({ |
| 1701 | message: `Integration not found: ${String(input.integration)}`, |
| 1702 | }); |
| 1703 | } |
| 1704 | // Sharing is one-directional (org → members): a Workspace (org) connection |
| 1705 | // cannot be backed by a member's private (user) app. The connection owner |
| 1706 | // and the app owner are otherwise independent — a Personal connection |
| 1707 | // through a shared Workspace app is the supported cross-owner case. |
| 1708 | // First-party apps are deployment-owned, outside the owner lattice |
| 1709 | // entirely, so the rule does not apply to them. |
| 1710 | const firstPartyFlow = isFirstPartyOAuthClientSlug(String(input.client)); |
| 1711 | yield* Effect.annotateCurrentSpan({ |
| 1712 | "executor.oauth.client_first_party": firstPartyFlow, |
| 1713 | }); |
| 1714 | if (!firstPartyFlow && input.owner === "org" && input.clientOwner === "user") { |
| 1715 | return yield* new OAuthStartError({ |
| 1716 | message: "A Workspace connection must use a Workspace app.", |
| 1717 | }); |
| 1718 | } |
| 1719 | // Load the app by its EXPLICIT owner (the caller knows it — no derivation). |
| 1720 | // The connection is still minted under `input.owner`. Storage visibility |
| 1721 | // policy hides apps the actor cannot see, so a wrong owner yields null. |
| 1722 | const client = yield* loadClient(input.clientOwner, input.client); |
| 1723 | if (!client) { |
| 1724 | return yield* new OAuthStartError({ |
| 1725 | message: `OAuth client not found: ${input.client}`, |
| 1726 | }); |
| 1727 | } |
| 1728 | |
| 1729 | // Normalize the name the same way the mint stores it, so the free-name |
| 1730 | // guard below compares against the exact stored form. |
| 1731 | const requestedName = connectionIdentifier(String(input.name)); |
| 1732 | // newConnection: resolve the requested name to a FREE one against the |
| 1733 | // stored rows (not a client-side, policy-filtered view), so a second |
| 1734 | // untyped connect mints `personalGmail2` instead of silently re-minting |
| 1735 | // the first account's row. Reconnects omit the flag and keep targeting |
| 1736 | // their existing row. Bounded: a pathological owner with 1000 same-named |
nothing calls this directly
no test coverage detected