From 1fd49b890c9dbc98036fed5053f3396c8b9e6153 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Mon, 13 Jul 2026 22:55:16 -0500 Subject: [PATCH 1/5] fix(backend): Add required provider field to CreateEnterpriseConnectionParams The Backend API validates provider as required on enterprise connection creation, so calls without it type-checked but failed at runtime. Co-Authored-By: Claude Fable 5 --- .changeset/heavy-melons-argue.md | 5 +++++ integration/tests/tanstack-start/enterprise-sso.test.ts | 3 +-- .../src/api/__tests__/EnterpriseConnectionApi.test.ts | 4 ++++ .../backend/src/api/endpoints/EnterpriseConnectionApi.ts | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/heavy-melons-argue.md diff --git a/.changeset/heavy-melons-argue.md b/.changeset/heavy-melons-argue.md new file mode 100644 index 00000000000..096cdff2096 --- /dev/null +++ b/.changeset/heavy-melons-argue.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Add the required `provider` field to `CreateEnterpriseConnectionParams`. The Backend API has always required `provider` when creating an enterprise connection, so calls to `createEnterpriseConnection()` without it type-checked but failed at runtime. The parameter type now reflects the API contract. diff --git a/integration/tests/tanstack-start/enterprise-sso.test.ts b/integration/tests/tanstack-start/enterprise-sso.test.ts index 96f83d23fdf..5f607c96b07 100644 --- a/integration/tests/tanstack-start/enterprise-sso.test.ts +++ b/integration/tests/tanstack-start/enterprise-sso.test.ts @@ -13,7 +13,6 @@ const FAKE_IDP_CERTIFICATE = /** * Helper to create and activate a SAML enterprise connection. * The Clerk API requires creating the connection first (inactive), then activating via update. - * The `provider` field is required by the API but missing from the SDK types, so we cast. */ async function createActiveEnterpriseConnection( clerk: ReturnType['services']['clerk'], @@ -28,7 +27,7 @@ async function createActiveEnterpriseConnection( idpSsoUrl: opts.idpSsoUrl, idpCertificate: FAKE_IDP_CERTIFICATE, }, - } as Parameters[0]); + }); return clerk.enterpriseConnections.updateEnterpriseConnection(conn.id, { active: true }); } diff --git a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts index 739eeb6445c..2cf4897df61 100644 --- a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts +++ b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts @@ -60,6 +60,7 @@ describe('EnterpriseConnectionAPI', () => { expect(body.name).toBe('Clerk'); expect(body.domains).toEqual(['clerk.dev']); + expect(body.provider).toBe('saml_custom'); expect(body.saml).toEqual({ idp_entity_id: 'xxx', idp_metadata_url: 'https://oauth.devsuccess.app/metadata', @@ -74,6 +75,7 @@ describe('EnterpriseConnectionAPI', () => { await apiClient.enterpriseConnections.createEnterpriseConnection({ name: 'Clerk', domains: ['clerk.dev'], + provider: 'saml_custom', saml: { idpEntityId: 'xxx', idpMetadataUrl: 'https://oauth.devsuccess.app/metadata', @@ -89,6 +91,7 @@ describe('EnterpriseConnectionAPI', () => { validateHeaders(async ({ request }) => { const body = (await request.json()) as Record; + expect(body.provider).toBe('oidc_custom'); expect(body.oidc).toEqual({ discovery_url: 'https://oidc.example.com/.well-known/openid-configuration', client_id: 'client_123', @@ -107,6 +110,7 @@ describe('EnterpriseConnectionAPI', () => { await apiClient.enterpriseConnections.createEnterpriseConnection({ name: 'OIDC Connection', domains: ['example.com'], + provider: 'oidc_custom', oidc: { discoveryUrl: 'https://oidc.example.com/.well-known/openid-configuration', clientId: 'client_123', diff --git a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts index ccbcc15d0d1..4cc8e3a46ec 100644 --- a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts +++ b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts @@ -83,6 +83,8 @@ export type CreateEnterpriseConnectionParams = { active?: boolean; /** Whether the enterprise connection should sync user attributes between the IdP and Clerk. */ syncUserAttributes?: boolean; + /** The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. */ + provider: string; /** Configuration for if the enterprise connection uses OAuth (OIDC). */ oidc?: EnterpriseConnectionOidcParams; /** Configuration for if the enterprise connection uses SAML. */ From 2092c3a2f1c1780939118560be08302638f549a7 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Mon, 13 Jul 2026 23:09:50 -0500 Subject: [PATCH 2/5] fix(backend): Narrow provider to supported values and lock in type contract Reuses the OrganizationEnterpriseConnectionProvider union from @clerk/shared/types (matching the sibling SamlConnectionApi pattern) and adds type-level tests so provider can't silently become optional or widen back to string. Co-Authored-By: Claude Fable 5 --- .changeset/heavy-melons-argue.md | 2 +- .../src/api/__tests__/EnterpriseConnectionApi.test.ts | 9 ++++++++- .../backend/src/api/endpoints/EnterpriseConnectionApi.ts | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.changeset/heavy-melons-argue.md b/.changeset/heavy-melons-argue.md index 096cdff2096..d2baf5ee0cc 100644 --- a/.changeset/heavy-melons-argue.md +++ b/.changeset/heavy-melons-argue.md @@ -2,4 +2,4 @@ '@clerk/backend': patch --- -Add the required `provider` field to `CreateEnterpriseConnectionParams`. The Backend API has always required `provider` when creating an enterprise connection, so calls to `createEnterpriseConnection()` without it type-checked but failed at runtime. The parameter type now reflects the API contract. +Add the required `provider` field to `CreateEnterpriseConnectionParams`. The Backend API has always required `provider` when creating an enterprise connection, so calls to `createEnterpriseConnection()` without it type-checked but failed at runtime. The field is typed to the supported provider values (`'saml_custom'`, `'saml_okta'`, `'saml_google'`, `'saml_microsoft'`, `'oidc_custom'`, `'oidc_github_enterprise'`, `'oidc_gitlab'`), so unsupported values are also caught at compile time. diff --git a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts index 2cf4897df61..d9675e0080c 100644 --- a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts +++ b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts @@ -1,7 +1,8 @@ import { http, HttpResponse } from 'msw'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, expectTypeOf, it } from 'vitest'; import { server, validateHeaders } from '../../mock-server'; +import type { CreateEnterpriseConnectionParams } from '../endpoints/EnterpriseConnectionApi'; import { createBackendApiClient } from '../factory'; describe('EnterpriseConnectionAPI', () => { @@ -122,6 +123,12 @@ describe('EnterpriseConnectionAPI', () => { }, }); }); + + it('requires provider and rejects unsupported values at the type level', () => { + expectTypeOf<{ name: string; domains: string[] }>().not.toExtend(); + expectTypeOf<'saml_bogus'>().not.toExtend(); + expectTypeOf<'saml_okta'>().toExtend(); + }); }); describe('updateEnterpriseConnection', () => { diff --git a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts index 4cc8e3a46ec..3a4a246fce2 100644 --- a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts +++ b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/shared/types'; +import type { ClerkPaginationRequest, OrganizationEnterpriseConnectionProvider } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { EnterpriseConnection } from '../resources'; @@ -84,7 +84,7 @@ export type CreateEnterpriseConnectionParams = { /** Whether the enterprise connection should sync user attributes between the IdP and Clerk. */ syncUserAttributes?: boolean; /** The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. */ - provider: string; + provider: OrganizationEnterpriseConnectionProvider; /** Configuration for if the enterprise connection uses OAuth (OIDC). */ oidc?: EnterpriseConnectionOidcParams; /** Configuration for if the enterprise connection uses SAML. */ From c6348b1e3dd3cc67a881d5b5eef35df88069142a Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Mon, 13 Jul 2026 23:00:47 -0500 Subject: [PATCH 3/5] fix(backend): Require name and domains when creating enterprise connections Stacked on #9153 (required provider field). Aligns the remaining CreateEnterpriseConnectionParams gaps with the Backend API contract, which validates name and domains (min 1) as required. Deprecates syncUserAttributes on create and provider on update, since the Backend API ignores both. Co-Authored-By: Claude Fable 5 --- .changeset/violet-planes-repeat.md | 9 +++++++++ .../src/api/endpoints/EnterpriseConnectionApi.ts | 16 +++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 .changeset/violet-planes-repeat.md diff --git a/.changeset/violet-planes-repeat.md b/.changeset/violet-planes-repeat.md new file mode 100644 index 00000000000..4aee6753981 --- /dev/null +++ b/.changeset/violet-planes-repeat.md @@ -0,0 +1,9 @@ +--- +'@clerk/backend': patch +--- + +Align `CreateEnterpriseConnectionParams` and `UpdateEnterpriseConnectionParams` with the Backend API contract: + +- `name` and `domains` are now required on `CreateEnterpriseConnectionParams`. The Backend API already rejected requests missing either of them, so calls that omitted these fields failed at runtime; the types now surface this at compile time. +- Deprecated `syncUserAttributes` on `CreateEnterpriseConnectionParams`. The Backend API ignores this parameter on create; use `updateEnterpriseConnection()` to set it. +- Deprecated `provider` on `UpdateEnterpriseConnectionParams`. The Backend API ignores this parameter on update; the provider cannot be changed after creation. diff --git a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts index 3a4a246fce2..7e3fdacab91 100644 --- a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts +++ b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts @@ -74,14 +74,17 @@ export interface EnterpriseConnectionSamlParams { /** @generateWithEmptyComment */ export type CreateEnterpriseConnectionParams = { /** The name of the enterprise connection. */ - name?: string; - /** The [Verified Domains](https://clerk.com/docs/guides/organizations/add-members/verified-domains) of the enterprise connection. */ - domains?: string[]; + name: string; + /** The [Verified Domains](https://clerk.com/docs/guides/organizations/add-members/verified-domains) of the enterprise connection. Must contain at least one domain. */ + domains: string[]; /** The organization ID of the enterprise connection. */ organizationId?: string; /** Whether the enterprise connection should be active. */ active?: boolean; - /** Whether the enterprise connection should sync user attributes between the IdP and Clerk. */ + /** + * Whether the enterprise connection should sync user attributes between the IdP and Clerk. + * @deprecated The Backend API does not support this parameter on create and ignores it. Use `updateEnterpriseConnection()` to set it. + */ syncUserAttributes?: boolean; /** The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. */ provider: OrganizationEnterpriseConnectionProvider; @@ -103,7 +106,10 @@ export type UpdateEnterpriseConnectionParams = { active?: boolean; /** Whether the enterprise connection should sync user attributes between the IdP and Clerk. */ syncUserAttributes?: boolean; - /** The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. */ + /** + * The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. + * @deprecated The Backend API does not support this parameter on update and ignores it. The provider cannot be changed after creation. + */ provider?: string; /** Configuration for if the enterprise connection uses OAuth (OIDC). */ oidc?: EnterpriseConnectionOidcParams; From 7b6b5c002591bfeeb6629ab5c38c54c016acc5a0 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Mon, 13 Jul 2026 23:12:34 -0500 Subject: [PATCH 4/5] fix(backend): Add missing optional enterprise connection params Adds the optional create/update params the Backend API supports but the SDK types omitted: allowOrganizationAccountLinking, customAttributes, authenticatable, disableJitProvisioning, disableAdditionalIdentifications (update only), and saml.loginHint. Verified against BAPI's CreateParams/UpdateParams/SAMLParams in clerk_go. Co-Authored-By: Claude Fable 5 --- .changeset/tidy-donuts-attend.md | 5 + .../__tests__/EnterpriseConnectionApi.test.ts | 94 +++++++++++++++++++ .../api/endpoints/EnterpriseConnectionApi.ts | 42 +++++++++ 3 files changed, 141 insertions(+) create mode 100644 .changeset/tidy-donuts-attend.md diff --git a/.changeset/tidy-donuts-attend.md b/.changeset/tidy-donuts-attend.md new file mode 100644 index 00000000000..82cccf3a5e8 --- /dev/null +++ b/.changeset/tidy-donuts-attend.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Add the remaining optional enterprise connection parameters supported by the Backend API. `CreateEnterpriseConnectionParams` and `UpdateEnterpriseConnectionParams` now accept `allowOrganizationAccountLinking`, `customAttributes`, `authenticatable`, and `disableJitProvisioning` (update also accepts `disableAdditionalIdentifications`), and SAML params accept `loginHint` for configuring the `login_hint` sent to the IdP. diff --git a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts index d9675e0080c..90ce3e6ec88 100644 --- a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts +++ b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts @@ -129,6 +129,59 @@ describe('EnterpriseConnectionAPI', () => { expectTypeOf<'saml_bogus'>().not.toExtend(); expectTypeOf<'saml_okta'>().toExtend(); }); + + it('sends provisioning, custom attribute, and login hint params in snake_case', async () => { + server.use( + http.post( + 'https://api.clerk.test/v1/enterprise_connections', + validateHeaders(async ({ request }) => { + const body = (await request.json()) as Record; + + expect(body.allow_organization_account_linking).toBe(true); + expect(body.authenticatable).toBe(false); + expect(body.disable_jit_provisioning).toBe(true); + expect(body.custom_attributes).toEqual([ + { + name: 'Employee Number', + key: 'employee_number', + sso_path: 'user.employeeNumber', + multi_valued: false, + }, + ]); + expect((body.saml as Record).login_hint).toEqual({ + mode: 'custom_attribute', + source: 'employee_number', + }); + + return HttpResponse.json(mockEnterpriseConnectionResponse); + }), + ), + ); + + await apiClient.enterpriseConnections.createEnterpriseConnection({ + name: 'Clerk', + domains: ['clerk.dev'], + provider: 'saml_custom', + allowOrganizationAccountLinking: true, + authenticatable: false, + disableJitProvisioning: true, + customAttributes: [ + { + name: 'Employee Number', + key: 'employee_number', + ssoPath: 'user.employeeNumber', + multiValued: false, + }, + ], + saml: { + idpEntityId: 'xxx', + loginHint: { + mode: 'custom_attribute', + source: 'employee_number', + }, + }, + }); + }); }); describe('updateEnterpriseConnection', () => { @@ -157,6 +210,47 @@ describe('EnterpriseConnectionAPI', () => { }, }); }); + + it('sends provisioning and custom attribute params in snake_case', async () => { + server.use( + http.patch( + 'https://api.clerk.test/v1/enterprise_connections/entconn_123', + validateHeaders(async ({ request }) => { + const body = (await request.json()) as Record; + + expect(body.sync_user_attributes).toBe(true); + expect(body.disable_additional_identifications).toBe(true); + expect(body.allow_organization_account_linking).toBe(false); + expect(body.authenticatable).toBe(true); + expect(body.disable_jit_provisioning).toBe(false); + expect(body.custom_attributes).toEqual([ + { + name: 'Department', + key: 'department', + scim_path: 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department', + }, + ]); + + return HttpResponse.json(mockEnterpriseConnectionResponse); + }), + ), + ); + + await apiClient.enterpriseConnections.updateEnterpriseConnection('entconn_123', { + syncUserAttributes: true, + disableAdditionalIdentifications: true, + allowOrganizationAccountLinking: false, + authenticatable: true, + disableJitProvisioning: false, + customAttributes: [ + { + name: 'Department', + key: 'department', + scimPath: 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department', + }, + ], + }); + }); }); describe('getEnterpriseConnectionList', () => { diff --git a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts index 7e3fdacab91..467b164e29e 100644 --- a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts +++ b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts @@ -49,6 +49,28 @@ export interface EnterpriseConnectionSamlAttributeMappingParams { lastName?: string | null; } +/** @inline */ +export interface EnterpriseConnectionSamlLoginHintParams { + /** 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`. */ + mode: 'email_address' | 'custom_attribute' | 'off'; + /** The user `publicMetadata` key to read the `login_hint` value from. Required when `mode` is `'custom_attribute'`, and must be omitted otherwise. */ + source?: string; +} + +/** @inline */ +export interface EnterpriseConnectionCustomAttributeParams { + /** The display name of the custom attribute. */ + name: string; + /** The key to store the custom attribute under. */ + key: string; + /** The SSO (SAML or OIDC) attribute path to read the value from. */ + ssoPath?: string; + /** The SCIM attribute path to read the value from. */ + scimPath?: string; + /** Whether the custom attribute holds multiple values. */ + multiValued?: boolean; +} + /** @inline */ export interface EnterpriseConnectionSamlParams { /** Whether the SAML connection allows Identity Provider (IdP) initiated flows. */ @@ -69,6 +91,8 @@ export interface EnterpriseConnectionSamlParams { idpMetadataUrl?: string; /** The IdP Single-Sign On URL for the SAML connection. */ idpSsoUrl?: string; + /** Configuration for the `login_hint` the SAML connection sends to the IdP. */ + loginHint?: EnterpriseConnectionSamlLoginHintParams; } /** @generateWithEmptyComment */ @@ -88,6 +112,14 @@ export type CreateEnterpriseConnectionParams = { syncUserAttributes?: boolean; /** The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. */ provider: OrganizationEnterpriseConnectionProvider; + /** Whether existing users who are members of the organization can link their account to their enterprise identity. If `false`, only sign-up flows apply. */ + allowOrganizationAccountLinking?: boolean; + /** The custom attribute mappings for the enterprise connection. */ + customAttributes?: EnterpriseConnectionCustomAttributeParams[]; + /** Whether the enterprise connection can be used for sign-in and sign-up. Requires the authenticatable enterprise connections feature to be enabled for the instance. */ + authenticatable?: boolean; + /** Whether Just-in-Time (JIT) provisioning of users is disabled for the enterprise connection. */ + disableJitProvisioning?: boolean; /** Configuration for if the enterprise connection uses OAuth (OIDC). */ oidc?: EnterpriseConnectionOidcParams; /** Configuration for if the enterprise connection uses SAML. */ @@ -106,6 +138,16 @@ export type UpdateEnterpriseConnectionParams = { active?: boolean; /** Whether the enterprise connection should sync user attributes between the IdP and Clerk. */ syncUserAttributes?: boolean; + /** Whether additional identifications are disabled for the enterprise connection. */ + disableAdditionalIdentifications?: boolean; + /** Whether existing users who are members of the organization can link their account to their enterprise identity. If `false`, only sign-up flows apply. */ + allowOrganizationAccountLinking?: boolean; + /** The custom attribute mappings for the enterprise connection. */ + customAttributes?: EnterpriseConnectionCustomAttributeParams[]; + /** Whether the enterprise connection can be used for sign-in and sign-up. Requires the authenticatable enterprise connections feature to be enabled for the instance. */ + authenticatable?: boolean; + /** Whether Just-in-Time (JIT) provisioning of users is disabled for the enterprise connection. */ + disableJitProvisioning?: boolean; /** * The identity provider (IdP) of the enterprise connection. For example, `'saml_custom'` or `'oidc_custom'`. * @deprecated The Backend API does not support this parameter on update and ignores it. The provider cannot be changed after creation. From 1508711f22743a0d26cf39f2900a70432f3af8f9 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Mon, 13 Jul 2026 23:41:33 -0500 Subject: [PATCH 5/5] fix(backend): Enforce login hint source/mode pairing at the type level Codex review: BAPI requires login_hint.source exactly when mode is custom_attribute and rejects it otherwise; model as a discriminated union. Also lock name/domains as required with type-level tests. Co-Authored-By: Claude Fable 5 --- .../__tests__/EnterpriseConnectionApi.test.ts | 22 ++++++++++++++++++- .../api/endpoints/EnterpriseConnectionApi.ts | 19 +++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts index 90ce3e6ec88..f46f0281e8f 100644 --- a/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts +++ b/packages/backend/src/api/__tests__/EnterpriseConnectionApi.test.ts @@ -2,7 +2,10 @@ import { http, HttpResponse } from 'msw'; import { describe, expect, expectTypeOf, it } from 'vitest'; import { server, validateHeaders } from '../../mock-server'; -import type { CreateEnterpriseConnectionParams } from '../endpoints/EnterpriseConnectionApi'; +import type { + CreateEnterpriseConnectionParams, + EnterpriseConnectionSamlLoginHintParams, +} from '../endpoints/EnterpriseConnectionApi'; import { createBackendApiClient } from '../factory'; describe('EnterpriseConnectionAPI', () => { @@ -130,6 +133,23 @@ describe('EnterpriseConnectionAPI', () => { expectTypeOf<'saml_okta'>().toExtend(); }); + it('requires name and domains at the type level', () => { + expectTypeOf<{ + name: string; + domains: string[]; + provider: 'saml_custom'; + }>().toExtend(); + expectTypeOf<{ domains: string[]; provider: 'saml_custom' }>().not.toExtend(); + expectTypeOf<{ name: string; provider: 'saml_custom' }>().not.toExtend(); + }); + + it('requires a login hint source exactly when mode is custom_attribute at the type level', () => { + expectTypeOf<{ mode: 'custom_attribute'; source: string }>().toExtend(); + expectTypeOf<{ mode: 'email_address' }>().toExtend(); + expectTypeOf<{ mode: 'custom_attribute' }>().not.toExtend(); + expectTypeOf<{ mode: 'off'; source: string }>().not.toExtend(); + }); + it('sends provisioning, custom attribute, and login hint params in snake_case', async () => { server.use( http.post( diff --git a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts index 467b164e29e..644fc0c88d8 100644 --- a/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts +++ b/packages/backend/src/api/endpoints/EnterpriseConnectionApi.ts @@ -50,12 +50,19 @@ export interface EnterpriseConnectionSamlAttributeMappingParams { } /** @inline */ -export interface EnterpriseConnectionSamlLoginHintParams { - /** 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`. */ - mode: 'email_address' | 'custom_attribute' | 'off'; - /** The user `publicMetadata` key to read the `login_hint` value from. Required when `mode` is `'custom_attribute'`, and must be omitted otherwise. */ - source?: string; -} +export type EnterpriseConnectionSamlLoginHintParams = + | { + /** Sends the value stored at the user `publicMetadata` key named by `source` as the `login_hint`. */ + mode: 'custom_attribute'; + /** The user `publicMetadata` key to read the `login_hint` value from. */ + source: string; + } + | { + /** How the SAML connection emits the `login_hint` sent to the IdP: `'email_address'` sends the typed identifier and `'off'` omits the `login_hint`. */ + mode: 'email_address' | 'off'; + /** Only supported when `mode` is `'custom_attribute'`. */ + source?: never; + }; /** @inline */ export interface EnterpriseConnectionCustomAttributeParams {