diff --git a/src/tools/auth0/handlers/organizations.ts b/src/tools/auth0/handlers/organizations.ts index 4e6982add..c16293c8f 100644 --- a/src/tools/auth0/handlers/organizations.ts +++ b/src/tools/auth0/handlers/organizations.ts @@ -11,6 +11,13 @@ import { Client } from './clients'; import { Connection } from './connections'; import { ClientGrant } from './clientGrants'; +// Org sub-resources are skipped when the tenant can't read them: the EA feature is off +// (feature_not_enabled, returned as 400 or 403) or the token lacks scope (403). The SDK +// puts the API code on `err.body.errorCode`, not `err.errorCode`. +function isOrgSubresourceUnavailable(err: any): boolean { + return err?.statusCode === 403 || err?.body?.errorCode === 'feature_not_enabled'; +} + export const schema = { type: 'array', items: { @@ -842,9 +849,9 @@ export default class OrganizationsHandler extends DefaultHandler { if (err.statusCode === 404 || err.statusCode === 501) { return null; } - if (err.statusCode === 403 || err.errorCode === 'feature_not_enabled') { + if (isOrgSubresourceUnavailable(err)) { log.debug( - 'Organization Discovery domains are not enabled for this tenant. Please verify `scope` or contact Auth0 support to enable this feature.' + `Skipping organization discovery domains (${err?.body?.errorCode ?? err.statusCode}).` ); return null; } @@ -933,10 +940,8 @@ export default class OrganizationsHandler extends DefaultHandler { if (err.statusCode === 404 || err.statusCode === 501) { return null; } - if (err.statusCode === 403 || err.errorCode === 'feature_not_enabled') { - log.debug( - 'Org-to-app entitlement is not enabled for this tenant. Skipping org-client associations.' - ); + if (isOrgSubresourceUnavailable(err)) { + log.debug(`Skipping org-client associations (${err?.body?.errorCode ?? err.statusCode}).`); return null; } throw err; diff --git a/test/tools/auth0/handlers/organizations.tests.js b/test/tools/auth0/handlers/organizations.tests.js index 4e3a41689..24632b8ea 100644 --- a/test/tools/auth0/handlers/organizations.tests.js +++ b/test/tools/auth0/handlers/organizations.tests.js @@ -1823,7 +1823,55 @@ describe('#organizations handler', () => { list: () => { const err = new Error('feature_not_enabled'); err.statusCode = 403; - err.errorCode = 'feature_not_enabled'; + err.body = { errorCode: 'feature_not_enabled' }; + throw err; + }, + }, + }, + clients: { + list: (params) => mockPagedData(params, 'clients', sampleClients), + }, + pool, + }; + + const handler = new organizations.default({ client: pageClient(auth0), config }); + const data = await handler.getType(); + + // clients property should not be set when feature is unavailable + expect(data[0].clients).to.be.undefined; + }); + + it('should gracefully handle when org-clients feature is not enabled (400 with errorCode on body)', async () => { + // Reproduces the reported failure: some tenants surface feature_not_enabled + // as a 400 whose error code lives on `err.body.errorCode`, not `err.errorCode`. + const freshOrg = { + id: '999', + name: 'fresh-org', + display_name: 'Fresh Org', + client_grants: [], + }; + const auth0 = { + organizations: { + list: (params) => Promise.resolve(mockPagedData(params, 'organizations', [freshOrg])), + connections: { + list: () => ({ data: [], hasNextPage: () => false }), + }, + clientGrants: { + list: () => ({ data: [], hasNextPage: () => false }), + }, + discoveryDomains: { + list: () => ({ data: [], hasNextPage: () => false }), + }, + clients: { + list: () => { + const err = new Error('Feature not enabled for this tenant.'); + err.statusCode = 400; + err.body = { + statusCode: 400, + error: 'Bad Request', + message: 'Feature not enabled for this tenant.', + errorCode: 'feature_not_enabled', + }; throw err; }, },