From 4d47edaf87394199a98e745a20d79b22cd5d867a Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 28 Sep 2026 23:59:23 +0000 Subject: [PATCH] feat(pipes): expose connected account provenance Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- docs/PIPES_COMPATIBILITY.md | 10 ++++++++++ src/pipes/fixtures/connected-account.json | 2 ++ ...ations-list-response-data-connected-account.json | 2 ++ .../data-integrations-list-response-data.json | 4 ++++ .../fixtures/data-integrations-list-response.json | 4 ++++ src/pipes/interfaces/connected-account.interface.ts | 13 ++++++++++++- ...ist-response-data-connected-account.interface.ts | 13 ++++++++++++- .../serializers/connected-account.serializer.ts | 2 ++ ...st-response-data-connected-account.serializer.ts | 2 ++ 9 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/PIPES_COMPATIBILITY.md b/docs/PIPES_COMPATIBILITY.md index 32478a5f6..cc47190e1 100644 --- a/docs/PIPES_COMPATIBILITY.md +++ b/docs/PIPES_COMPATIBILITY.md @@ -58,6 +58,16 @@ use `connectionOwner`, which defaults to `user`. Organization-owned requests nee an `organizationId`; the supplied `userId` identifies the acting member rather than making that member the owner. +## Connection provenance + +`ConnectedAccount` includes `establishedByUserId` and `stateReason`. Both are +optional in the wire type and deserialize to `null` when an older API response +omits them. `establishedByUserId` is `null` for developer-managed connections, +connections established before provenance was recorded, and after the +establishing member's membership ends; in that last case `stateReason` is +`establishing_member_membership_ended`. Keep branching on `state`, and treat a +`stateReason` value the SDK doesn't list as an unknown reason. + ## Multiple connections are opt-in Omitting `supportsMultipleConnections`, or passing `false`, keeps the diff --git a/src/pipes/fixtures/connected-account.json b/src/pipes/fixtures/connected-account.json index 29c49bf1a..ce82dde47 100644 --- a/src/pipes/fixtures/connected-account.json +++ b/src/pipes/fixtures/connected-account.json @@ -15,6 +15,8 @@ "instance_url": "https://example.my.salesforce.com" }, "state": "connected", + "established_by_user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "state_reason": null, "created_at": "2024-01-16T14:20:00.000Z", "updated_at": "2024-01-16T14:20:00.000Z" } diff --git a/src/pipes/fixtures/data-integrations-list-response-data-connected-account.json b/src/pipes/fixtures/data-integrations-list-response-data-connected-account.json index 1103670fe..e77f25aff 100644 --- a/src/pipes/fixtures/data-integrations-list-response-data-connected-account.json +++ b/src/pipes/fixtures/data-integrations-list-response-data-connected-account.json @@ -15,6 +15,8 @@ "instance_url": "https://example.my.salesforce.com" }, "state": "connected", + "established_by_user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "state_reason": null, "created_at": "test_createdAt", "updated_at": "test_updatedAt", "userland_user_id": "test_userlandUserId" diff --git a/src/pipes/fixtures/data-integrations-list-response-data.json b/src/pipes/fixtures/data-integrations-list-response-data.json index c3f83c315..1a152d6ca 100644 --- a/src/pipes/fixtures/data-integrations-list-response-data.json +++ b/src/pipes/fixtures/data-integrations-list-response-data.json @@ -29,6 +29,8 @@ "instance_url": "https://example.my.salesforce.com" }, "state": "connected", + "established_by_user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "state_reason": null, "created_at": "test_createdAt", "updated_at": "test_updatedAt", "userland_user_id": "test_userlandUserId" @@ -51,6 +53,8 @@ "instance_url": "https://example.my.salesforce.com" }, "state": "connected", + "established_by_user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "state_reason": null, "created_at": "test_createdAt", "updated_at": "test_updatedAt", "userland_user_id": "test_userlandUserId" diff --git a/src/pipes/fixtures/data-integrations-list-response.json b/src/pipes/fixtures/data-integrations-list-response.json index c69556a35..4d4cdef3c 100644 --- a/src/pipes/fixtures/data-integrations-list-response.json +++ b/src/pipes/fixtures/data-integrations-list-response.json @@ -32,6 +32,8 @@ "instance_url": "https://example.my.salesforce.com" }, "state": "connected", + "established_by_user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "state_reason": null, "created_at": "test_createdAt", "updated_at": "test_updatedAt", "userland_user_id": "test_userlandUserId" @@ -54,6 +56,8 @@ "instance_url": "https://example.my.salesforce.com" }, "state": "connected", + "established_by_user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "state_reason": null, "created_at": "test_createdAt", "updated_at": "test_updatedAt", "userland_user_id": "test_userlandUserId" diff --git a/src/pipes/interfaces/connected-account.interface.ts b/src/pipes/interfaces/connected-account.interface.ts index 992b87629..d3c64f1f0 100644 --- a/src/pipes/interfaces/connected-account.interface.ts +++ b/src/pipes/interfaces/connected-account.interface.ts @@ -34,10 +34,19 @@ export interface ConnectedAccount { /** * The state of the connected account: * - `connected`: The connection is active and tokens are valid. - * - `needs_reauthorization`: The user needs to reauthorize the connection, typically because required scopes have changed. + * - `needs_reauthorization`: The user needs to reauthorize the connection, typically because required scopes have changed. `state_reason` explains why when WorkOS invalidated the connection itself. * - `disconnected`: The connection has been disconnected. */ state: ConnectedAccountState; + /** The [User](https://workos.com/docs/reference/authkit/user) whose authorization established this connection. `null` when the connection is developer-managed or its establisher is unknown, and when the establishing member lost access to the organization — in which case `state_reason` is `establishing_member_membership_ended`. */ + establishedByUserId?: string | null; + /** + * Why WorkOS put the connection in its current `state`, or `null` when there is no WorkOS-recorded reason: + * - `establishing_member_membership_ended`: The member who established this organization-owned connection is no longer an active member of the organization, so its credentials were cleared. An organization admin must provide new credentials. + * + * Additional values may be added; treat an unrecognized value as an unknown reason. + */ + stateReason?: 'establishing_member_membership_ended' | null; /** The timestamp when the connection was created. */ createdAt: string; /** The timestamp when the connection was last updated. */ @@ -59,6 +68,8 @@ export interface ConnectedAccountResponse { client_secret_last_4?: string | null; config?: Record; state: ConnectedAccountState; + established_by_user_id?: string | null; + state_reason?: 'establishing_member_membership_ended' | null; created_at: string; updated_at: string; } diff --git a/src/pipes/interfaces/data-integrations-list-response-data-connected-account.interface.ts b/src/pipes/interfaces/data-integrations-list-response-data-connected-account.interface.ts index 5cb37b258..007261f81 100644 --- a/src/pipes/interfaces/data-integrations-list-response-data-connected-account.interface.ts +++ b/src/pipes/interfaces/data-integrations-list-response-data-connected-account.interface.ts @@ -34,10 +34,19 @@ export interface DataIntegrationsListResponseDataConnectedAccount { /** * The state of the connected account: * - `connected`: The connection is active and tokens are valid. - * - `needs_reauthorization`: The user needs to reauthorize the connection, typically because required scopes have changed. + * - `needs_reauthorization`: The user needs to reauthorize the connection, typically because required scopes have changed. `state_reason` explains why when WorkOS invalidated the connection itself. * - `disconnected`: The connection has been disconnected. */ state: DataIntegrationsListResponseDataConnectedAccountState; + /** The [User](https://workos.com/docs/reference/authkit/user) whose authorization established this connection. `null` when the connection is developer-managed or its establisher is unknown, and when the establishing member lost access to the organization — in which case `state_reason` is `establishing_member_membership_ended`. */ + establishedByUserId?: string | null; + /** + * Why WorkOS put the connection in its current `state`, or `null` when there is no WorkOS-recorded reason: + * - `establishing_member_membership_ended`: The member who established this organization-owned connection is no longer an active member of the organization, so its credentials were cleared. An organization admin must provide new credentials. + * + * Additional values may be added; treat an unrecognized value as an unknown reason. + */ + stateReason?: 'establishing_member_membership_ended' | null; /** The timestamp when the connection was created. */ createdAt: string; /** The timestamp when the connection was last updated. */ @@ -64,6 +73,8 @@ export interface DataIntegrationsListResponseDataConnectedAccountResponse { client_secret_last_4?: string | null; config?: Record; state: DataIntegrationsListResponseDataConnectedAccountState; + established_by_user_id?: string | null; + state_reason?: 'establishing_member_membership_ended' | null; created_at: string; updated_at: string; userland_user_id: string | null; diff --git a/src/pipes/serializers/connected-account.serializer.ts b/src/pipes/serializers/connected-account.serializer.ts index 489615855..fe21ddc71 100644 --- a/src/pipes/serializers/connected-account.serializer.ts +++ b/src/pipes/serializers/connected-account.serializer.ts @@ -22,6 +22,8 @@ export const deserializeConnectedAccount = ( clientSecretLast4: response.client_secret_last_4 ?? null, config: response.config, state: response.state, + establishedByUserId: response.established_by_user_id ?? null, + stateReason: response.state_reason ?? null, createdAt: response.created_at, updatedAt: response.updated_at, }); diff --git a/src/pipes/serializers/data-integrations-list-response-data-connected-account.serializer.ts b/src/pipes/serializers/data-integrations-list-response-data-connected-account.serializer.ts index 5800677c4..b4e7e7978 100644 --- a/src/pipes/serializers/data-integrations-list-response-data-connected-account.serializer.ts +++ b/src/pipes/serializers/data-integrations-list-response-data-connected-account.serializer.ts @@ -22,6 +22,8 @@ export const deserializeDataIntegrationsListResponseDataConnectedAccount = ( clientSecretLast4: response.client_secret_last_4 ?? null, config: response.config, state: response.state, + establishedByUserId: response.established_by_user_id ?? null, + stateReason: response.state_reason ?? null, createdAt: response.created_at, updatedAt: response.updated_at, userlandUserId: response.userland_user_id ?? null,