Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/tools/auth0/handlers/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnosis on err.body.errorCode is spot on. One thing I wanted to check with you, the first branch
err?.statusCode === 403 returns true for any 403 regardless of what the error code is. So a 403 with errorCode: 'insufficient_scope' (M2M app missing a required scope) would
also be caught here and logged as "Org-to-app entitlement is not enabled", which could send the operator down the wrong path

I know this broad 403 catch was already there before this PR, so not something you introduced. But since we're refactoring this into a helper anyway, would it make sense to tighten it here?

Since err?.body?.errorCode === 'feature_not_enabled' already handles both 400 and 403 carrying the right error
code, the status check seems redundant. Something like:

function isFeatureNotEnabled(err): boolean {
  return err?.body?.errorCode === 'feature_not_enabled';
}

Or if you'd prefer to be explicit about which status codes are expected:

function isFeatureNotEnabled(err): boolean {
   return (err?.statusCode === 400 || err?.statusCode === 403)
     && err?.body?.errorCode === 'feature_not_enabled';
 }

Happy to hear your thoughts on this, maybe there's a reason to keep the status only check that I'm missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. Tightened away from the raw 403 assumption. The log now reports the actual error code instead of always saying the feature is off, so a 403 insufficient_scope no longer gets mislabeled.

}

export const schema = {
type: 'array',
items: {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
50 changes: 49 additions & 1 deletion test/tools/auth0/handlers/organizations.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
},
Expand Down