Skip to content

Commit 7c7d025

Browse files
authored
feat(backend): add enterpriseAccounts to User resource (#8181)
1 parent 9ec56ab commit 7c7d025

File tree

8 files changed

+248
-1
lines changed

8 files changed

+248
-1
lines changed

.changeset/wild-badgers-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': patch
3+
---
4+
5+
Add EnterpriseAccount and EnterpriseAccountConnection classes to @clerk/backend, restoring enterprise SSO account data on the User object that was lost when samlAccounts was removed in v3.

packages/backend/src/api/__tests__/factory.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ describe('api.client', () => {
2929
expect(response.emailAddresses[0].emailAddress).toBe('john.doe@clerk.test');
3030
expect(response.phoneNumbers[0].phoneNumber).toBe('+311-555-2368');
3131
expect(response.externalAccounts[0].emailAddress).toBe('john.doe@clerk.test');
32+
expect(response.enterpriseAccounts[0].emailAddress).toBe('john.doe@clerk.test');
33+
expect(response.enterpriseAccounts[0].provider).toBe('saml_okta');
34+
expect(response.enterpriseAccounts[0].enterpriseConnection?.name).toBe('Okta SSO');
3235
expect(response.publicMetadata.zodiac_sign).toBe('leo');
3336
});
3437

packages/backend/src/api/resources/Deserializer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Domain,
1111
Email,
1212
EmailAddress,
13+
EnterpriseAccount,
1314
EnterpriseConnection,
1415
IdPOAuthAccessToken,
1516
Instance,
@@ -154,6 +155,8 @@ function jsonToObject(item: any): any {
154155
return Domain.fromJSON(item);
155156
case ObjectType.EmailAddress:
156157
return EmailAddress.fromJSON(item);
158+
case ObjectType.EnterpriseAccount:
159+
return EnterpriseAccount.fromJSON(item);
157160
case ObjectType.Email:
158161
return Email.fromJSON(item);
159162
case ObjectType.IdpOAuthAccessToken:
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import type { EnterpriseAccountConnectionJSON, EnterpriseAccountJSON } from './JSON';
2+
import { Verification } from './Verification';
3+
4+
/**
5+
* Represents an enterprise SSO connection associated with an enterprise account.
6+
*/
7+
export class EnterpriseAccountConnection {
8+
constructor(
9+
/**
10+
* The unique identifier for this enterprise connection.
11+
*/
12+
readonly id: string,
13+
/**
14+
* Whether the connection is currently active.
15+
*/
16+
readonly active: boolean,
17+
/**
18+
* Whether IdP-initiated SSO is allowed.
19+
*/
20+
readonly allowIdpInitiated: boolean,
21+
/**
22+
* Whether subdomains are allowed for this connection.
23+
*/
24+
readonly allowSubdomains: boolean,
25+
/**
26+
* Whether additional identifications are disabled for users authenticating via this connection.
27+
*/
28+
readonly disableAdditionalIdentifications: boolean,
29+
/**
30+
* The domain associated with this connection.
31+
*/
32+
readonly domain: string,
33+
/**
34+
* The public URL of the connection's logo, if available.
35+
*/
36+
readonly logoPublicUrl: string | null,
37+
/**
38+
* The name of the enterprise connection.
39+
*/
40+
readonly name: string,
41+
/**
42+
* The SSO protocol used (e.g., `saml` or `oauth`).
43+
*/
44+
readonly protocol: string,
45+
/**
46+
* The SSO provider (e.g., `saml_custom`, `saml_okta`).
47+
*/
48+
readonly provider: string,
49+
/**
50+
* Whether user attributes are synced from the IdP.
51+
*/
52+
readonly syncUserAttributes: boolean,
53+
/**
54+
* The date when this connection was created.
55+
*/
56+
readonly createdAt: number,
57+
/**
58+
* The date when this connection was last updated.
59+
*/
60+
readonly updatedAt: number,
61+
) {}
62+
63+
static fromJSON(data: EnterpriseAccountConnectionJSON): EnterpriseAccountConnection {
64+
return new EnterpriseAccountConnection(
65+
data.id,
66+
data.active,
67+
data.allow_idp_initiated,
68+
data.allow_subdomains,
69+
data.disable_additional_identifications,
70+
data.domain,
71+
data.logo_public_url,
72+
data.name,
73+
data.protocol,
74+
data.provider,
75+
data.sync_user_attributes,
76+
data.created_at,
77+
data.updated_at,
78+
);
79+
}
80+
}
81+
82+
/**
83+
* The Backend `EnterpriseAccount` object represents an identification obtained via enterprise SSO (SAML or OIDC).
84+
*/
85+
export class EnterpriseAccount {
86+
constructor(
87+
/**
88+
* The unique identifier for this enterprise account.
89+
*/
90+
readonly id: string,
91+
/**
92+
* Whether this enterprise account is currently active.
93+
*/
94+
readonly active: boolean,
95+
/**
96+
* The email address associated with this enterprise account.
97+
*/
98+
readonly emailAddress: string,
99+
/**
100+
* The enterprise connection through which this account was authenticated.
101+
*/
102+
readonly enterpriseConnection: EnterpriseAccountConnection | null,
103+
/**
104+
* The user's first name as provided by the IdP.
105+
*/
106+
readonly firstName: string | null,
107+
/**
108+
* The user's last name as provided by the IdP.
109+
*/
110+
readonly lastName: string | null,
111+
/**
112+
* The SSO protocol used (e.g., `saml` or `oauth`).
113+
*/
114+
readonly protocol: string,
115+
/**
116+
* The SSO provider (e.g., `saml_custom`, `saml_okta`).
117+
*/
118+
readonly provider: string,
119+
/**
120+
* The unique ID of the user in the provider.
121+
*/
122+
readonly providerUserId: string | null,
123+
/**
124+
* Metadata that can be read from the Frontend API and Backend API and can be set only from the Backend API.
125+
*/
126+
readonly publicMetadata: Record<string, unknown>,
127+
/**
128+
* An object holding information on the verification of this enterprise account.
129+
*/
130+
readonly verification: Verification | null,
131+
/**
132+
* The date when the user last authenticated via this enterprise account.
133+
*/
134+
readonly lastAuthenticatedAt: number | null,
135+
/**
136+
* The ID of the enterprise connection associated with this account.
137+
*/
138+
readonly enterpriseConnectionId: string | null,
139+
) {}
140+
141+
static fromJSON(data: EnterpriseAccountJSON): EnterpriseAccount {
142+
return new EnterpriseAccount(
143+
data.id,
144+
data.active,
145+
data.email_address,
146+
data.enterprise_connection && EnterpriseAccountConnection.fromJSON(data.enterprise_connection),
147+
data.first_name,
148+
data.last_name,
149+
data.protocol,
150+
data.provider,
151+
data.provider_user_id,
152+
data.public_metadata,
153+
data.verification && Verification.fromJSON(data.verification),
154+
data.last_authenticated_at,
155+
data.enterprise_connection_id,
156+
);
157+
}
158+
}

packages/backend/src/api/resources/JSON.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const ObjectType = {
2727
Cookies: 'cookies',
2828
Domain: 'domain',
2929
Email: 'email',
30+
EnterpriseAccount: 'enterprise_account',
3031
EnterpriseConnection: 'enterprise_connection',
3132
EmailAddress: 'email_address',
3233
ExternalAccount: 'external_account',
@@ -194,6 +195,37 @@ export interface EmailAddressJSON extends ClerkResourceJSON {
194195
linked_to: IdentificationLinkJSON[];
195196
}
196197

198+
export interface EnterpriseAccountConnectionJSON extends ClerkResourceJSON {
199+
active: boolean;
200+
allow_idp_initiated: boolean;
201+
allow_subdomains: boolean;
202+
disable_additional_identifications: boolean;
203+
domain: string;
204+
logo_public_url: string | null;
205+
name: string;
206+
protocol: string;
207+
provider: string;
208+
sync_user_attributes: boolean;
209+
created_at: number;
210+
updated_at: number;
211+
}
212+
213+
export interface EnterpriseAccountJSON extends ClerkResourceJSON {
214+
object: typeof ObjectType.EnterpriseAccount;
215+
active: boolean;
216+
email_address: string;
217+
enterprise_connection: EnterpriseAccountConnectionJSON | null;
218+
first_name: string | null;
219+
last_name: string | null;
220+
protocol: string;
221+
provider: string;
222+
provider_user_id: string | null;
223+
public_metadata: Record<string, unknown>;
224+
verification: VerificationJSON | null;
225+
last_authenticated_at: number | null;
226+
enterprise_connection_id: string | null;
227+
}
228+
197229
export interface ExternalAccountJSON extends ClerkResourceJSON {
198230
object: typeof ObjectType.ExternalAccount;
199231
provider: string;
@@ -596,6 +628,7 @@ export interface UserJSON extends ClerkResourceJSON {
596628
web3_wallets: Web3WalletJSON[];
597629
organization_memberships: OrganizationMembershipJSON[] | null;
598630
external_accounts: ExternalAccountJSON[];
631+
enterprise_accounts: EnterpriseAccountJSON[];
599632
password_last_updated_at: number | null;
600633
public_metadata: UserPublicMetadata;
601634
private_metadata: UserPrivateMetadata;

packages/backend/src/api/resources/User.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EmailAddress } from './EmailAddress';
2+
import { EnterpriseAccount } from './EnterpriseAccount';
23
import { ExternalAccount } from './ExternalAccount';
3-
import type { ExternalAccountJSON, UserJSON } from './JSON';
4+
import type { EnterpriseAccountJSON, ExternalAccountJSON, UserJSON } from './JSON';
45
import { PhoneNumber } from './PhoneNumber';
56
import { Web3Wallet } from './Web3Wallet';
67

@@ -119,6 +120,10 @@ export class User {
119120
* An array of all the `ExternalAccount` objects associated with the user via OAuth. **Note**: This includes both verified & unverified external accounts.
120121
*/
121122
readonly externalAccounts: ExternalAccount[] = [],
123+
/**
124+
* An array of all the `EnterpriseAccount` objects associated with the user via enterprise SSO.
125+
*/
126+
readonly enterpriseAccounts: EnterpriseAccount[] = [],
122127
/**
123128
* Date when the user was last active.
124129
*/
@@ -174,6 +179,7 @@ export class User {
174179
(data.phone_numbers || []).map(x => PhoneNumber.fromJSON(x)),
175180
(data.web3_wallets || []).map(x => Web3Wallet.fromJSON(x)),
176181
(data.external_accounts || []).map((x: ExternalAccountJSON) => ExternalAccount.fromJSON(x)),
182+
(data.enterprise_accounts || []).map((x: EnterpriseAccountJSON) => EnterpriseAccount.fromJSON(x)),
177183
data.last_active_at,
178184
data.create_organization_enabled,
179185
data.create_organizations_limit,

packages/backend/src/api/resources/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type { SignUpStatus } from '@clerk/shared/types';
2626
export * from './CommercePlan';
2727
export * from './CommerceSubscription';
2828
export * from './CommerceSubscriptionItem';
29+
export * from './EnterpriseAccount';
2930
export * from './EnterpriseConnection';
3031
export * from './ExternalAccount';
3132
export * from './Feature';

packages/backend/src/fixtures/user.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,44 @@
9191
"updated_at": 1611948436
9292
}
9393
],
94+
"enterprise_accounts": [
95+
{
96+
"object": "enterprise_account",
97+
"id": "ea_okta",
98+
"active": true,
99+
"email_address": "john.doe@clerk.test",
100+
"first_name": "John",
101+
"last_name": "Doe",
102+
"protocol": "saml",
103+
"provider": "saml_okta",
104+
"provider_user_id": "okta_user_123",
105+
"public_metadata": {},
106+
"verification": {
107+
"status": "verified",
108+
"strategy": "saml",
109+
"attempts": null,
110+
"expire_at": 1613831855
111+
},
112+
"last_authenticated_at": 1611948436,
113+
"enterprise_connection_id": "ent_conn_okta",
114+
"enterprise_connection": {
115+
"id": "ent_conn_okta",
116+
"object": "enterprise_account_connection",
117+
"active": true,
118+
"allow_idp_initiated": false,
119+
"allow_subdomains": false,
120+
"disable_additional_identifications": false,
121+
"domain": "clerk.test",
122+
"logo_public_url": null,
123+
"name": "Okta SSO",
124+
"protocol": "saml",
125+
"provider": "saml_okta",
126+
"sync_user_attributes": true,
127+
"created_at": 1611948436,
128+
"updated_at": 1611948436
129+
}
130+
}
131+
],
94132
"saml_accounts": [
95133
{
96134
"object": "saml_account",

0 commit comments

Comments
 (0)