Skip to content

Commit fce6458

Browse files
Extensibility as custom phone provider deploy cli support (#1038)
Extensibility as custom phone provider deploy cli support --------- Co-authored-by: Kushal <[email protected]>
1 parent f306cc9 commit fce6458

25 files changed

+6597
-5490
lines changed

docs/configuring-the-deploy-cli.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Boolean. When enabled, will allow the tool to delete resources. Default: `false`
8888

8989
### `AUTH0_EXCLUDED`
9090

91-
Array of strings. Excludes entire resource types from being managed, bi-directionally. See also: [excluding resources from management](excluding-from-management.md). Possible values: `actions`, `attackProtection`, `branding`, `clientGrants`, `clients`, `connections`, `customDomains`, `databases`, `emailProvider`, `emailTemplates`, `guardianFactorProviders`, `guardianFactorTemplates`, `guardianFactors`, `guardianPhoneFactorMessageTypes`, `guardianPhoneFactorSelectedProvider`, `guardianPolicies`, `logStreams`, `migrations`, `organizations`, `pages`, `prompts`, `resourceServers`, `roles`, `tenant`, `triggers`, `selfServiceProfiles`.
91+
Array of strings. Excludes entire resource types from being managed, bi-directionally. See also: [excluding resources from management](excluding-from-management.md). Possible values: `actions`, `attackProtection`, `branding`, `clientGrants`, `clients`, `connections`, `customDomains`, `databases`, `emailProvider`, `phoneProviders`, `emailTemplates`, `guardianFactorProviders`, `guardianFactorTemplates`, `guardianFactors`, `guardianPhoneFactorMessageTypes`, `guardianPhoneFactorSelectedProvider`, `guardianPolicies`, `logStreams`, `migrations`, `organizations`, `pages`, `prompts`, `resourceServers`, `roles`, `tenant`, `triggers`, `selfServiceProfiles`.
9292

9393
Cannot be used simultaneously with `AUTH0_INCLUDED_ONLY`.
9494

@@ -102,7 +102,7 @@ Cannot be used simultaneously with `AUTH0_INCLUDED_ONLY`.
102102

103103
### `AUTH0_INCLUDED_ONLY`
104104

105-
Array of strings. Dictates which resource types to _only_ manage, bi-directionally. See also: [excluding resources from management](excluding-from-management.md). Possible values: `actions`, `attackProtection`, `branding`, `clientGrants`, `clients`, `connections`, `customDomains`, `databases`, `emailProvider`, `emailTemplates`, `guardianFactorProviders`, `guardianFactorTemplates`, `guardianFactors`, `guardianPhoneFactorMessageTypes`, `guardianPhoneFactorSelectedProvider`, `guardianPolicies`, `logStreams`, `migrations`, `organizations`, `pages`, `prompts`, `resourceServers`, `roles`, `tenant`, `triggers`, `selfServiceProfiles`.
105+
Array of strings. Dictates which resource types to _only_ manage, bi-directionally. See also: [excluding resources from management](excluding-from-management.md). Possible values: `actions`, `attackProtection`, `branding`, `clientGrants`, `clients`, `connections`, `customDomains`, `databases`, `emailProvider`, `phoneProviders`, `emailTemplates`, `guardianFactorProviders`, `guardianFactorTemplates`, `guardianFactors`, `guardianPhoneFactorMessageTypes`, `guardianPhoneFactorSelectedProvider`, `guardianPolicies`, `logStreams`, `migrations`, `organizations`, `pages`, `prompts`, `resourceServers`, `roles`, `tenant`, `triggers`, `selfServiceProfiles`.
106106

107107
#### Example
108108

docs/resource-specific-documentation.md

+47
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,50 @@ For `universal_login` template `templates/` will be created.
434434
"body": "./universal_login.html"
435435
}
436436
```
437+
438+
## PhoneProviders
439+
440+
When managing phone providers, credentials are never exported.
441+
442+
For the Twilio `phoneProvider`, we add the placeholder `##TWILIO_AUTH_TOKEN##` for the credential's `auth_token`, which can be used with keyword replacement.
443+
444+
Refer to [keyword-replacement.md](keyword-replacement.md), [multi-environment-workflow.md](multi-environment-workflow.md), and the [Management API](https://auth0.com/docs/api/management/v2/branding/create-phone-provider) for more details.
445+
446+
447+
**YAML Example**
448+
449+
```yaml
450+
# Contents of ./tenant.yaml
451+
phoneProviders:
452+
- name: twilio
453+
configuration:
454+
sid: "twilio_sid"
455+
default_from: "+1234567890"
456+
delivery_methods:
457+
- text
458+
- voice
459+
disabled: false
460+
credentials:
461+
auth_token: '##TWILIO_AUTH_TOKEN##'
462+
```
463+
464+
**Directory Example**
465+
466+
```json
467+
[
468+
{
469+
"name": "twilio",
470+
"disabled": true,
471+
"configuration": {
472+
"sid": "twilio_sid",
473+
"default_from": "+1234567890",
474+
"delivery_methods": [
475+
"text", "voice"
476+
]
477+
},
478+
"credentials": {
479+
"auth_token": "##TWILIO_AUTH_TOKEN##"
480+
}
481+
}
482+
]
483+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[
2+
{
3+
"name": "twilio",
4+
"disabled": true,
5+
"configuration": {
6+
"sid": "twilio_sid",
7+
"default_from": "+1234567890",
8+
"delivery_methods": [
9+
"text", "voice"
10+
]
11+
},
12+
"credentials": {
13+
"auth_token": "some_auth_token"
14+
}
15+
}
16+
]

examples/yaml/tenant.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ resourceServers:
8383
description: "read account"
8484
# Add other resource server settings (https://auth0.com/docs/api/management/v2#!/Resource_Servers/post_resource_servers)
8585

86+
phoneProviders:
87+
- name: twilio
88+
configuration:
89+
sid: "twilio_sid"
90+
default_from: "+1234567890"
91+
delivery_methods:
92+
- text
93+
- voice
94+
disabled: false
95+
credentials:
96+
auth_token: "some_auth_token"
8697

8798
emailProvider:
8899
name: "smtp"

package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"homepage": "https://github.com/auth0/auth0-deploy-cli#readme",
3434
"dependencies": {
3535
"ajv": "^6.12.6",
36-
"auth0": "^4.18.0",
36+
"auth0": "^4.19.0",
3737
"dot-prop": "^5.2.0",
3838
"fs-extra": "^10.1.0",
3939
"js-yaml": "^4.1.0",

src/context/defaults.ts

+23
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,26 @@ export function emailProviderDefaults(emailProvider) {
5252

5353
return updated;
5454
}
55+
56+
export function phoneProviderDefaults(phoneProvider) {
57+
const updated = { ...phoneProvider };
58+
59+
const removeKeysFromOutput = ['id', 'created_at', 'updated_at', 'channel', 'tenant', 'credentials'];
60+
removeKeysFromOutput.forEach((key) => {
61+
if (key in updated) {
62+
delete updated[key];
63+
}
64+
});
65+
66+
const apiKeyProviders = ['twilio'];
67+
68+
// Add placeholder for credentials as they cannot be exported
69+
const { name } = updated;
70+
71+
if (apiKeyProviders.includes(name)) {
72+
updated.credentials = {
73+
auth_token: `##${name.toUpperCase()}_AUTH_TOKEN##`,
74+
};
75+
}
76+
return updated;
77+
}

