diff --git a/.changeset/fair-doodles-change.md b/.changeset/fair-doodles-change.md new file mode 100644 index 00000000000..02471af6464 --- /dev/null +++ b/.changeset/fair-doodles-change.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Introduce `samlConnection` and `oauthConfig` into the `EnterpriseConnection` resource. diff --git a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts index 68ed1dabf8f..40033c3635c 100644 --- a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts +++ b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts @@ -22,6 +22,30 @@ describe('EnterpriseConnectionAPI', () => { sync_user_attributes: false, allow_subdomains: false, disable_additional_identifications: false, + saml_connection: { + id: 'samlc_1', + name: 'Acme SAML', + idp_entity_id: 'https://idp.example.com', + idp_sso_url: 'https://idp.example.com/sso', + idp_certificate: '-----BEGIN CERTIFICATE-----', + idp_metadata_url: 'https://idp.example.com/metadata', + idp_metadata: '', + acs_url: 'https://clerk.example.com/v1/saml/acs', + sp_entity_id: 'https://clerk.example.com', + sp_metadata_url: 'https://clerk.example.com/v1/saml/metadata', + sync_user_attributes: true, + allow_subdomains: true, + allow_idp_initiated: false, + }, + oauth_config: { + id: 'eaoc_1', + name: 'Acme OIDC', + client_id: 'client_abc', + discovery_url: 'https://oauth.example.com/.well-known/openid-configuration', + logo_public_url: 'https://img.example.com/logo.png', + created_at: 1672531200000, + updated_at: 1672531200000, + }, }; describe('createEnterpriseConnection', () => { @@ -178,6 +202,12 @@ describe('EnterpriseConnectionAPI', () => { expect(response.domains).toEqual(['clerk.dev']); expect(response.active).toBe(true); expect(response.organizationId).toBeNull(); + expect(response.samlConnection).not.toBeNull(); + expect(response.samlConnection?.id).toBe('samlc_1'); + expect(response.samlConnection?.idpEntityId).toBe('https://idp.example.com'); + expect(response.oauthConfig).not.toBeNull(); + expect(response.oauthConfig?.clientId).toBe('client_abc'); + expect(response.oauthConfig?.discoveryUrl).toBe('https://oauth.example.com/.well-known/openid-configuration'); }); }); diff --git a/packages/backend/src/api/resources/EnterpriseConnection.ts b/packages/backend/src/api/resources/EnterpriseConnection.ts index 92e1278549b..03bfeb50a82 100644 --- a/packages/backend/src/api/resources/EnterpriseConnection.ts +++ b/packages/backend/src/api/resources/EnterpriseConnection.ts @@ -1,4 +1,131 @@ -import type { EnterpriseConnectionJSON } from './JSON'; +import type { + EnterpriseConnectionJSON, + EnterpriseConnectionOauthConfigJSON, + EnterpriseConnectionSamlConnectionJSON, +} from './JSON'; + +export class EnterpriseConnectionSamlConnection { + constructor( + /** + * The unique identifier for the SAML connection. + */ + readonly id: string, + /** + * The name to use as a label for the connection. + */ + readonly name: string, + /** + * The Entity ID as provided by the Identity Provider (IdP). + */ + readonly idpEntityId: string, + /** + * The Single-Sign On URL as provided by the Identity Provider (IdP). + */ + readonly idpSsoUrl: string, + /** + * The X.509 certificate as provided by the Identity Provider (IdP). + */ + readonly idpCertificate: string, + /** + * The URL which serves the Identity Provider (IdP) metadata. + */ + readonly idpMetadataUrl: string, + /** + * The XML content of the Identity Provider (IdP) metadata file. + */ + readonly idpMetadata: string, + /** + * The Assertion Consumer Service (ACS) URL of the connection. + */ + readonly acsUrl: string, + /** + * The Entity ID as provided by the Service Provider (Clerk). + */ + readonly spEntityId: string, + /** + * The metadata URL as provided by the Service Provider (Clerk). + */ + readonly spMetadataUrl: string, + /** + * Indicates whether the connection syncs user attributes between the IdP and Clerk. + */ + readonly syncUserAttributes: boolean, + /** + * Indicates whether users with an email address subdomain are allowed to use this connection. + */ + readonly allowSubdomains: boolean, + /** + * Indicates whether Identity Provider (IdP) initiated flows are allowed. + */ + readonly allowIdpInitiated: boolean, + ) {} + + static fromJSON(data: EnterpriseConnectionSamlConnectionJSON): EnterpriseConnectionSamlConnection { + return new EnterpriseConnectionSamlConnection( + data.id, + data.name, + data.idp_entity_id, + data.idp_sso_url, + data.idp_certificate, + data.idp_metadata_url, + data.idp_metadata, + data.acs_url, + data.sp_entity_id, + data.sp_metadata_url, + data.sync_user_attributes, + data.allow_subdomains, + data.allow_idp_initiated, + ); + } +} + +/** + * OAuth configuration included on a Backend API {@link EnterpriseConnection} response. + */ +export class EnterpriseConnectionOauthConfig { + constructor( + /** + * The unique identifier for the OAuth configuration. + */ + readonly id: string, + /** + * The name to use as a label for the configuration. + */ + readonly name: string, + /** + * The OAuth client ID. + */ + readonly clientId: string, + /** + * The OpenID Connect discovery URL. + */ + readonly discoveryUrl: string, + /** + * The public URL of the OAuth provider logo, if available. + */ + readonly logoPublicUrl: string, + /** + * The date when the configuration was first created. + */ + readonly createdAt: number, + /** + * The date when the configuration was last updated. + */ + readonly updatedAt: number, + ) {} + + static fromJSON(data: EnterpriseConnectionOauthConfigJSON): EnterpriseConnectionOauthConfig { + return new EnterpriseConnectionOauthConfig( + data.id, + data.name, + data.client_id, + data.discovery_url, + data.logo_public_url, + data.created_at, + data.updated_at, + ); + } +} /** * The Backend `EnterpriseConnection` object holds information about an enterprise connection (SAML or OAuth) for an instance or organization. @@ -45,6 +172,14 @@ export class EnterpriseConnection { * The date when the connection was last updated. */ readonly updatedAt: number, + /** + * SAML connection details when the enterprise connection uses SAML. + */ + readonly samlConnection: EnterpriseConnectionSamlConnection | null, + /** + * OAuth (OIDC) configuration when the enterprise connection uses OAuth. + */ + readonly oauthConfig: EnterpriseConnectionOauthConfig | null, ) {} static fromJSON(data: EnterpriseConnectionJSON): EnterpriseConnection { @@ -59,6 +194,8 @@ export class EnterpriseConnection { data.disable_additional_identifications, data.created_at, data.updated_at, + data.saml_connection != null ? EnterpriseConnectionSamlConnection.fromJSON(data.saml_connection) : null, + data.oauth_config != null ? EnterpriseConnectionOauthConfig.fromJSON(data.oauth_config) : null, ); } } diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 3815e83b8d2..abb67e67019 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -704,6 +704,32 @@ export interface PaginatedResponseJSON { total_count?: number; } +export interface EnterpriseConnectionSamlConnectionJSON { + id: string; + name: string; + idp_entity_id: string; + idp_sso_url: string; + idp_certificate: string; + idp_metadata_url: string; + idp_metadata: string; + acs_url: string; + sp_entity_id: string; + sp_metadata_url: string; + sync_user_attributes: boolean; + allow_subdomains: boolean; + allow_idp_initiated: boolean; +} + +export interface EnterpriseConnectionOauthConfigJSON { + id: string; + name: string; + client_id: string; + discovery_url: string; + logo_public_url: string; + created_at: number; + updated_at: number; +} + export interface EnterpriseConnectionJSON extends ClerkResourceJSON { object: typeof ObjectType.EnterpriseConnection; name: string; @@ -715,31 +741,8 @@ export interface EnterpriseConnectionJSON extends ClerkResourceJSON { disable_additional_identifications: boolean; created_at: number; updated_at: number; - saml_connection?: Pick< - SamlConnectionJSON, - | 'id' - | 'name' - | 'idp_entity_id' - | 'idp_sso_url' - | 'idp_certificate' - | 'idp_metadata_url' - | 'idp_metadata' - | 'acs_url' - | 'sp_entity_id' - | 'sp_metadata_url' - | 'sync_user_attributes' - | 'allow_subdomains' - | 'allow_idp_initiated' - >; - oauth_config?: { - id: string; - name: string; - client_id: string; - discovery_url: string; - logo_public_url: string; - created_at: number; - updated_at: number; - }; + saml_connection?: EnterpriseConnectionSamlConnectionJSON | null; + oauth_config?: EnterpriseConnectionOauthConfigJSON | null; } export interface SamlConnectionJSON extends ClerkResourceJSON { diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index c0e81cd7ecc..a91007ddca1 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -67,6 +67,8 @@ export type { EmailJSON, EmailAddressJSON, EnterpriseConnectionJSON, + EnterpriseConnectionOauthConfigJSON, + EnterpriseConnectionSamlConnectionJSON, ExternalAccountJSON, IdentificationLinkJSON, InstanceJSON, @@ -123,6 +125,8 @@ export type { Domain, EmailAddress, EnterpriseConnection, + EnterpriseConnectionOauthConfig, + EnterpriseConnectionSamlConnection, ExternalAccount, Feature, Instance,