Skip to content

Commit 766ae5b

Browse files
authored
feat(backend): Add enterprise connection resource (#8017)
1 parent 3b8066d commit 766ae5b

10 files changed

Lines changed: 229 additions & 1 deletion

File tree

.changeset/slick-berries-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': minor
3+
---
4+
5+
Add `EnterpriseConnection` resource, allowing to create both OIDC and SAML connections
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { ClerkPaginationRequest } from '@clerk/shared/types';
2+
3+
import { joinPaths } from '../../util/path';
4+
import type { EnterpriseConnection } from '../resources';
5+
import type { PaginatedResourceResponse } from '../resources/Deserializer';
6+
import { AbstractAPI } from './AbstractApi';
7+
8+
const basePath = '/enterprise_connections';
9+
10+
type EnterpriseConnectionListParams = ClerkPaginationRequest<{
11+
organizationId?: string;
12+
active?: boolean;
13+
}>;
14+
15+
export interface EnterpriseConnectionOidcParams {
16+
authUrl?: string;
17+
clientId?: string;
18+
clientSecret?: string;
19+
discoveryUrl?: string;
20+
requiresPkce?: boolean;
21+
tokenUrl?: string;
22+
userInfoUrl?: string;
23+
}
24+
25+
export interface EnterpriseConnectionSamlAttributeMappingParams {
26+
userId?: string | null;
27+
emailAddress?: string | null;
28+
firstName?: string | null;
29+
lastName?: string | null;
30+
}
31+
32+
export interface EnterpriseConnectionSamlParams {
33+
allowIdpInitiated?: boolean;
34+
allowSubdomains?: boolean;
35+
attributeMapping?: EnterpriseConnectionSamlAttributeMappingParams;
36+
forceAuthn?: boolean;
37+
idpCertificate?: string;
38+
idpEntityId?: string;
39+
idpMetadata?: string;
40+
idpMetadataUrl?: string;
41+
idpSsoUrl?: string;
42+
}
43+
44+
type CreateEnterpriseConnectionParams = {
45+
name?: string;
46+
domains?: string[];
47+
organizationId?: string;
48+
active?: boolean;
49+
syncUserAttributes?: boolean;
50+
oidc?: EnterpriseConnectionOidcParams;
51+
saml?: EnterpriseConnectionSamlParams;
52+
};
53+
54+
type UpdateEnterpriseConnectionParams = {
55+
name?: string;
56+
domains?: string[];
57+
organizationId?: string;
58+
active?: boolean;
59+
syncUserAttributes?: boolean;
60+
provider?: string;
61+
oidc?: EnterpriseConnectionOidcParams;
62+
saml?: EnterpriseConnectionSamlParams;
63+
};
64+
65+
export class EnterpriseConnectionAPI extends AbstractAPI {
66+
public async createEnterpriseConnection(params: CreateEnterpriseConnectionParams) {
67+
return this.request<EnterpriseConnection>({
68+
method: 'POST',
69+
path: basePath,
70+
bodyParams: params,
71+
});
72+
}
73+
74+
public async updateEnterpriseConnection(enterpriseConnectionId: string, params: UpdateEnterpriseConnectionParams) {
75+
this.requireId(enterpriseConnectionId);
76+
return this.request<EnterpriseConnection>({
77+
method: 'PATCH',
78+
path: joinPaths(basePath, enterpriseConnectionId),
79+
bodyParams: params,
80+
});
81+
}
82+
83+
public async getEnterpriseConnectionList(params: EnterpriseConnectionListParams = {}) {
84+
return this.request<PaginatedResourceResponse<EnterpriseConnection[]>>({
85+
method: 'GET',
86+
path: basePath,
87+
queryParams: params,
88+
});
89+
}
90+
91+
public async getEnterpriseConnection(enterpriseConnectionId: string) {
92+
this.requireId(enterpriseConnectionId);
93+
return this.request<EnterpriseConnection>({
94+
method: 'GET',
95+
path: joinPaths(basePath, enterpriseConnectionId),
96+
});
97+
}
98+
99+
public async deleteEnterpriseConnection(enterpriseConnectionId: string) {
100+
this.requireId(enterpriseConnectionId);
101+
return this.request<EnterpriseConnection>({
102+
method: 'DELETE',
103+
path: joinPaths(basePath, enterpriseConnectionId),
104+
});
105+
}
106+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './BlocklistIdentifierApi';
99
export * from './ClientApi';
1010
export * from './DomainApi';
1111
export * from './EmailAddressApi';
12+
export * from './EnterpriseConnectionApi';
1213
export * from './IdPOAuthAccessTokenApi';
1314
export * from './InstanceApi';
1415
export * from './InvitationApi';

packages/backend/src/api/factory.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ClientAPI,
1010
DomainAPI,
1111
EmailAddressAPI,
12+
EnterpriseConnectionAPI,
1213
IdPOAuthAccessTokenApi,
1314
InstanceAPI,
1415
InvitationAPI,
@@ -67,6 +68,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
6768
clients: new ClientAPI(request),
6869
domains: new DomainAPI(request),
6970
emailAddresses: new EmailAddressAPI(request),
71+
enterpriseConnections: new EnterpriseConnectionAPI(request),
7072
idPOAuthAccessToken: new IdPOAuthAccessTokenApi(
7173
buildRequest({
7274
...options,
@@ -96,13 +98,17 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
9698
phoneNumbers: new PhoneNumberAPI(request),
9799
proxyChecks: new ProxyCheckAPI(request),
98100
redirectUrls: new RedirectUrlAPI(request),
99-
samlConnections: new SamlConnectionAPI(request),
100101
sessions: new SessionAPI(request),
101102
signInTokens: new SignInTokenAPI(request),
102103
signUps: new SignUpAPI(request),
103104
testingTokens: new TestingTokenAPI(request),
104105
users: new UserAPI(request),
105106
waitlistEntries: new WaitlistEntryAPI(request),
106107
webhooks: new WebhookAPI(request),
108+
109+
/**
110+
* @deprecated Use `enterpriseConnections` instead.
111+
*/
112+
samlConnections: new SamlConnectionAPI(request),
107113
};
108114
}

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+
EnterpriseConnection,
1314
IdPOAuthAccessToken,
1415
Instance,
1516
InstanceRestrictions,
@@ -193,6 +194,8 @@ function jsonToObject(item: any): any {
193194
return ProxyCheck.fromJSON(item);
194195
case ObjectType.RedirectUrl:
195196
return RedirectUrl.fromJSON(item);
197+
case ObjectType.EnterpriseConnection:
198+
return EnterpriseConnection.fromJSON(item);
196199
case ObjectType.SamlConnection:
197200
return SamlConnection.fromJSON(item);
198201
case ObjectType.SignInToken:
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { EnterpriseConnectionJSON } from './JSON';
2+
3+
/**
4+
* The Backend `EnterpriseConnection` object holds information about an enterprise connection (SAML or OAuth) for an instance or organization.
5+
*/
6+
export class EnterpriseConnection {
7+
constructor(
8+
/**
9+
* The unique identifier for the connection.
10+
*/
11+
readonly id: string,
12+
/**
13+
* The name to use as a label for the connection.
14+
*/
15+
readonly name: string,
16+
/**
17+
* The domain of the enterprise. Sign-in flows using an email with this domain may use the connection.
18+
*/
19+
readonly domains: Array<string>,
20+
/**
21+
* The Organization ID if the connection is scoped to an organization.
22+
*/
23+
readonly organizationId: string | null,
24+
/**
25+
* Indicates whether the connection is active or not.
26+
*/
27+
readonly active: boolean,
28+
/**
29+
* Indicates whether the connection syncs user attributes between the IdP and Clerk or not.
30+
*/
31+
readonly syncUserAttributes: boolean,
32+
/**
33+
* Indicates whether users with an email address subdomain are allowed to use this connection or not.
34+
*/
35+
readonly allowSubdomains: boolean,
36+
/**
37+
* Indicates whether additional identifications are disabled for this connection.
38+
*/
39+
readonly disableAdditionalIdentifications: boolean,
40+
/**
41+
* The date when the connection was first created.
42+
*/
43+
readonly createdAt: number,
44+
/**
45+
* The date when the connection was last updated.
46+
*/
47+
readonly updatedAt: number,
48+
) {}
49+
50+
static fromJSON(data: EnterpriseConnectionJSON): EnterpriseConnection {
51+
return new EnterpriseConnection(
52+
data.id,
53+
data.name,
54+
data.domains,
55+
data.organization_id,
56+
data.active,
57+
data.sync_user_attributes,
58+
data.allow_subdomains,
59+
data.disable_additional_identifications,
60+
data.created_at,
61+
data.updated_at,
62+
);
63+
}
64+
}

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

Lines changed: 39 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+
EnterpriseConnection: 'enterprise_connection',
3031
EmailAddress: 'email_address',
3132
ExternalAccount: 'external_account',
3233
FacebookAccount: 'facebook_account',
@@ -670,6 +671,44 @@ export interface PaginatedResponseJSON {
670671
total_count?: number;
671672
}
672673

674+
export interface EnterpriseConnectionJSON extends ClerkResourceJSON {
675+
object: typeof ObjectType.EnterpriseConnection;
676+
name: string;
677+
domains: string[];
678+
organization_id: string | null;
679+
active: boolean;
680+
sync_user_attributes: boolean;
681+
allow_subdomains: boolean;
682+
disable_additional_identifications: boolean;
683+
created_at: number;
684+
updated_at: number;
685+
saml_connection?: Pick<
686+
SamlConnectionJSON,
687+
| 'id'
688+
| 'name'
689+
| 'idp_entity_id'
690+
| 'idp_sso_url'
691+
| 'idp_certificate'
692+
| 'idp_metadata_url'
693+
| 'idp_metadata'
694+
| 'acs_url'
695+
| 'sp_entity_id'
696+
| 'sp_metadata_url'
697+
| 'sync_user_attributes'
698+
| 'allow_subdomains'
699+
| 'allow_idp_initiated'
700+
>;
701+
oauth_config?: {
702+
id: string;
703+
name: string;
704+
client_id: string;
705+
discovery_url: string;
706+
logo_public_url: string;
707+
created_at: number;
708+
updated_at: number;
709+
};
710+
}
711+
673712
export interface SamlConnectionJSON extends ClerkResourceJSON {
674713
object: typeof ObjectType.SamlConnection;
675714
name: string;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AttributeMappingJSON, SamlConnectionJSON } from './JSON';
22

33
/**
44
* The Backend `SamlConnection` object holds information about a SAML connection for an organization.
5+
* @deprecated Use `EnterpriseConnection` instead.
56
*/
67
export class SamlConnection {
78
constructor(

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 './EnterpriseConnection';
2930
export * from './ExternalAccount';
3031
export * from './Feature';
3132
export * from './IdentificationLink';

packages/backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export type {
6666
DomainJSON,
6767
EmailJSON,
6868
EmailAddressJSON,
69+
EnterpriseConnectionJSON,
6970
ExternalAccountJSON,
7071
IdentificationLinkJSON,
7172
InstanceJSON,
@@ -121,6 +122,7 @@ export type {
121122
CnameTarget,
122123
Domain,
123124
EmailAddress,
125+
EnterpriseConnection,
124126
ExternalAccount,
125127
Feature,
126128
Instance,

0 commit comments

Comments
 (0)