Skip to content

Commit c97c897

Browse files
authored
fix: error handling in guardian handlers for forbidden depreated feature (#1086)
* fix: enhance error handling in guardian handlers for forbidden deprecated feature * test: add unit tests for isForbiddenFeatureError utility function * test: add handling for forbidden errors in guardian factor and template tests
1 parent f52336b commit c97c897

12 files changed

+234
-68
lines changed

src/tools/auth0/handlers/guardianFactorProviders.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import DefaultHandler from './default';
22
import constants from '../../constants';
33
import { Asset, Assets } from '../../../types';
4+
import { isForbiddenFeatureError } from '../../utils';
45

56
const mappings = Object.entries(constants.GUARDIAN_FACTOR_PROVIDERS).reduce(
67
(accum: { name: string; provider: string }[], [name, providers]) => {
@@ -35,29 +36,40 @@ export default class GuardianFactorProvidersHandler extends DefaultHandler {
3536
});
3637
}
3738

38-
async getType(): Promise<Asset[]> {
39+
async getType(): Promise<Asset[] | null> {
3940
if (this.existing) return this.existing;
4041

41-
const data = await Promise.all(
42-
mappings.map(async (m) => {
43-
let provider;
44-
// TODO: This is quite a change, needs to be validated for sure.
45-
if (m.name === 'phone' && m.provider === 'twilio') {
46-
provider = await this.client.guardian.getPhoneFactorProviderTwilio();
47-
} else if (m.name === 'sms' && m.provider === 'twilio') {
48-
provider = await this.client.guardian.getSmsFactorProviderTwilio();
49-
} else if (m.name === 'push-notification' && m.provider === 'apns') {
50-
provider = await this.client.guardian.getPushNotificationProviderAPNS();
51-
} else if (m.name === 'push-notification' && m.provider === 'sns') {
52-
provider = await this.client.guardian.getPushNotificationProviderSNS();
53-
}
42+
try {
43+
const data = await Promise.all(
44+
mappings.map(async (m) => {
45+
let provider;
46+
// TODO: This is quite a change, needs to be validated for sure.
47+
if (m.name === 'phone' && m.provider === 'twilio') {
48+
provider = await this.client.guardian.getPhoneFactorProviderTwilio();
49+
} else if (m.name === 'sms' && m.provider === 'twilio') {
50+
provider = await this.client.guardian.getSmsFactorProviderTwilio();
51+
} else if (m.name === 'push-notification' && m.provider === 'apns') {
52+
provider = await this.client.guardian.getPushNotificationProviderAPNS();
53+
} else if (m.name === 'push-notification' && m.provider === 'sns') {
54+
provider = await this.client.guardian.getPushNotificationProviderSNS();
55+
}
5456

55-
return { ...m, ...provider.data };
56-
})
57-
);
57+
return { ...m, ...provider.data };
58+
})
59+
);
60+
61+
// Filter out empty, should have more then 2 keys (name, provider)
62+
return data.filter((d) => Object.keys(d).length > 2);
63+
} catch (err) {
64+
if (err.statusCode === 404 || err.statusCode === 501) {
65+
return null;
66+
}
67+
if (isForbiddenFeatureError(err, this.type)) {
68+
return null;
69+
}
5870

59-
// Filter out empty, should have more then 2 keys (name, provider)
60-
return data.filter((d) => Object.keys(d).length > 2);
71+
throw err;
72+
}
6173
}
6274

6375
async processChanges(assets: Assets): Promise<void> {

src/tools/auth0/handlers/guardianFactorTemplates.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { TemplateMessages } from 'auth0';
12
import DefaultHandler from './default';
23
import constants from '../../constants';
34
import { Assets, Asset } from '../../../types';
4-
import { TemplateMessages } from 'auth0';
5+
import { isForbiddenFeatureError } from '../../utils';
56

67
export const schema = {
78
type: 'array',
@@ -25,25 +26,33 @@ export default class GuardianFactorTemplatesHandler extends DefaultHandler {
2526
});
2627
}
2728

28-
async getType(): Promise<Asset[]> {
29+
async getType(): Promise<Asset[] | null> {
2930
if (this.existing) return this.existing;
31+
try {
32+
const data = await Promise.all(
33+
constants.GUARDIAN_FACTOR_TEMPLATES.map(async (name) => {
34+
if (name === 'sms') {
35+
const { data: templates } = await this.client.guardian.getSmsFactorTemplates();
36+
return { name, ...templates };
37+
}
3038

31-
const data = await Promise.all(
32-
constants.GUARDIAN_FACTOR_TEMPLATES.map(async (name) => {
33-
// TODO: This is quite a change, needs to be validated for sure.
34-
if (name === 'sms') {
35-
const { data: templates } = await this.client.guardian.getSmsFactorTemplates();
36-
return { name, ...templates };
37-
// TODO: GUARDIAN_FACTOR_TEMPLATES only contains 'sms'. Is that expected? We also have 'phone'.
38-
} else {
3939
const { data: templates } = await this.client.guardian.getPhoneFactorTemplates();
4040
return { name, ...templates };
41-
}
42-
})
43-
);
41+
})
42+
);
43+
44+
// Filter out empty, should have more then 1 keys (name)
45+
return data.filter((d) => Object.keys(d).length > 1);
46+
} catch (err) {
47+
if (err.statusCode === 404 || err.statusCode === 501) {
48+
return null;
49+
}
50+
if (isForbiddenFeatureError(err, this.type)) {
51+
return null;
52+
}
4453

45-
// Filter out empty, should have more then 1 keys (name)
46-
return data.filter((d) => Object.keys(d).length > 1);
54+
throw err;
55+
}
4756
}
4857

4958
async processChanges(assets: Assets): Promise<void> {

src/tools/auth0/handlers/guardianFactors.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { Factor, FactorNameEnum } from 'auth0';
12
import DefaultHandler from './default';
23
import constants from '../../constants';
34
import { Asset, Assets } from '../../../types';
4-
import { Factor, FactorNameEnum } from 'auth0';
5+
import { isForbiddenFeatureError } from '../../utils';
56

67
export const schema = {
78
type: 'array',
@@ -25,11 +26,22 @@ export default class GuardianFactorsHandler extends DefaultHandler {
2526
});
2627
}
2728

28-
async getType(): Promise<Asset[]> {
29+
async getType(): Promise<Asset[] | null> {
2930
if (this.existing) return this.existing;
30-
const { data } = await this.client.guardian.getFactors();
31-
this.existing = data;
32-
return this.existing;
31+
try {
32+
const { data } = await this.client.guardian.getFactors();
33+
this.existing = data;
34+
return this.existing;
35+
} catch (err) {
36+
if (err.statusCode === 404 || err.statusCode === 501) {
37+
return null;
38+
}
39+
if (isForbiddenFeatureError(err, this.type)) {
40+
return null;
41+
}
42+
43+
throw err;
44+
}
3345
}
3446

3547
async processChanges(assets: Assets): Promise<void> {

src/tools/auth0/handlers/guardianPhoneFactorMessageTypes.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { GetMessageTypes200Response } from 'auth0';
12
import DefaultHandler from './default';
23
import constants from '../../constants';
34
import { Asset, Assets } from '../../../types';
4-
import { GetMessageTypes200Response } from 'auth0';
5+
import { isForbiddenFeatureError } from '../../utils';
56

67
export const schema = {
78
type: 'object',
@@ -45,26 +46,29 @@ export default class GuardianPhoneMessageTypesHandler extends DefaultHandler {
4546
});
4647
}
4748

48-
async getType(): Promise<Asset[] | {}> {
49+
async getType(): Promise<Asset | null> {
4950
// in case client version does not support the operation
5051
if (
5152
!this.client.guardian ||
5253
typeof this.client.guardian.getPhoneFactorMessageTypes !== 'function'
5354
) {
54-
return {};
55+
return null;
5556
}
5657

5758
if (this.existing) return this.existing;
5859

5960
try {
6061
const { data } = await this.client.guardian.getPhoneFactorMessageTypes();
6162
this.existing = data;
62-
} catch (e) {
63-
if (isFeatureUnavailableError(e)) {
63+
} catch (err) {
64+
if (isFeatureUnavailableError(err)) {
6465
// Gracefully skip processing this configuration value.
65-
return {};
66+
return null;
67+
}
68+
if (isForbiddenFeatureError(err, this.type)) {
69+
return null;
6670
}
67-
throw e;
71+
throw err;
6872
}
6973

7074
return this.existing;

src/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { GetPhoneProviders200Response } from 'auth0';
12
import DefaultHandler from './default';
23
import constants from '../../constants';
34
import { Asset, Assets } from '../../../types';
4-
import { GetPhoneProviders200Response } from 'auth0';
5+
import { isForbiddenFeatureError } from '../../utils';
56

67
export const schema = {
78
type: 'object',
@@ -42,26 +43,29 @@ export default class GuardianPhoneSelectedProviderHandler extends DefaultHandler
4243
});
4344
}
4445

45-
async getType() {
46+
async getType(): Promise<Asset | null> {
4647
// in case client version does not support the operation
4748
if (
4849
!this.client.guardian ||
4950
typeof this.client.guardian.getPhoneFactorSelectedProvider !== 'function'
5051
) {
51-
return {};
52+
return null;
5253
}
5354

5455
if (this.existing) return this.existing;
5556

5657
try {
5758
const { data } = await this.client.guardian.getPhoneFactorSelectedProvider();
5859
this.existing = data;
59-
} catch (e) {
60-
if (isFeatureUnavailableError(e)) {
60+
} catch (err) {
61+
if (isFeatureUnavailableError(err)) {
6162
// Gracefully skip processing this configuration value.
62-
return {};
63+
return null;
64+
}
65+
if (isForbiddenFeatureError(err, this.type)) {
66+
return null;
6367
}
64-
throw e;
68+
throw err;
6569
}
6670

6771
return this.existing;

src/tools/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,11 @@ export const isDeprecatedError = (err: { message: string; statusCode: number }):
275275
if (!err) return false;
276276
return !!(err.statusCode === 403 || err.message?.includes('deprecated feature'));
277277
};
278+
279+
export const isForbiddenFeatureError = (err, type): boolean => {
280+
if (err.statusCode === 403) {
281+
log.warn(`${err.message};${err.errorCode ?? ''} - Skipping ${type}`);
282+
return true;
283+
}
284+
return false;
285+
};

test/tools/auth0/handlers/guardianFactorProviders.tests.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ describe('#guardianFactorProviders handler', () => {
5959
});
6060

6161
describe('#guardianFactorProviders process', () => {
62+
it('should handle forbidden error', async () => {
63+
const throwForbidden = () => {
64+
const error = new Error('Forbidden resource access');
65+
error.statusCode = 403;
66+
throw error;
67+
};
68+
69+
const auth0 = {
70+
guardian: {
71+
getPhoneFactorProviderTwilio: throwForbidden,
72+
getSmsFactorProviderTwilio: throwForbidden,
73+
getPushNotificationProviderAPNS: throwForbidden,
74+
getPushNotificationProviderSNS: throwForbidden,
75+
},
76+
pool,
77+
};
78+
79+
const handler = new guardianFactorProvidersTests.default({ client: auth0, config });
80+
const data = await handler.getType();
81+
expect(data).to.equal(null);
82+
});
83+
6284
it('should get guardianFactorProviders', async () => {
6385
const auth0 = {
6486
guardian: {

test/tools/auth0/handlers/guardianFactorTemplates.tests.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ describe('#guardianFactorTemplates handler', () => {
5656
});
5757

5858
describe('#guardianFactorTemplates process', () => {
59+
it('should handle forbidden error', async () => {
60+
const auth0 = {
61+
guardian: {
62+
getSmsFactorTemplates: () => {
63+
const error = new Error('Forbidden resource access');
64+
error.statusCode = 403;
65+
throw error;
66+
},
67+
},
68+
pool,
69+
};
70+
71+
const handler = new guardianFactorTemplatesTests.default({ client: auth0, config });
72+
const data = await handler.getType();
73+
expect(data).to.equal(null);
74+
});
75+
5976
it('should get guardianFactorTemplates', async () => {
6077
const auth0 = {
6178
guardian: {

test/tools/auth0/handlers/guardianFactors.tests.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,23 @@ describe('#guardianFactors handler', () => {
5555
});
5656

5757
describe('#guardianFactors process', () => {
58+
it('should handle forbidden error', async () => {
59+
const auth0 = {
60+
guardian: {
61+
getFactors: () => {
62+
const error = new Error('Forbidden resource access');
63+
error.statusCode = 403;
64+
throw error;
65+
},
66+
},
67+
pool,
68+
};
69+
70+
const handler = new guardianFactorsTests.default({ client: auth0, config });
71+
const data = await handler.getType();
72+
expect(data).to.equal(null);
73+
});
74+
5875
it('should get guardianFactors', async () => {
5976
const factors = [
6077
{ name: 'sms', enabled: true },

test/tools/auth0/handlers/guardianPhoneFactorMessageTypes.tests.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('#guardianPhoneFactorMessageTypes handler', () => {
1212

1313
const handler = new guardianPhoneFactorMessageTypes.default({ client: auth0 });
1414
const data = await handler.getType();
15-
expect(data).to.deep.equal({});
15+
expect(data).to.deep.equal(null);
1616
});
1717

1818
it('should support when endpoint does not exist (older installations)', async () => {
@@ -42,7 +42,7 @@ describe('#guardianPhoneFactorMessageTypes handler', () => {
4242

4343
const handler = new guardianPhoneFactorMessageTypes.default({ client: auth0 });
4444
const data = await handler.getType();
45-
expect(data).to.deep.equal({});
45+
expect(data).to.deep.equal(null);
4646
});
4747

4848
it('should support when endpoint is disabled for tenant', async () => {
@@ -73,7 +73,7 @@ describe('#guardianPhoneFactorMessageTypes handler', () => {
7373

7474
const handler = new guardianPhoneFactorMessageTypes.default({ client: auth0 });
7575
const data = await handler.getType();
76-
expect(data).to.deep.equal({});
76+
expect(data).to.deep.equal(null);
7777
});
7878

7979
it('should get guardian phone factor message types', async () => {
@@ -140,7 +140,7 @@ describe('#guardianPhoneFactorMessageTypes handler', () => {
140140
const handler = new guardianPhoneFactorMessageTypes.default({ client: auth0 });
141141
const stageFn = Object.getPrototypeOf(handler).processChanges;
142142

143-
await stageFn.apply(handler, [{ guardianPhoneFactorMessageTypes: {} }]);
143+
await stageFn.apply(handler, [{ guardianPhoneFactorMessageTypes: null }]);
144144
});
145145
});
146146
});

0 commit comments

Comments
 (0)