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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Makefile
.github/copilot-instructions.md
.github/chatmodes/*
.github/prompts/*
.github/agents/*
53 changes: 53 additions & 0 deletions docs/resource-specific-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,3 +575,56 @@ phoneProviders:
}
]
```

## PhoneTemplates

Phone templates allow you to customize the SMS and voice messages sent to users for phone-based authentication.
Refer to the [Management API](https://auth0.com/docs/api/management/v2/branding/get-phone-templates) for more details.

### YAML Example

```yaml
# Contents of ./tenant.yaml
phoneTemplates:
- type: otp_verify
disabled: false
content:
from: '+12341234567'
body:
text: 'Your verification code is {{ code }}'
voice: 'Your verification code is {{ code }}'
- type: otp_enroll
disabled: false
content:
from: '+12341234567'
body:
text: 'Your enrollment code is {{ code }}'
```

### Directory Example

Create individual JSON files for each template in the `phone-templates` directory:

```text
phone-templates/
├── otp_verify.json
├── otp_enroll.json
├── change_password.json
└── ...
```

Example `phone-templates/otp_verify.json`:

```json
{
"type": "otp_verify",
"disabled": false,
"content": {
"from": "+12341234567",
"body": {
"text": "Your verification code is {{ code }}",
"voice": "Your verification code is {{ code }}"
}
}
}
```
12 changes: 12 additions & 0 deletions examples/directory/phone-templates/otp_enroll.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "otp_enroll",
"disabled": false,
"content": {
"syntax": "liquid",
"from": "+12341234567",
"body": {
"text": "Your enrollment code is {{ code }}",
"voice": "Your enrollment code is {{ code }}"
}
}
}
12 changes: 12 additions & 0 deletions examples/directory/phone-templates/otp_verify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "otp_verify",
"disabled": false,
"content": {
"syntax": "liquid",
"from": "+12341234567",
"body": {
"text": "Your verification code is {{ code }}",
"voice": "Your verification code is {{ code }}"
}
}
}
15 changes: 15 additions & 0 deletions examples/yaml/tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ phoneProviders:
credentials:
auth_token: "some_auth_token"

phoneTemplates:
- type: otp_verify
disabled: false
content:
from: "+12341234567"
body:
text: "Your verification code is {{ code }}"
voice: "Your verification code is {{ code }}"
- type: otp_enroll
disabled: false
content:
from: "+12341234567"
body:
text: "Your enrollment code is {{ code }}"

emailProvider:
name: "smtp"
enabled: true
Expand Down
21 changes: 21 additions & 0 deletions src/context/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,27 @@ export function phoneProviderDefaults(phoneProvider) {
return updated;
}

export function phoneTemplatesDefaults(phoneTemplate) {
const updated = { ...phoneTemplate };

// Strip read-only fields that are returned by the API but should not be included in exported config
const removeKeysFromOutput = [
'id',
'channel',
'customizable',
'tenant',
'created_at',
'updated_at',
];
removeKeysFromOutput.forEach((key) => {
if (key in updated) {
delete updated[key];
}
});

return updated;
}

export function connectionDefaults(connection) {
if (connection.options) {
// Mask secret for key: connection.options.client_secret
Expand Down
2 changes: 2 additions & 0 deletions src/context/directory/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import triggers from './triggers';
import attackProtection from './attackProtection';
import branding from './branding';
import phoneProviders from './phoneProvider';
import phoneTemplates from './phoneTemplates';
import logStreams from './logStreams';
import prompts from './prompts';
import customDomains from './customDomains';
Expand Down Expand Up @@ -70,6 +71,7 @@ const directoryHandlers: {
attackProtection,
branding,
phoneProviders,
phoneTemplates,
logStreams,
prompts,
customDomains,
Expand Down
52 changes: 52 additions & 0 deletions src/context/directory/handlers/phoneTemplates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import path from 'path';
import fs from 'fs-extra';
import { constants } from '../../../tools';

import { existsMustBeDir, getFiles, dumpJSON, loadJSON } from '../../../utils';
import { DirectoryHandler } from '.';
import DirectoryContext from '..';
import { ParsedAsset } from '../../../types';
import { PhoneTemplate } from '../../../tools/auth0/handlers/phoneTemplates';
import { phoneTemplatesDefaults } from '../../defaults';

type ParsedPhoneTemplates = ParsedAsset<'phoneTemplates', PhoneTemplate[]>;

function parse(context: DirectoryContext): ParsedPhoneTemplates {
const phoneTemplatesFolder = path.join(context.filePath, constants.PHONE_TEMPLATES_DIRECTORY);
if (!existsMustBeDir(phoneTemplatesFolder)) return { phoneTemplates: null }; // Skip

const files = getFiles(phoneTemplatesFolder, ['.json']);

const phoneTemplates = files.map((f) =>
loadJSON(f, {
mappings: context.mappings,
disableKeywordReplacement: context.disableKeywordReplacement,
})
);

return { phoneTemplates };
}

async function dump(context: DirectoryContext): Promise<void> {
const { phoneTemplates } = context.assets;

if (!phoneTemplates) {
return;
} // Skip, nothing to dump

const phoneTemplatesFolder = path.join(context.filePath, constants.PHONE_TEMPLATES_DIRECTORY);
fs.ensureDirSync(phoneTemplatesFolder);

phoneTemplates.forEach((template) => {
const templateWithDefaults = phoneTemplatesDefaults(template);
const templateFile = path.join(phoneTemplatesFolder, `${template.type}.json`);
dumpJSON(templateFile, templateWithDefaults);
});
}

const phoneTemplatesHandler: DirectoryHandler<ParsedPhoneTemplates> = {
parse,
dump,
};

export default phoneTemplatesHandler;
2 changes: 2 additions & 0 deletions src/context/yaml/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import triggers from './triggers';
import attackProtection from './attackProtection';
import branding from './branding';
import phoneProviders from './phoneProvider';
import phoneTemplates from './phoneTemplates';
import logStreams from './logStreams';
import prompts from './prompts';
import customDomains from './customDomains';
Expand Down Expand Up @@ -68,6 +69,7 @@ const yamlHandlers: { [key in AssetTypes]: YAMLHandler<{ [key: string]: unknown
attackProtection,
branding,
phoneProviders,
phoneTemplates,
logStreams,
prompts,
customDomains,
Expand Down
36 changes: 36 additions & 0 deletions src/context/yaml/handlers/phoneTemplates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { YAMLHandler } from '.';
import YAMLContext from '..';
import { PhoneTemplate } from '../../../tools/auth0/handlers/phoneTemplates';
import { ParsedAsset } from '../../../types';
import { phoneTemplatesDefaults } from '../../defaults';

type ParsedPhoneTemplates = ParsedAsset<'phoneTemplates', PhoneTemplate[]>;

async function parse(context: YAMLContext): Promise<ParsedPhoneTemplates> {
const { phoneTemplates } = context.assets;

if (!phoneTemplates) return { phoneTemplates: null };

return {
phoneTemplates,
};
}

async function dump(context: YAMLContext): Promise<ParsedPhoneTemplates> {
const { phoneTemplates } = context.assets;

if (!phoneTemplates) return { phoneTemplates: null };

const processedTemplates = phoneTemplates.map((template) => phoneTemplatesDefaults(template));

return {
phoneTemplates: processedTemplates,
};
}

const phoneTemplatesHandler: YAMLHandler<ParsedPhoneTemplates> = {
parse,
dump,
};

export default phoneTemplatesHandler;
2 changes: 2 additions & 0 deletions src/tools/auth0/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as guardianPhoneFactorMessageTypes from './guardianPhoneFactorMessageTy
import * as roles from './roles';
import * as branding from './branding';
import * as phoneProviders from './phoneProvider';
import * as phoneTemplates from './phoneTemplates';
import * as prompts from './prompts';
import * as actions from './actions';
import * as triggers from './triggers';
Expand Down Expand Up @@ -59,6 +60,7 @@ const auth0ApiHandlers: { [key in AssetTypes]: any } = {
roles,
branding,
phoneProviders,
phoneTemplates,
//@ts-ignore because prompts have not been universally implemented yet
prompts,
actions,
Expand Down
Loading