src/context/directory/handlers/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import organizations from './organizations';
1919
import triggers from './triggers';
2020
import attackProtection from './attackProtection';
2121
import branding from './branding';
22+
import phoneProviders from './phoneProvider';
2223
import logStreams from './logStreams';
2324
import prompts from './prompts';
2425
import customDomains from './customDomains';
@@ -66,6 +67,7 @@ const directoryHandlers: {
6667
triggers,
6768
attackProtection,
6869
branding,
70+
phoneProviders,
6971
logStreams,
7072
prompts,
7173
customDomains,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import path from 'path';
2+
import fs from 'fs-extra';
3+
import { constants } from '../../../tools';
4+
5+
import { existsMustBeDir, isFile, dumpJSON, loadJSON } from '../../../utils';
6+
import { DirectoryHandler } from '.';
7+
import DirectoryContext from '..';
8+
import { ParsedAsset } from '../../../types';
9+
import { PhoneProvider } from '../../../tools/auth0/handlers/phoneProvider';
10+
import { phoneProviderDefaults } from '../../defaults';
11+
12+
type ParsedPhoneProvider = ParsedAsset<'phoneProviders', PhoneProvider[]>;
13+
14+
function parse(context: DirectoryContext): ParsedPhoneProvider {
15+
const phoneProvidersFolder = path.join(context.filePath, constants.PHONE_PROVIDER_DIRECTORY);
16+
if (!existsMustBeDir(phoneProvidersFolder)) return { phoneProviders: null }; // Skip
17+
18+
const providerFile = path.join(phoneProvidersFolder, 'provider.json');
19+
20+
if (isFile(providerFile)) {
21+
return {
22+
phoneProviders: loadJSON(providerFile, {
23+
mappings: context.mappings,
24+
disableKeywordReplacement: context.disableKeywordReplacement,
25+
}),
26+
};
27+
}
28+
29+
return { phoneProviders: null };
30+
}
31+
32+
async function dump(context: DirectoryContext): Promise<void> {
33+
let { phoneProviders } = context.assets;
34+
35+
if (!phoneProviders) {
36+
return;
37+
}// Skip, nothing to dump
38+
39+
const phoneProvidersFolder = path.join(context.filePath, constants.PHONE_PROVIDER_DIRECTORY);
40+
fs.ensureDirSync(phoneProvidersFolder);
41+
42+
const phoneProviderFile = path.join(phoneProvidersFolder, 'provider.json');
43+
44+
phoneProviders = phoneProviders.map((provider) => {
45+
provider = phoneProviderDefaults(provider);
46+
return provider;
47+
});
48+
49+
dumpJSON(phoneProviderFile, phoneProviders);
50+
}
51+
52+
const phoneProvidersHandler: DirectoryHandler<ParsedPhoneProvider> = {
53+
parse,
54+
dump,
55+
};
56+
57+
export default phoneProvidersHandler;

src/context/yaml/handlers/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import actions from './actions';
1919
import triggers from './triggers';
2020
import attackProtection from './attackProtection';
2121
import branding from './branding';
22+
import phoneProviders from './phoneProvider';
2223
import logStreams from './logStreams';
2324
import prompts from './prompts';
2425
import customDomains from './customDomains';
@@ -64,6 +65,7 @@ const yamlHandlers: { [key in AssetTypes]: YAMLHandler<{ [key: string]: unknown
6465
triggers,
6566
attackProtection,
6667
branding,
68+
phoneProviders,
6769
logStreams,
6870
prompts,
6971
customDomains,
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { YAMLHandler } from '.';
2+
import YAMLContext from '..';
3+
import { PhoneProvider } from '../../../tools/auth0/handlers/phoneProvider';
4+
import { ParsedAsset } from '../../../types';
5+
import { phoneProviderDefaults } from '../../defaults';
6+
7+
type ParsedPhoneProviders = ParsedAsset<'phoneProviders', PhoneProvider[] >;
8+
9+
async function parse(context: YAMLContext): Promise<ParsedPhoneProviders> {
10+
const { phoneProviders } = context.assets;
11+
12+
if (!phoneProviders) return { phoneProviders: null };
13+
14+
return {
15+
phoneProviders,
16+
};
17+
}
18+
19+
async function dump(context: YAMLContext): Promise<ParsedPhoneProviders> {
20+
if (!context.assets.phoneProviders) return { phoneProviders: null };
21+
22+
let { phoneProviders } = context.assets;
23+
24+
phoneProviders = phoneProviders.map((provider) => {
25+
provider = phoneProviderDefaults(provider);
26+
return provider;
27+
});
28+
29+
return {
30+
phoneProviders
31+
};
32+
}
33+
34+
const phoneProviderHandler: YAMLHandler<ParsedPhoneProviders> = {
35+
parse,
36+
dump,
37+
};
38+
39+
export default phoneProviderHandler;

src/tools/auth0/handlers/emailProvider.ts

-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { EmailProviderCreate } from 'auth0';
22
import { isEmpty } from 'lodash';
33
import DefaultHandler, { order } from './default';
44
import { Asset, Assets } from '../../../types';
5-
import log from '../../../logger';
65

76
export const schema = { type: 'object' };
87

@@ -46,7 +45,6 @@ export default class EmailProviderHandler extends DefaultHandler {
4645
if (Object.keys(emailProvider).length === 0) {
4746
if (this.config('AUTH0_ALLOW_DELETE') === true) {
4847
// await this.client.emails.delete(); is not supported
49-
existing.enabled = false;
5048
if (isEmpty(existing.credentials)) {
5149
delete existing.credentials;
5250
}
@@ -57,14 +55,6 @@ export default class EmailProviderHandler extends DefaultHandler {
5755
return;
5856
}
5957

60-
if (existing.name) {
61-
if (existing.name !== emailProvider.name) {
62-
// Delete the current provider as it's different
63-
// await this.client.emailProvider.delete(); is not supported
64-
existing.enabled = false;
65-
}
66-
}
67-
6858
if (existing.name) {
6959
const updated = await this.client.emails.update(emailProvider);
7060
this.updated += 1;

src/tools/auth0/handlers/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as guardianPhoneFactorSelectedProvider from './guardianPhoneFactorSelec
1818
import * as guardianPhoneFactorMessageTypes from './guardianPhoneFactorMessageTypes';
1919
import * as roles from './roles';
2020
import * as branding from './branding';
21+
import * as phoneProviders from './phoneProvider';
2122
import * as prompts from './prompts';
2223
import * as actions from './actions';
2324
import * as triggers from './triggers';
@@ -55,6 +56,7 @@ const auth0ApiHandlers: { [key in AssetTypes]: any } = {
5556
guardianPhoneFactorMessageTypes,
5657
roles,
5758
branding,
59+
phoneProviders,
5860
//@ts-ignore because prompts have not been universally implemented yet
5961
prompts,
6062
actions,

0 commit comments

Comments
 (0)