From 38d2dee04a52b0774a46c8dcd2f1b51f1428a037 Mon Sep 17 00:00:00 2001 From: Hagai Date: Wed, 29 Jul 2026 10:48:06 +0000 Subject: [PATCH 1/2] feat(management): add enforceSSOExclusions to tenant create and update The /v1/mgmt/tenant/create and /v1/mgmt/tenant/update endpoints accept enforceSSOExclusions and the Go SDK sends it on every write, but the Node SDK had no way to supply it. Since tenant update is a full replace, any Node SDK tenant write silently cleared an enforceSSOExclusions list set from the console or another SDK. Also adds enforceSSOExclusions and roleInheritance to the Tenant read type; both are returned by GET /v1/mgmt/tenant but were missing, which made roleInheritance write-only and unpreservable in a read-modify-write. --- README.md | 19 ++++++++++++++++--- lib/management/tenant.test.ts | 6 ++++++ lib/management/tenant.ts | 6 ++++++ lib/management/types.ts | 2 ++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 808fe3f39..6cbde1a02 100644 --- a/README.md +++ b/README.md @@ -614,9 +614,16 @@ You can create, update, delete or load tenants, as well as read and update tenan ```typescript // The self provisioning domains or optional. If given they'll be used to associate // Users logging in to this tenant -await descopeClient.management.tenant.create('My Tenant', ['domain.com'], { - customAttributeName: 'val', -}); +await descopeClient.management.tenant.create( + 'My Tenant', + ['domain.com'], + { customAttributeName: 'val' }, + true, // enforceSSO + false, // disabled + '', // parent tenant ID + 'none', // roleInheritance + ['admin@domain.com'], // enforceSSOExclusions - user IDs excluded from SSO enforcement +); // You can optionally set your own ID when creating a tenant await descopeClient.management.tenant.createWithId('my-custom-id', 'My Tenant', ['domain.com'], { @@ -624,11 +631,17 @@ await descopeClient.management.tenant.createWithId('my-custom-id', 'My Tenant', }); // Update will override all fields as is. Use carefully. +// Any field you omit is cleared on the tenant, so read the tenant first and pass +// back the values you want to keep. await descopeClient.management.tenant.update( 'my-custom-id', 'My Tenant', ['domain.com', 'another-domain.com'], { customAttributeName: 'val' }, + true, // enforceSSO + false, // disabled + 'none', // roleInheritance + ['admin@domain.com'], // enforceSSOExclusions - user IDs excluded from SSO enforcement ); // Update the tenant's default roles by providing role names. diff --git a/lib/management/tenant.test.ts b/lib/management/tenant.test.ts index 328d4b686..8f35159ab 100644 --- a/lib/management/tenant.test.ts +++ b/lib/management/tenant.test.ts @@ -65,6 +65,7 @@ describe('Management Tenant', () => { true, 'p', 'none', + ['excluded@example.com'], ); expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.tenant.create, { @@ -72,6 +73,7 @@ describe('Management Tenant', () => { selfProvisioningDomains: ['d1'], customAttributes: { customAttr: 'value' }, enforceSSO: true, + enforceSSOExclusions: ['excluded@example.com'], disabled: true, parent: 'p', roleInheritance: 'none', @@ -146,6 +148,7 @@ describe('Management Tenant', () => { true, 'p', '', + ['excluded@example.com'], ); expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.tenant.create, { @@ -154,6 +157,7 @@ describe('Management Tenant', () => { selfProvisioningDomains: ['d1'], customAttributes: { customAttr: 'value' }, enforceSSO: true, + enforceSSOExclusions: ['excluded@example.com'], disabled: true, parent: 'p', roleInheritance: '', @@ -227,6 +231,7 @@ describe('Management Tenant', () => { true, true, 'none', + ['excluded@example.com'], ); expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.tenant.update, { @@ -235,6 +240,7 @@ describe('Management Tenant', () => { selfProvisioningDomains: ['d1'], customAttributes: { customAttr: 'value' }, enforceSSO: true, + enforceSSOExclusions: ['excluded@example.com'], disabled: true, roleInheritance: 'none', }); diff --git a/lib/management/tenant.ts b/lib/management/tenant.ts index fc77d6e41..abc70f947 100644 --- a/lib/management/tenant.ts +++ b/lib/management/tenant.ts @@ -21,6 +21,7 @@ const withTenant = (httpClient: HttpClient) => ({ disabled?: boolean, parent?: string, roleInheritance?: '' | 'none' | 'userOnly', + enforceSSOExclusions?: string[], ): Promise> => transformResponse( httpClient.post(apiPaths.tenant.create, { @@ -28,6 +29,7 @@ const withTenant = (httpClient: HttpClient) => ({ selfProvisioningDomains, customAttributes, enforceSSO, + enforceSSOExclusions, disabled, parent, roleInheritance, @@ -42,6 +44,7 @@ const withTenant = (httpClient: HttpClient) => ({ disabled?: boolean, parent?: string, roleInheritance?: '' | 'none' | 'userOnly', + enforceSSOExclusions?: string[], ): Promise> => transformResponse( httpClient.post(apiPaths.tenant.create, { @@ -50,6 +53,7 @@ const withTenant = (httpClient: HttpClient) => ({ selfProvisioningDomains, customAttributes, enforceSSO, + enforceSSOExclusions, disabled, parent, roleInheritance, @@ -63,6 +67,7 @@ const withTenant = (httpClient: HttpClient) => ({ enforceSSO?: boolean, disabled?: boolean, roleInheritance?: '' | 'none' | 'userOnly', + enforceSSOExclusions?: string[], ): Promise> => transformResponse( httpClient.post(apiPaths.tenant.update, { @@ -71,6 +76,7 @@ const withTenant = (httpClient: HttpClient) => ({ selfProvisioningDomains, customAttributes, enforceSSO, + enforceSSOExclusions, disabled, roleInheritance, }), diff --git a/lib/management/types.ts b/lib/management/types.ts index fcf5e40d4..061f1e6f5 100644 --- a/lib/management/types.ts +++ b/lib/management/types.ts @@ -190,8 +190,10 @@ export type Tenant = { domains?: string[]; authType?: 'none' | 'saml' | 'oidc'; enforceSSO?: boolean; + enforceSSOExclusions?: string[]; disabled?: boolean; defaultRoles?: string[]; + roleInheritance?: '' | 'none' | 'userOnly'; }; export type SSOSetupSuiteSettingsDisabledFeatures = { From d570c85f940fddd592a5e0b5676200aaf3516475 Mon Sep 17 00:00:00 2001 From: Hagai Date: Thu, 30 Jul 2026 07:09:07 +0000 Subject: [PATCH 2/2] docs(management): address review findings on tenant examples Use the README's user-ID placeholder instead of an email for enforceSSOExclusions, expand the createWithId example to show the same optional arguments, scope the full-replace warning to the parameters update actually takes, and note that the sample values are illustrative rather than defaults. --- README.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6cbde1a02..db401e5f2 100644 --- a/README.md +++ b/README.md @@ -622,17 +622,29 @@ await descopeClient.management.tenant.create( false, // disabled '', // parent tenant ID 'none', // roleInheritance - ['admin@domain.com'], // enforceSSOExclusions - user IDs excluded from SSO enforcement + [''], // enforceSSOExclusions - Descope user IDs excluded from SSO enforcement ); -// You can optionally set your own ID when creating a tenant -await descopeClient.management.tenant.createWithId('my-custom-id', 'My Tenant', ['domain.com'], { - customAttributeName: 'val', -}); +// You can optionally set your own ID when creating a tenant. It accepts the same +// optional arguments as create, shifted one position to make room for the ID. +await descopeClient.management.tenant.createWithId( + 'my-custom-id', + 'My Tenant', + ['domain.com'], + { customAttributeName: 'val' }, + true, // enforceSSO + false, // disabled + '', // parent tenant ID + 'none', // roleInheritance + [''], // enforceSSOExclusions - Descope user IDs excluded from SSO enforcement +); // Update will override all fields as is. Use carefully. -// Any field you omit is cleared on the tenant, so read the tenant first and pass -// back the values you want to keep. +// Every parameter update takes is replaced, so any one you omit is cleared on the tenant - +// read the tenant first and pass back the values you want to keep. Fields update has no +// parameter for, such as the parent tenant and default roles, are not affected. +// The values below are illustrative rather than defaults: copying this call as-is also +// turns enforceSSO on. await descopeClient.management.tenant.update( 'my-custom-id', 'My Tenant', @@ -641,7 +653,7 @@ await descopeClient.management.tenant.update( true, // enforceSSO false, // disabled 'none', // roleInheritance - ['admin@domain.com'], // enforceSSOExclusions - user IDs excluded from SSO enforcement + [''], // enforceSSOExclusions - Descope user IDs excluded from SSO enforcement ); // Update the tenant's default roles by providing role names.