Skip to content

Commit 76f2d4b

Browse files
committed
wip
1 parent 0bd10a3 commit 76f2d4b

11 files changed

Lines changed: 390 additions & 52 deletions

File tree

packages/clerk-js/src/core/resources/EnterpriseAccount.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export class EnterpriseAccount extends BaseResource implements EnterpriseAccount
6161
return this;
6262
}
6363

64+
destroy = (): Promise<void> => this._baseDelete();
65+
6466
public __internal_toSnapshot(): EnterpriseAccountJSONSnapshot {
6567
return {
6668
object: 'enterprise_account',
@@ -93,6 +95,7 @@ export class EnterpriseAccountConnection extends BaseResource implements Enterpr
9395
protocol!: EnterpriseAccountResource['protocol'];
9496
provider!: EnterpriseAccountResource['provider'];
9597
syncUserAttributes!: boolean;
98+
allowAccountLinking!: boolean;
9699
createdAt!: Date;
97100
updatedAt!: Date;
98101
enterpriseConnectionId: string | null = '';
@@ -114,6 +117,7 @@ export class EnterpriseAccountConnection extends BaseResource implements Enterpr
114117
this.allowSubdomains = data.allow_subdomains;
115118
this.allowIdpInitiated = data.allow_idp_initiated;
116119
this.disableAdditionalIdentifications = data.disable_additional_identifications;
120+
this.allowAccountLinking = data.allow_account_linking;
117121
this.createdAt = unixEpochToDate(data.created_at);
118122
this.updatedAt = unixEpochToDate(data.updated_at);
119123
this.enterpriseConnectionId = data.enterprise_connection_id;
@@ -136,6 +140,7 @@ export class EnterpriseAccountConnection extends BaseResource implements Enterpr
136140
allow_subdomains: this.allowSubdomains,
137141
allow_idp_initiated: this.allowIdpInitiated,
138142
disable_additional_identifications: this.disableAdditionalIdentifications,
143+
allow_account_linking: this.allowAccountLinking,
139144
enterprise_connection_id: this.enterpriseConnectionId,
140145
created_at: this.createdAt.getTime(),
141146
updated_at: this.updatedAt.getTime(),

packages/clerk-js/src/core/resources/User.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import type {
99
DeletedObjectJSON,
1010
DeletedObjectResource,
1111
EmailAddressResource,
12+
EnterpriseAccountConnectionJSON,
13+
EnterpriseAccountConnectionResource,
1214
EnterpriseAccountResource,
1315
ExternalAccountJSON,
1416
ExternalAccountResource,
@@ -42,6 +44,7 @@ import {
4244
DeletedObject,
4345
EmailAddress,
4446
EnterpriseAccount,
47+
EnterpriseAccountConnection,
4548
ExternalAccount,
4649
Image,
4750
OrganizationMembership,
@@ -156,7 +159,7 @@ export class User extends BaseResource implements UserResource {
156159
};
157160

158161
createExternalAccount = async (params: CreateExternalAccountParams): Promise<ExternalAccountResource> => {
159-
const { strategy, redirectUrl, additionalScopes } = params || {};
162+
const { strategy, redirectUrl, additionalScopes, enterpriseConnectionId } = params || {};
160163

161164
const json = (
162165
await BaseResource._fetch<ExternalAccountJSON>({
@@ -166,6 +169,7 @@ export class User extends BaseResource implements UserResource {
166169
strategy,
167170
redirect_url: redirectUrl,
168171
additional_scope: additionalScopes,
172+
enterprise_connection_id: enterpriseConnectionId,
169173
} as any,
170174
})
171175
)?.response as unknown as ExternalAccountJSON;
@@ -289,6 +293,17 @@ export class User extends BaseResource implements UserResource {
289293
return new DeletedObject(json);
290294
};
291295

296+
getEnterpriseConnections = async (): Promise<EnterpriseAccountConnectionResource[]> => {
297+
const json = (
298+
await BaseResource._fetch({
299+
path: '/me/enterprise_connections',
300+
method: 'GET',
301+
})
302+
)?.response as unknown as EnterpriseAccountConnectionJSON[];
303+
304+
return (json || []).map(connection => new EnterpriseAccountConnection(connection));
305+
};
306+
292307
initializePaymentMethod: typeof initializePaymentMethod = params => {
293308
return initializePaymentMethod(params);
294309
};

packages/clerk-js/src/core/resources/__tests__/User.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,88 @@ describe('User', () => {
4242
});
4343
});
4444

45+
it('creates an external account with enterprise connection id', async () => {
46+
const externalAccountJSON = {
47+
object: 'external_account',
48+
provider: 'saml_okta',
49+
verification: {
50+
external_verification_redirect_url: 'https://www.example.com',
51+
},
52+
};
53+
54+
// @ts-ignore
55+
BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: externalAccountJSON }));
56+
57+
const user = new User({
58+
email_addresses: [],
59+
phone_numbers: [],
60+
web3_wallets: [],
61+
external_accounts: [],
62+
} as unknown as UserJSON);
63+
64+
await user.createExternalAccount({
65+
enterpriseConnectionId: 'ec_123',
66+
redirectUrl: 'https://www.example.com',
67+
});
68+
69+
// @ts-ignore
70+
expect(BaseResource._fetch).toHaveBeenCalledWith({
71+
method: 'POST',
72+
path: '/me/external_accounts',
73+
body: {
74+
strategy: undefined,
75+
redirect_url: 'https://www.example.com',
76+
additional_scope: undefined,
77+
enterprise_connection_id: 'ec_123',
78+
},
79+
});
80+
});
81+
82+
it('fetches enterprise connections', async () => {
83+
const enterpriseConnectionsJSON = [
84+
{
85+
id: 'ec_123',
86+
object: 'enterprise_account_connection',
87+
name: 'Acme Corp SSO',
88+
active: true,
89+
allow_account_linking: true,
90+
domain: 'acme.com',
91+
protocol: 'saml',
92+
provider: 'saml_okta',
93+
logo_public_url: null,
94+
sync_user_attributes: true,
95+
allow_subdomains: false,
96+
allow_idp_initiated: false,
97+
disable_additional_identifications: false,
98+
enterprise_connection_id: 'ec_123',
99+
created_at: 1234567890,
100+
updated_at: 1234567890,
101+
},
102+
];
103+
104+
// @ts-ignore
105+
BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: enterpriseConnectionsJSON }));
106+
107+
const user = new User({
108+
email_addresses: [],
109+
phone_numbers: [],
110+
web3_wallets: [],
111+
external_accounts: [],
112+
} as unknown as UserJSON);
113+
114+
const connections = await user.getEnterpriseConnections();
115+
116+
// @ts-ignore
117+
expect(BaseResource._fetch).toHaveBeenCalledWith({
118+
method: 'GET',
119+
path: '/me/enterprise_connections',
120+
});
121+
122+
expect(connections).toHaveLength(1);
123+
expect(connections[0].name).toBe('Acme Corp SSO');
124+
expect(connections[0].allowAccountLinking).toBe(true);
125+
});
126+
45127
it('creates a web3 wallet', async () => {
46128
const targetWeb3Wallet = '0x0000000000000000000000000000000000000000';
47129
const web3WalletJSON = {

packages/localizations/src/en-US.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,15 @@ export const enUS: LocalizationResource = {
11881188
successMessage: 'The provider has been added to your account',
11891189
title: 'Add connected account',
11901190
},
1191+
enterpriseAccountPage: {
1192+
removeResource: {
1193+
messageLine1: '{{identifier}} will be removed from this account.',
1194+
messageLine2:
1195+
'You will no longer be able to use this enterprise account and any dependent features will no longer work.',
1196+
successMessage: '{{enterpriseAccount}} has been removed from your account.',
1197+
title: 'Remove enterprise account',
1198+
},
1199+
},
11911200
deletePage: {
11921201
actionDescription: 'Type "Delete account" below to continue.',
11931202
confirm: 'Delete account',
@@ -1360,6 +1369,8 @@ export const enUS: LocalizationResource = {
13601369
title: 'Email addresses',
13611370
},
13621371
enterpriseAccountsSection: {
1372+
destructiveActionTitle: 'Remove',
1373+
primaryButton: 'Connect account',
13631374
title: 'Enterprise accounts',
13641375
},
13651376
headerTitle__account: 'Profile details',

packages/shared/src/types/enterpriseAccount.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface EnterpriseAccountResource extends ClerkResource {
2121
publicMetadata: Record<string, unknown> | null;
2222
verification: VerificationResource | null;
2323
lastAuthenticatedAt: Date | null;
24+
destroy: () => Promise<void>;
2425
__internal_toSnapshot: () => EnterpriseAccountJSONSnapshot;
2526
}
2627

@@ -35,6 +36,7 @@ export interface EnterpriseAccountConnectionResource extends ClerkResource {
3536
protocol: EnterpriseProtocol;
3637
provider: EnterpriseProvider;
3738
syncUserAttributes: boolean;
39+
allowAccountLinking: boolean;
3840
enterpriseConnectionId: string | null;
3941
__internal_toSnapshot: () => EnterpriseAccountConnectionJSONSnapshot;
4042
}

packages/shared/src/types/json.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ export interface EnterpriseAccountConnectionJSON extends ClerkResourceJSON {
272272
protocol: EnterpriseProtocol;
273273
provider: EnterpriseProvider;
274274
sync_user_attributes: boolean;
275+
allow_account_linking: boolean;
275276
created_at: number;
276277
updated_at: number;
277278
enterprise_connection_id: string | null;

packages/shared/src/types/localization.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ export type __internal_LocalizationResource = {
683683
};
684684
enterpriseAccountsSection: {
685685
title: LocalizationValue;
686+
primaryButton: LocalizationValue;
687+
destructiveActionTitle: LocalizationValue;
686688
};
687689
passwordSection: {
688690
title: LocalizationValue;
@@ -819,6 +821,14 @@ export type __internal_LocalizationResource = {
819821
successMessage: LocalizationValue<'connectedAccount'>;
820822
};
821823
};
824+
enterpriseAccountPage: {
825+
removeResource: {
826+
title: LocalizationValue;
827+
messageLine1: LocalizationValue<'identifier'>;
828+
messageLine2: LocalizationValue;
829+
successMessage: LocalizationValue<'enterpriseAccount'>;
830+
};
831+
};
822832
web3WalletPage: {
823833
title: LocalizationValue;
824834
subtitle__availableWallets: LocalizationValue;

packages/shared/src/types/user.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { BackupCodeResource } from './backupCode';
22
import type { BillingPayerMethods } from './billing';
33
import type { DeletedObjectResource } from './deletedObject';
44
import type { EmailAddressResource } from './emailAddress';
5-
import type { EnterpriseAccountResource } from './enterpriseAccount';
5+
import type { EnterpriseAccountConnectionResource, EnterpriseAccountResource } from './enterpriseAccount';
66
import type { ExternalAccountResource } from './externalAccount';
77
import type { ImageResource } from './image';
88
import type { UserJSON } from './json';
@@ -118,6 +118,7 @@ export interface UserResource extends ClerkResource, BillingPayerMethods {
118118
) => Promise<ClerkPaginatedResponse<OrganizationSuggestionResource>>;
119119
getOrganizationCreationDefaults: () => Promise<OrganizationCreationDefaultsResource>;
120120
leaveOrganization: (organizationId: string) => Promise<DeletedObjectResource>;
121+
getEnterpriseConnections: () => Promise<EnterpriseAccountConnectionResource[]>;
121122
createTOTP: () => Promise<TOTPResource>;
122123
verifyTOTP: (params: VerifyTOTPParams) => Promise<TOTPResource>;
123124
disableTOTP: () => Promise<DeletedObjectResource>;
@@ -141,7 +142,8 @@ export type CreatePhoneNumberParams = { phoneNumber: string };
141142
export type CreateWeb3WalletParams = { web3Wallet: string };
142143
export type SetProfileImageParams = { file: Blob | File | string | null };
143144
export type CreateExternalAccountParams = {
144-
strategy: OAuthStrategy;
145+
strategy?: OAuthStrategy;
146+
enterpriseConnectionId?: string;
145147
redirectUrl?: string;
146148
additionalScopes?: OAuthScope[];
147149
oidcPrompt?: string;

packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const ConnectMenuButton = (props: { strategy: OAuthStrategy; onClick?: () => voi
4646
card.setLoading(strategy);
4747
return createExternalAccount()
4848
.then(res => {
49-
if (res && res.verification?.externalVerificationRedirectURL) {
49+
if (res?.verification?.externalVerificationRedirectURL) {
5050
void sleep(2000).then(() => card.setIdle(strategy));
5151
void navigate(res.verification.externalVerificationRedirectURL.href);
5252
}

0 commit comments

Comments
 (0)