-
Notifications
You must be signed in to change notification settings - Fork 461
fix(backend): Align EnterpriseConnection resource with the Backend API response #9156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1fd49b8
2092c3a
c6348b1
7b6b5c0
1508711
9d3a99e
f55c1e5
f2a4865
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| '@clerk/backend': minor | ||
| --- | ||
|
|
||
| Align the `EnterpriseConnection` response resource with what the Backend API actually returns: | ||
|
|
||
| - `EnterpriseConnection` now exposes `provider`, `logoPublicUrl`, `allowOrganizationAccountLinking`, `authenticatable`, `disableJitProvisioning`, and `customAttributes`. | ||
| - `EnterpriseConnectionSamlConnection` now exposes `active`, `forceAuthn`, and `loginHint`. | ||
| - `EnterpriseConnectionOauthConfig` now exposes `providerKey`, `authUrl`, `tokenUrl`, `userInfoUrl`, and `requiresPkce`. | ||
| - Deprecated properties the Backend API never returns, which were always `undefined` despite their declared types: `allowSubdomains` on `EnterpriseConnection` (use `samlConnection.allowSubdomains`), and `idpMetadata` and `syncUserAttributes` on `EnterpriseConnectionSamlConnection` (use the top-level `syncUserAttributes`). | ||
| - `organizationId` is now normalized to `null` when the Backend API omits it, matching its declared `string | null` type. Properties backed by optional API fields (for example `oauthConfig.clientId` and the SAML IdP fields) are now typed as possibly `undefined` to match runtime behavior. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,55 @@ | ||
| import type { | ||
| EnterpriseConnectionCustomAttributeJSON, | ||
| EnterpriseConnectionJSON, | ||
| EnterpriseConnectionOauthConfigJSON, | ||
| EnterpriseConnectionSamlConnectionJSON, | ||
| EnterpriseConnectionSamlConnectionLoginHintJSON, | ||
| } from './JSON'; | ||
|
|
||
| /** | ||
| * The `login_hint` configuration included on a Backend API {@link EnterpriseConnectionSamlConnection} response. | ||
| */ | ||
| export class EnterpriseConnectionSamlConnectionLoginHint { | ||
| constructor( | ||
| /** How the SAML connection emits the `login_hint` sent to the IdP: `'email_address'` sends the typed identifier, `'custom_attribute'` sends the value stored at the user `publicMetadata` key named by `source`, and `'off'` omits the `login_hint`. */ | ||
| readonly mode: 'email_address' | 'custom_attribute' | 'off', | ||
| /** The user `publicMetadata` key the `login_hint` value is read from. Only set when `mode` is `'custom_attribute'`. */ | ||
| readonly source?: string, | ||
| ) {} | ||
|
|
||
| static fromJSON(data: EnterpriseConnectionSamlConnectionLoginHintJSON): EnterpriseConnectionSamlConnectionLoginHint { | ||
| return new EnterpriseConnectionSamlConnectionLoginHint(data.mode, data.source); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A custom attribute mapping included on a Backend API {@link EnterpriseConnection} response. | ||
| */ | ||
| export class EnterpriseConnectionCustomAttribute { | ||
| constructor( | ||
| /** The display name of the custom attribute. */ | ||
| readonly name: string, | ||
| /** The key the custom attribute is stored under. */ | ||
| readonly key: string, | ||
| /** The SSO (SAML or OIDC) attribute path the value is read from. */ | ||
| readonly ssoPath: string, | ||
| /** The SCIM attribute path the value is read from. */ | ||
| readonly scimPath: string, | ||
| /** Whether the custom attribute holds multiple values. */ | ||
| readonly multiValued: boolean, | ||
| ) {} | ||
|
|
||
| static fromJSON(data: EnterpriseConnectionCustomAttributeJSON): EnterpriseConnectionCustomAttribute { | ||
| return new EnterpriseConnectionCustomAttribute( | ||
| data.name, | ||
| data.key, | ||
| data.sso_path, | ||
| data.scim_path, | ||
| data.multi_valued, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * The Backend `EnterpriseConnectionSamlConnection` object holds information about a SAML enterprise connection for an instance or organization. | ||
| */ | ||
|
|
@@ -14,31 +60,43 @@ export class EnterpriseConnectionSamlConnection { | |
| /** 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, | ||
| readonly idpEntityId: string | undefined, | ||
| /** The Single-Sign On URL as provided by the Identity Provider (IdP). */ | ||
| readonly idpSsoUrl: string, | ||
| readonly idpSsoUrl: string | undefined, | ||
| /** The X.509 certificate as provided by the Identity Provider (IdP). */ | ||
| readonly idpCertificate: string, | ||
| readonly idpCertificate: string | undefined, | ||
| /** The Unix timestamp when the Identity Provider (IdP) certificate was issued. */ | ||
| readonly idpCertificateIssuedAt: number, | ||
| readonly idpCertificateIssuedAt: number | undefined, | ||
| /** The Unix timestamp when the Identity Provider (IdP) certificate expires. */ | ||
| readonly idpCertificateExpiresAt: number, | ||
| readonly idpCertificateExpiresAt: number | undefined, | ||
| /** 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, | ||
| readonly idpMetadataUrl: string | undefined, | ||
| /** | ||
| * The XML content of the Identity Provider (IdP) metadata file. | ||
| * @deprecated The Backend API does not return this field, so it is always `undefined`. | ||
| */ | ||
| readonly idpMetadata: string | undefined, | ||
| /** The Assertion Consumer Service (ACS) URL of the connection. */ | ||
| readonly acsUrl: string, | ||
| readonly acsUrl: string | undefined, | ||
| /** The Entity ID as provided by the Service Provider (Clerk). */ | ||
| readonly spEntityId: string, | ||
| readonly spEntityId: string | undefined, | ||
| /** The metadata URL as provided by the Service Provider (Clerk). */ | ||
| readonly spMetadataUrl: string, | ||
| /** Whether the connection syncs user attributes between the IdP and Clerk. */ | ||
| readonly syncUserAttributes: boolean, | ||
| readonly spMetadataUrl: string | undefined, | ||
| /** | ||
| * Whether the connection syncs user attributes between the IdP and Clerk. | ||
| * @deprecated The Backend API does not return this field on the nested SAML connection, so it is always `undefined`. Use the top-level `syncUserAttributes` on {@link EnterpriseConnection} instead. | ||
| */ | ||
| readonly syncUserAttributes: boolean | undefined, | ||
| /** Whether users with an email address subdomain are allowed to use this connection. */ | ||
| readonly allowSubdomains: boolean, | ||
| /** Whether IdP-initiated SSO is allowed. */ | ||
| readonly allowIdpInitiated: boolean, | ||
| /** Whether the SAML connection is active. */ | ||
| readonly active: boolean, | ||
| /** Whether the SAML connection requires force authentication. */ | ||
| readonly forceAuthn: boolean, | ||
| /** The `login_hint` configuration of the SAML connection. */ | ||
| readonly loginHint: EnterpriseConnectionSamlConnectionLoginHint, | ||
| ) {} | ||
|
|
||
| static fromJSON(data: EnterpriseConnectionSamlConnectionJSON): EnterpriseConnectionSamlConnection { | ||
|
|
@@ -58,6 +116,9 @@ export class EnterpriseConnectionSamlConnection { | |
| data.sync_user_attributes, | ||
| data.allow_subdomains, | ||
| data.allow_idp_initiated, | ||
| data.active, | ||
| data.force_authn, | ||
| EnterpriseConnectionSamlConnectionLoginHint.fromJSON(data.login_hint), | ||
| ); | ||
| } | ||
| } | ||
|
|
@@ -78,15 +139,15 @@ export class EnterpriseConnectionOauthConfig { | |
| /** | ||
| * The OAuth client ID. | ||
| */ | ||
| readonly clientId: string, | ||
| readonly clientId: string | undefined, | ||
| /** | ||
| * The OpenID Connect discovery URL. | ||
| */ | ||
| readonly discoveryUrl: string, | ||
| readonly discoveryUrl: string | undefined, | ||
| /** | ||
| * The public URL of the OAuth provider logo, if available. | ||
| */ | ||
| readonly logoPublicUrl: string, | ||
| readonly logoPublicUrl: string | undefined, | ||
| /** | ||
| * The date when the configuration was first created. | ||
| */ | ||
|
|
@@ -95,6 +156,26 @@ export class EnterpriseConnectionOauthConfig { | |
| * The date when the configuration was last updated. | ||
| */ | ||
| readonly updatedAt: number, | ||
| /** | ||
| * The OAuth provider key of the configuration. For example, `'custom_oidc'`. | ||
| */ | ||
| readonly providerKey: string, | ||
| /** | ||
| * The OAuth authorization URL. | ||
| */ | ||
| readonly authUrl: string | undefined, | ||
| /** | ||
| * The OAuth token URL. | ||
| */ | ||
| readonly tokenUrl: string | undefined, | ||
| /** | ||
| * The OAuth user info URL. | ||
| */ | ||
| readonly userInfoUrl: string | undefined, | ||
| /** | ||
| * Whether the OAuth configuration requires PKCE. | ||
| */ | ||
| readonly requiresPkce: boolean, | ||
| ) {} | ||
|
|
||
| static fromJSON(data: EnterpriseConnectionOauthConfigJSON): EnterpriseConnectionOauthConfig { | ||
|
|
@@ -106,6 +187,11 @@ export class EnterpriseConnectionOauthConfig { | |
| data.logo_public_url, | ||
| data.created_at, | ||
| data.updated_at, | ||
| data.provider_key, | ||
| data.auth_url, | ||
| data.token_url, | ||
| data.user_info_url, | ||
| data.requires_pkce, | ||
| ); | ||
| } | ||
| } | ||
|
|
@@ -141,8 +227,9 @@ export class EnterpriseConnection { | |
| readonly syncUserAttributes: boolean, | ||
| /** | ||
| * Whether users with an email address subdomain are allowed to use this connection or not. | ||
| * @deprecated The Backend API does not return this field at the top level, so it is always `undefined`. Use `samlConnection.allowSubdomains` instead. | ||
| */ | ||
| readonly allowSubdomains: boolean, | ||
| readonly allowSubdomains: boolean | undefined, | ||
| /** | ||
| * Whether additional identifications are disabled for this connection. | ||
| */ | ||
|
|
@@ -163,14 +250,38 @@ export class EnterpriseConnection { | |
| * OAuth (OIDC) configuration when the enterprise connection uses OAuth. | ||
| */ | ||
| readonly oauthConfig: EnterpriseConnectionOauthConfig | null, | ||
| /** | ||
| * The identity provider (IdP) of the connection. For example, `'saml_custom'` or `'oidc_custom'`. | ||
| */ | ||
| readonly provider: string, | ||
| /** | ||
| * The public URL of the provider logo, if available. | ||
| */ | ||
| readonly logoPublicUrl: string | undefined, | ||
| /** | ||
| * Whether existing users who are members of the organization can link their account to their enterprise identity. | ||
| */ | ||
| readonly allowOrganizationAccountLinking: boolean, | ||
| /** | ||
| * Whether the connection can be used for sign-in and sign-up. | ||
| */ | ||
| readonly authenticatable: boolean, | ||
| /** | ||
| * Whether Just-in-Time (JIT) provisioning of users is disabled for the connection. | ||
| */ | ||
| readonly disableJitProvisioning: boolean, | ||
| /** | ||
| * The custom attribute mappings of the connection. Only returned when the custom attributes feature is enabled for the instance. | ||
| */ | ||
| readonly customAttributes: EnterpriseConnectionCustomAttribute[] | undefined, | ||
|
Comment on lines
+253
to
+276
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 '"version"|"exports"' packages/backend/package.json
ast-grep outline packages/backend/src/api/resources/EnterpriseConnection.ts --items all --type class
rg -n -C 3 '\bEnterpriseConnection(SamlConnection|OauthConfig)?\b' packages/backend/src/index.tsRepository: clerk/javascript Length of output: 1546 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n## Constructor definitions\n'
sed -n '1,340p' packages/backend/src/api/resources/EnterpriseConnection.ts | nl -ba | sed -n '1,340p'
printf '\n## Direct usages in repo\n'
rg -n 'new\s+EnterpriseConnection(SamlConnectionLoginHint|CustomAttribute|SamlConnection|OauthConfig|)\b' packages/backend/src || true
printf '\n## Package entry exports around backend index\n'
sed -n '120,190p' packages/backend/src/index.ts | nl -ba | sed -n '1,120p'
printf '\n## Changeset\n'
sed -n '1,80p' .changeset/major-poets-refuse.md | nl -ba | sed -n '1,80p'Repository: clerk/javascript Length of output: 222 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n## EnterpriseConnection.ts constructors\n'
grep -nE '^export class (EnterpriseConnectionSamlConnectionLoginHint|EnterpriseConnectionCustomAttribute|EnterpriseConnectionSamlConnection|EnterpriseConnectionOauthConfig|EnterpriseConnection)\b|^ constructor\(' packages/backend/src/api/resources/EnterpriseConnection.ts
printf '\n## Relevant source around constructors\n'
sed -n '1,340p' packages/backend/src/api/resources/EnterpriseConnection.ts | cat -n | sed -n '1,340p'
printf '\n## Public exports from backend entry\n'
grep -nE 'EnterpriseConnection(SamlConnectionLoginHint|CustomAttribute|SamlConnection|OauthConfig)?' packages/backend/src/index.ts
printf '\n## Direct constructor usages in repo\n'
rg -n 'new\s+EnterpriseConnection(SamlConnectionLoginHint|CustomAttribute|SamlConnection|OauthConfig)?\b' packages/backend/src || true
printf '\n## Changeset\n'
cat -n .changeset/major-poets-refuse.mdRepository: clerk/javascript Length of output: 15917 Bump These exported constructors are part of the public API, so adding required trailing parameters is a breaking change. Update
📍 Affects 2 files
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| ) {} | ||
|
|
||
| static fromJSON(data: EnterpriseConnectionJSON): EnterpriseConnection { | ||
| return new EnterpriseConnection( | ||
| data.id, | ||
| data.name, | ||
| data.domains, | ||
| data.organization_id, | ||
| data.organization_id ?? null, | ||
| data.active, | ||
| data.sync_user_attributes, | ||
| data.allow_subdomains, | ||
|
|
@@ -179,6 +290,12 @@ export class EnterpriseConnection { | |
| data.updated_at, | ||
| data.saml_connection != null ? EnterpriseConnectionSamlConnection.fromJSON(data.saml_connection) : null, | ||
| data.oauth_config != null ? EnterpriseConnectionOauthConfig.fromJSON(data.oauth_config) : null, | ||
| data.provider, | ||
| data.logo_public_url, | ||
| data.allow_organization_account_linking, | ||
| data.authenticatable, | ||
| data.disable_jit_provisioning, | ||
| data.custom_attributes?.map(attr => EnterpriseConnectionCustomAttribute.fromJSON(attr)), | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Cover omitted optional response fields.
This fixture populates every newly optional SAML/OAuth field and
custom_attributes. Add a case omitting them and assert the resource exposesundefined; this locks in the intended serializer-omission behavior.🤖 Prompt for AI Agents
Source: Coding guidelines