From 8097c8af1f660641c1cf2c49e6fec7ee270e1ec6 Mon Sep 17 00:00:00 2001 From: VihaanAgarwal Date: Wed, 24 Jun 2026 12:01:47 -0700 Subject: [PATCH] fix(backend): expose externalAccountId on ExternalAccount resource The backend ExternalAccount mapped the API's `id` field, which holds an `idn_` identification id, and never read `external_account_id` (the `eac_` resource id). Passing `getUser().externalAccounts[].id` to `deleteUserExternalAccount()` then returned a 404 because that method expects the `eac_` id. Map `external_account_id` to a new `externalAccountId` property so the resource id is reachable without a raw API call. `id` keeps its existing value to stay backwards compatible. --- .changeset/external-account-id.md | 5 +++ .../src/api/resources/ExternalAccount.ts | 6 +++ packages/backend/src/api/resources/JSON.ts | 1 + .../__tests__/ExternalAccount.test.ts | 40 +++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 .changeset/external-account-id.md create mode 100644 packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts diff --git a/.changeset/external-account-id.md b/.changeset/external-account-id.md new file mode 100644 index 00000000000..41722a3957c --- /dev/null +++ b/.changeset/external-account-id.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Add `externalAccountId` to the backend `ExternalAccount` resource. This exposes the external account's `eac_`-prefixed id returned by `getUser()`, which is the id `users.deleteUserExternalAccount()` expects. Previously only the `idn_`-prefixed identification id was reachable through `id`, so deleting an external account fetched from `getUser()` failed with a 404. diff --git a/packages/backend/src/api/resources/ExternalAccount.ts b/packages/backend/src/api/resources/ExternalAccount.ts index a623a7f8a51..849ea239098 100644 --- a/packages/backend/src/api/resources/ExternalAccount.ts +++ b/packages/backend/src/api/resources/ExternalAccount.ts @@ -12,6 +12,11 @@ export class ExternalAccount { * The unique identifier for this external account. */ readonly id: string, + /** + * The unique identifier for the external account resource (prefixed with `eac_`). + * This is the value expected by methods such as `users.deleteUserExternalAccount()`. + */ + readonly externalAccountId: string, /** * The provider name (e.g., `google`). */ @@ -74,6 +79,7 @@ export class ExternalAccount { static fromJSON(data: ExternalAccountJSON): ExternalAccount { return new ExternalAccount( data.id, + data.external_account_id, data.provider, data.provider_user_id, data.identification_id, diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 3193401523d..03adce54096 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -231,6 +231,7 @@ export interface EnterpriseAccountJSON extends ClerkResourceJSON { export interface ExternalAccountJSON extends ClerkResourceJSON { object: typeof ObjectType.ExternalAccount; + external_account_id: string; provider: string; identification_id: string; provider_user_id: string; diff --git a/packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts b/packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts new file mode 100644 index 00000000000..35b3002c623 --- /dev/null +++ b/packages/backend/src/api/resources/__tests__/ExternalAccount.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; + +import { ExternalAccount } from '../ExternalAccount'; +import type { ExternalAccountJSON } from '../JSON'; + +describe('ExternalAccount', () => { + describe('fromJSON', () => { + const data: ExternalAccountJSON = { + object: 'external_account', + id: 'idn_2ABXLLckIF5kLikvzAVRxuuN31M', + external_account_id: 'eac_2ABXLObDmeHsnLsLgOd5panvOPJ', + identification_id: 'idn_2ABXLLckIF5kLikvzAVRxuuN31M', + provider: 'oauth_google', + provider_user_id: '1029384756', + approved_scopes: 'email profile', + email_address: 'jane@example.com', + first_name: 'Jane', + last_name: 'Doe', + image_url: 'https://img.clerk.com/jane.png', + username: 'jane', + phone_number: null, + public_metadata: {}, + label: null, + verification: null, + } as ExternalAccountJSON; + + it('maps external_account_id to externalAccountId', () => { + const externalAccount = ExternalAccount.fromJSON(data); + + expect(externalAccount.externalAccountId).toBe('eac_2ABXLObDmeHsnLsLgOd5panvOPJ'); + }); + + it('keeps id and identificationId pointing at the identification id', () => { + const externalAccount = ExternalAccount.fromJSON(data); + + expect(externalAccount.id).toBe('idn_2ABXLLckIF5kLikvzAVRxuuN31M'); + expect(externalAccount.identificationId).toBe('idn_2ABXLLckIF5kLikvzAVRxuuN31M'); + }); + }); +});