auth0-deploy-cli 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -6
- package/lib/context/directory/handlers/attackProtection.js +10 -0
- package/lib/context/index.js +1 -0
- package/lib/context/yaml/handlers/attackProtection.js +4 -1
- package/lib/context/yaml/index.js +84 -3
- package/lib/tools/auth0/handlers/attackProtection.d.ts +12 -0
- package/lib/tools/auth0/handlers/attackProtection.js +47 -0
- package/lib/tools/auth0/handlers/clients.js +1 -1
- package/lib/tools/auth0/handlers/connections.d.ts +2 -0
- package/lib/tools/auth0/handlers/connections.js +26 -4
- package/lib/tools/auth0/handlers/prompts.d.ts +17 -0
- package/lib/tools/auth0/handlers/prompts.js +46 -13
- package/lib/tools/auth0/handlers/resourceServers.d.ts +4 -0
- package/lib/tools/auth0/handlers/resourceServers.js +12 -2
- package/lib/tools/auth0/handlers/roles.js +17 -9
- package/lib/tools/auth0/handlers/scimHandler.js +8 -3
- package/lib/tools/constants.d.ts +1 -0
- package/lib/tools/constants.js +5 -0
- package/lib/types.d.ts +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,13 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [9.
|
|
10
|
+
## [9.1.0] - 2026-10-07
|
|
11
11
|
|
|
12
|
-
###
|
|
12
|
+
### Added
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
- Add support for the `auth_email_by_code` email template. [#1500]
|
|
15
|
+
- Add support for phone provider protection (`phoneProviderProtection`) to the `attackProtection` resource. [#1503]
|
|
16
|
+
- Support the `require_consent_non_repudiation` resource server property and the `my-account` client `async_approval_notification_channels` value (EA). [#1508]
|
|
17
|
+
- Support `!include` directives in YAML config files, including nested includes, so a large `tenant.yaml` can be split across multiple files. [#1252]
|
|
18
|
+
- Add `AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES` config to limit which languages' prompts custom text is fetched, shortening exports on tenants with many languages enabled. Behaviour is unchanged when not set. [#1511]
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Speed up imports into large tenants by enriching connection `enabled_clients` only for connections in the local config and skipping the SCIM id map when SCIM is not enabled. [#1505]
|
|
23
|
+
- Allow system resource servers other than the Auth0 My Account API (e.g. the Auth0 My Organization API) to be updated. [#1485]
|
|
24
|
+
- Prevent roles from losing permissions on an interrupted import by diffing permissions instead of clearing and re-adding them, and retrying role updates on 429 responses. [#1510]
|
|
25
|
+
- Reduce `deploy`/`import` memory use and startup time by defining the `prompts.customText` JSON schema once and referencing it per language and prompt, instead of expanding it for every language × prompt × screen combination. [#1509]
|
|
26
|
+
|
|
27
|
+
## [9.0.0] - 2026-09-18
|
|
17
28
|
|
|
18
29
|
### Added
|
|
19
30
|
|
|
@@ -23,6 +34,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
23
34
|
- Support `enforce_permission_ceiling` and `enforce_self_assignment_restriction` on `my_organization_configuration` (EA). [#1495]
|
|
24
35
|
- Support tenants without the `riskAssessment` and `guardianPolicies` entitlement by handling `insufficient_entitlement` 403 responses gracefully instead of failing the run. [#1493]
|
|
25
36
|
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **⚠️ Breaking changes:**
|
|
40
|
+
- **Config file handlers (import): path traversal is now a hard error.** A file reference that resolves outside the config directory now aborts the import with an error instead of logging a deprecation warning (the warning shipped in 8.43.0 and 8.44.0). This applies to every file-backed field in both directory and YAML formats: action `code`, rule/hook `script`, database `customScripts`, client `custom_login_page`, connection/email-template/branding `body`, prompt partials, and flow/form bodies. Move any out-of-tree references (`../` relative paths or absolute paths) inside your config directory. Blocked references fail with `Path traversal blocked: "<input>" resolves to "<resolved>" which is outside the config directory "<root>".` before any Management API call. `a0deploy export` is unaffected. [#1498]
|
|
41
|
+
- **`AUTH0_DOMAIN` environment variable: must now be a bare host.** The `node-auth0` v7 upgrade validates the domain more strictly and rejects values that include a scheme or a trailing slash (for example `https://tenant.auth0.com/`); use a bare host such as `tenant.us.auth0.com`. This Management-API-only release also moves token acquisition to the SDK's managed auth, which auto-refreshes tokens during long-running import/export runs while preserving the previous fail-fast behavior on bad credentials. See the [node-auth0 v7 migration guide](https://github.com/auth0/node-auth0/blob/master/v7_MIGRATION_GUIDE.md). [#1491]
|
|
42
|
+
|
|
26
43
|
### Fixed
|
|
27
44
|
|
|
28
45
|
- Surface the real API error on `tokenExchangeProfiles` 403 responses. [#1492]
|
|
@@ -1898,6 +1915,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1898
1915
|
[#1242]: https://github.com/auth0/auth0-deploy-cli/issues/1242
|
|
1899
1916
|
[#1244]: https://github.com/auth0/auth0-deploy-cli/issues/1244
|
|
1900
1917
|
[#1246]: https://github.com/auth0/auth0-deploy-cli/issues/1246
|
|
1918
|
+
[#1252]: https://github.com/auth0/auth0-deploy-cli/issues/1252
|
|
1901
1919
|
[#1253]: https://github.com/auth0/auth0-deploy-cli/issues/1253
|
|
1902
1920
|
[#1261]: https://github.com/auth0/auth0-deploy-cli/issues/1261
|
|
1903
1921
|
[#1263]: https://github.com/auth0/auth0-deploy-cli/issues/1263
|
|
@@ -2000,6 +2018,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
2000
2018
|
[#1481]: https://github.com/auth0/auth0-deploy-cli/issues/1481
|
|
2001
2019
|
[#1482]: https://github.com/auth0/auth0-deploy-cli/issues/1482
|
|
2002
2020
|
[#1483]: https://github.com/auth0/auth0-deploy-cli/issues/1483
|
|
2021
|
+
[#1485]: https://github.com/auth0/auth0-deploy-cli/issues/1485
|
|
2003
2022
|
[#1486]: https://github.com/auth0/auth0-deploy-cli/issues/1486
|
|
2004
2023
|
[#1487]: https://github.com/auth0/auth0-deploy-cli/issues/1487
|
|
2005
2024
|
[#1489]: https://github.com/auth0/auth0-deploy-cli/issues/1489
|
|
@@ -2011,7 +2030,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
2011
2030
|
[#1496]: https://github.com/auth0/auth0-deploy-cli/issues/1496
|
|
2012
2031
|
[#1497]: https://github.com/auth0/auth0-deploy-cli/issues/1497
|
|
2013
2032
|
[#1498]: https://github.com/auth0/auth0-deploy-cli/issues/1498
|
|
2014
|
-
[
|
|
2033
|
+
[#1500]: https://github.com/auth0/auth0-deploy-cli/issues/1500
|
|
2034
|
+
[#1503]: https://github.com/auth0/auth0-deploy-cli/issues/1503
|
|
2035
|
+
[#1505]: https://github.com/auth0/auth0-deploy-cli/issues/1505
|
|
2036
|
+
[#1509]: https://github.com/auth0/auth0-deploy-cli/issues/1509
|
|
2037
|
+
[#1508]: https://github.com/auth0/auth0-deploy-cli/issues/1508
|
|
2038
|
+
[#1510]: https://github.com/auth0/auth0-deploy-cli/issues/1510
|
|
2039
|
+
[#1511]: https://github.com/auth0/auth0-deploy-cli/issues/1511
|
|
2040
|
+
[Unreleased]: https://github.com/auth0/auth0-deploy-cli/compare/v9.1.0...HEAD
|
|
2041
|
+
[9.1.0]: https://github.com/auth0/auth0-deploy-cli/compare/v9.0.0...v9.1.0
|
|
2015
2042
|
[9.0.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.45.0...v9.0.0
|
|
2016
2043
|
[8.45.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.44.0...v8.45.0
|
|
2017
2044
|
[8.44.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.43.0...v8.44.0
|
|
@@ -17,6 +17,7 @@ function attackProtectionFiles(filePath) {
|
|
|
17
17
|
bruteForceProtection: path_1.default.join(directory, 'brute-force-protection.json'),
|
|
18
18
|
captcha: path_1.default.join(directory, 'captcha.json'),
|
|
19
19
|
suspiciousIpThrottling: path_1.default.join(directory, 'suspicious-ip-throttling.json'),
|
|
20
|
+
phoneProviderProtection: path_1.default.join(directory, 'phone-provider-protection.json'),
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
function parse(context) {
|
|
@@ -55,6 +56,12 @@ function parse(context) {
|
|
|
55
56
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
56
57
|
});
|
|
57
58
|
}
|
|
59
|
+
if ((0, utils_1.isFile)(files.phoneProviderProtection)) {
|
|
60
|
+
attackProtection.phoneProviderProtection = (0, utils_1.loadJSON)(files.phoneProviderProtection, {
|
|
61
|
+
mappings: context.mappings,
|
|
62
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
58
65
|
return {
|
|
59
66
|
attackProtection,
|
|
60
67
|
};
|
|
@@ -81,6 +88,9 @@ async function dump(context) {
|
|
|
81
88
|
if (maskedAttackProtection.suspiciousIpThrottling) {
|
|
82
89
|
(0, utils_1.dumpJSON)(files.suspiciousIpThrottling, maskedAttackProtection.suspiciousIpThrottling);
|
|
83
90
|
}
|
|
91
|
+
if (maskedAttackProtection.phoneProviderProtection) {
|
|
92
|
+
(0, utils_1.dumpJSON)(files.phoneProviderProtection, maskedAttackProtection.phoneProviderProtection);
|
|
93
|
+
}
|
|
84
94
|
}
|
|
85
95
|
const attackProtectionHandler = {
|
|
86
96
|
parse,
|
package/lib/context/index.js
CHANGED
|
@@ -13,7 +13,7 @@ async function dump(context) {
|
|
|
13
13
|
const { attackProtection } = context.assets;
|
|
14
14
|
if (!attackProtection)
|
|
15
15
|
return { attackProtection: null };
|
|
16
|
-
const { botDetection, suspiciousIpThrottling, breachedPasswordDetection, bruteForceProtection, captcha, } = attackProtection;
|
|
16
|
+
const { botDetection, suspiciousIpThrottling, breachedPasswordDetection, bruteForceProtection, captcha, phoneProviderProtection, } = attackProtection;
|
|
17
17
|
const attackProtectionConfig = {
|
|
18
18
|
suspiciousIpThrottling,
|
|
19
19
|
breachedPasswordDetection,
|
|
@@ -25,6 +25,9 @@ async function dump(context) {
|
|
|
25
25
|
if (captcha) {
|
|
26
26
|
attackProtectionConfig.captcha = captcha;
|
|
27
27
|
}
|
|
28
|
+
if (phoneProviderProtection) {
|
|
29
|
+
attackProtectionConfig.phoneProviderProtection = phoneProviderProtection;
|
|
30
|
+
}
|
|
28
31
|
const maskedAttackProtection = (0, defaults_1.attackProtectionDefaults)(attackProtectionConfig, context.config);
|
|
29
32
|
return {
|
|
30
33
|
attackProtection: (0, defaults_1.sortAttackProtectionArrays)(maskedAttackProtection),
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
7
7
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const lodash_1 = require("lodash");
|
|
9
10
|
const tools_1 = require("../../tools");
|
|
10
11
|
const client_1 = __importDefault(require("../../tools/auth0/client"));
|
|
11
12
|
const logger_1 = __importDefault(require("../../logger"));
|
|
@@ -14,6 +15,83 @@ const handlers_1 = __importDefault(require("./handlers"));
|
|
|
14
15
|
const readonly_1 = __importDefault(require("../../readonly"));
|
|
15
16
|
const __1 = require("..");
|
|
16
17
|
const keywordPreservation_1 = require("../../keywordPreservation");
|
|
18
|
+
// Symbol marker so a literal `__include` key in user YAML isn't read as a directive
|
|
19
|
+
const INCLUDE = Symbol('include');
|
|
20
|
+
// Custom YAML type for file includes
|
|
21
|
+
const includeType = new js_yaml_1.default.Type('!include', {
|
|
22
|
+
kind: 'scalar',
|
|
23
|
+
resolve: (data) => typeof data === 'string',
|
|
24
|
+
construct: (data) => {
|
|
25
|
+
// This will be handled during the actual loading process
|
|
26
|
+
return { [INCLUDE]: data };
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
const schema = js_yaml_1.default.DEFAULT_SCHEMA.extend([includeType]);
|
|
30
|
+
function prepareYamlContent(content, mappings, disableKeywordReplacement) {
|
|
31
|
+
return disableKeywordReplacement
|
|
32
|
+
? (0, tools_1.wrapArrayReplaceMarkersInQuotes)(content, mappings)
|
|
33
|
+
: (0, tools_1.keywordReplace)(content, mappings);
|
|
34
|
+
}
|
|
35
|
+
function parseYaml(content) {
|
|
36
|
+
return js_yaml_1.default.load(content, { schema });
|
|
37
|
+
}
|
|
38
|
+
// Resolves !include directives. Keyword handling is applied by the caller before parse.
|
|
39
|
+
// configRoot is threaded unchanged so chained `../` includes can't escape it.
|
|
40
|
+
function resolveIncludes(obj, basePath, configRoot, loadIncludedYaml, visitedFiles = new Set()) {
|
|
41
|
+
if (Array.isArray(obj)) {
|
|
42
|
+
return obj.map((item) => resolveIncludes(item, basePath, configRoot, loadIncludedYaml, visitedFiles));
|
|
43
|
+
}
|
|
44
|
+
if (obj && typeof obj === 'object') {
|
|
45
|
+
const includePath = obj[INCLUDE];
|
|
46
|
+
if (includePath !== undefined) {
|
|
47
|
+
const filePath = path_1.default.resolve(basePath, includePath);
|
|
48
|
+
(0, utils_1.assertInsideConfigRoot)(includePath, filePath, configRoot);
|
|
49
|
+
if (visitedFiles.has(filePath)) {
|
|
50
|
+
throw new Error(`Circular include detected: ${filePath}`);
|
|
51
|
+
}
|
|
52
|
+
let stat;
|
|
53
|
+
try {
|
|
54
|
+
stat = fs_extra_1.default.statSync(filePath);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err.code === 'ENOENT') {
|
|
58
|
+
throw new Error(`Include file not found: ${filePath}`);
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
if (stat.isDirectory()) {
|
|
63
|
+
throw new Error(`Include path is a directory, expected a file: ${filePath}`);
|
|
64
|
+
}
|
|
65
|
+
// visitedFiles backtracks (add before, delete after), so one shared set is enough
|
|
66
|
+
visitedFiles.add(filePath);
|
|
67
|
+
const result = resolveIncludes(loadIncludedYaml(filePath), path_1.default.dirname(filePath), configRoot, loadIncludedYaml, visitedFiles);
|
|
68
|
+
visitedFiles.delete(filePath);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
// Only rebuild plain objects; pass others (e.g. a Date from a YAML timestamp) through untouched
|
|
72
|
+
if (!(0, lodash_1.isPlainObject)(obj)) {
|
|
73
|
+
return obj;
|
|
74
|
+
}
|
|
75
|
+
const result = {};
|
|
76
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
77
|
+
const resolved = resolveIncludes(value, basePath, configRoot, loadIncludedYaml, visitedFiles);
|
|
78
|
+
// Define `__proto__` as an own property so an included file can't pollute the prototype
|
|
79
|
+
if (key === '__proto__') {
|
|
80
|
+
Object.defineProperty(result, key, {
|
|
81
|
+
value: resolved,
|
|
82
|
+
enumerable: true,
|
|
83
|
+
writable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
result[key] = resolved;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
return obj;
|
|
94
|
+
}
|
|
17
95
|
class YAMLContext {
|
|
18
96
|
constructor(config, mgmtClient) {
|
|
19
97
|
this.configFile = config.AUTH0_INPUT_FILE;
|
|
@@ -61,9 +139,12 @@ class YAMLContext {
|
|
|
61
139
|
try {
|
|
62
140
|
const fPath = path_1.default.resolve(this.configFile);
|
|
63
141
|
logger_1.default.debug(`Loading YAML from ${fPath}`);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
142
|
+
const prepareContent = (content) => prepareYamlContent(content, this.mappings, opts.disableKeywordReplacement);
|
|
143
|
+
const loadIncludedYaml = (filePath) => parseYaml(prepareContent(fs_extra_1.default.readFileSync(filePath, 'utf8')));
|
|
144
|
+
const parsed = parseYaml(prepareContent(fs_extra_1.default.readFileSync(fPath, 'utf8')));
|
|
145
|
+
// Bound includes by the same config root loadFile() enforces
|
|
146
|
+
const configRoot = path_1.default.resolve(this.basePath);
|
|
147
|
+
Object.assign(this.assets, resolveIncludes(parsed, path_1.default.dirname(fPath), configRoot, loadIncludedYaml) || {});
|
|
67
148
|
}
|
|
68
149
|
catch (err) {
|
|
69
150
|
logger_1.default.debug(err.stack);
|
|
@@ -165,6 +165,17 @@ export declare const schema: {
|
|
|
165
165
|
suspiciousIpThrottling: {
|
|
166
166
|
type: string;
|
|
167
167
|
};
|
|
168
|
+
phoneProviderProtection: {
|
|
169
|
+
type: string;
|
|
170
|
+
properties: {
|
|
171
|
+
type: {
|
|
172
|
+
type: string;
|
|
173
|
+
enum: string[];
|
|
174
|
+
description: string;
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
required: string[];
|
|
178
|
+
};
|
|
168
179
|
};
|
|
169
180
|
additionalProperties: boolean;
|
|
170
181
|
};
|
|
@@ -174,6 +185,7 @@ export type AttackProtection = {
|
|
|
174
185
|
bruteForceProtection: Asset;
|
|
175
186
|
captcha?: Asset | null;
|
|
176
187
|
suspiciousIpThrottling: Asset;
|
|
188
|
+
phoneProviderProtection?: Asset | null;
|
|
177
189
|
};
|
|
178
190
|
export default class AttackProtectionHandler extends DefaultAPIHandler {
|
|
179
191
|
existing: AttackProtection | null;
|
|
@@ -180,6 +180,17 @@ exports.schema = {
|
|
|
180
180
|
suspiciousIpThrottling: {
|
|
181
181
|
type: 'object',
|
|
182
182
|
},
|
|
183
|
+
phoneProviderProtection: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
type: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
enum: ['exponential', 'default'],
|
|
189
|
+
description: 'The backoff strategy applied to Send SMS during MFA enrollment. Gated by the sms_exponential_backoff feature flag; when the flag is disabled the API returns 403 and this setting is skipped on export.',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
required: ['type'],
|
|
193
|
+
},
|
|
183
194
|
},
|
|
184
195
|
additionalProperties: false,
|
|
185
196
|
};
|
|
@@ -227,6 +238,11 @@ class AttackProtectionHandler extends default_1.default {
|
|
|
227
238
|
enabled: item.suspiciousIpThrottling.enabled,
|
|
228
239
|
};
|
|
229
240
|
}
|
|
241
|
+
if (item.phoneProviderProtection?.type) {
|
|
242
|
+
obj['phone-provider-protection'] = {
|
|
243
|
+
type: item.phoneProviderProtection.type,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
230
246
|
return obj;
|
|
231
247
|
})();
|
|
232
248
|
return super.objString(objectString);
|
|
@@ -242,6 +258,7 @@ class AttackProtectionHandler extends default_1.default {
|
|
|
242
258
|
]);
|
|
243
259
|
let botDetection = null;
|
|
244
260
|
let captcha = null;
|
|
261
|
+
let phoneProviderProtection = null;
|
|
245
262
|
try {
|
|
246
263
|
botDetection = await this.client.attackProtection.botDetection.get();
|
|
247
264
|
}
|
|
@@ -264,6 +281,17 @@ class AttackProtectionHandler extends default_1.default {
|
|
|
264
281
|
throw err;
|
|
265
282
|
}
|
|
266
283
|
}
|
|
284
|
+
try {
|
|
285
|
+
phoneProviderProtection = await this.client.attackProtection.phoneProviderProtection.get();
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
if (err.statusCode === 403) {
|
|
289
|
+
logger_1.default.warn('Phone provider protection is not enabled for this tenant (sms_exponential_backoff feature flag). Skipping phone provider protection settings.');
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
267
295
|
const attackProtection = {
|
|
268
296
|
breachedPasswordDetection: breachedPasswordDetection,
|
|
269
297
|
bruteForceProtection: bruteForceProtection,
|
|
@@ -275,6 +303,9 @@ class AttackProtectionHandler extends default_1.default {
|
|
|
275
303
|
if (captcha) {
|
|
276
304
|
attackProtection.captcha = captcha;
|
|
277
305
|
}
|
|
306
|
+
if (phoneProviderProtection) {
|
|
307
|
+
attackProtection.phoneProviderProtection = phoneProviderProtection;
|
|
308
|
+
}
|
|
278
309
|
this.existing = attackProtection;
|
|
279
310
|
return this.existing;
|
|
280
311
|
}
|
|
@@ -321,6 +352,22 @@ class AttackProtectionHandler extends default_1.default {
|
|
|
321
352
|
if (attackProtection.suspiciousIpThrottling) {
|
|
322
353
|
updates.push(this.client.attackProtection.suspiciousIpThrottling.update(attackProtection.suspiciousIpThrottling));
|
|
323
354
|
}
|
|
355
|
+
// Phone provider protection uses PATCH (not update) and requires `type` on every request.
|
|
356
|
+
// Guard the 403 (sms_exponential_backoff flag off) per-promise so a flag-off tenant does
|
|
357
|
+
// not fail the whole attackProtection stage — mirrors the 403-skip in getType().
|
|
358
|
+
if (attackProtection.phoneProviderProtection?.type) {
|
|
359
|
+
updates.push(this.client.attackProtection.phoneProviderProtection
|
|
360
|
+
.patch({
|
|
361
|
+
type: attackProtection.phoneProviderProtection.type,
|
|
362
|
+
})
|
|
363
|
+
.catch((err) => {
|
|
364
|
+
if (err.statusCode === 403) {
|
|
365
|
+
logger_1.default.warn('Phone provider protection is not enabled for this tenant (sms_exponential_backoff feature flag). Skipping phone provider protection update.');
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
throw err;
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
324
371
|
if (!updates.length) {
|
|
325
372
|
return;
|
|
326
373
|
}
|
|
@@ -354,7 +354,7 @@ exports.schema = {
|
|
|
354
354
|
description: 'An ordered array of notification channels enabled for CIBA (Client-Initiated Backchannel Authentication) requests. Channels are evaluated in the order specified.',
|
|
355
355
|
items: {
|
|
356
356
|
type: 'string',
|
|
357
|
-
enum: ['guardian-push', 'email'],
|
|
357
|
+
enum: ['guardian-push', 'email', 'my-account'],
|
|
358
358
|
},
|
|
359
359
|
},
|
|
360
360
|
skip_non_verifiable_callback_uri_confirmation_prompt: {
|
|
@@ -230,6 +230,7 @@ export declare const processConnectionEnabledClients: (auth0Client: Auth0APIClie
|
|
|
230
230
|
export default class ConnectionsHandler extends DefaultAPIHandler {
|
|
231
231
|
existing: Connection[] | null;
|
|
232
232
|
scimHandler: ScimHandler;
|
|
233
|
+
private enabledClientsEnrichmentFilter;
|
|
233
234
|
constructor(config: DefaultAPIHandler);
|
|
234
235
|
objString(connection: any): string;
|
|
235
236
|
getFormattedOptions(connection: any, clients: any): {
|
|
@@ -270,6 +271,7 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
|
|
|
270
271
|
* @param changes - Object containing arrays of connections to create, update, and resolve conflicts for
|
|
271
272
|
*/
|
|
272
273
|
processConnectionDirectoryProvisioning(changes: CalculatedChanges): Promise<void>;
|
|
274
|
+
private setEnabledClientsEnrichmentFilter;
|
|
273
275
|
getType(): Promise<Asset[] | null>;
|
|
274
276
|
calcChanges(assets: Assets): Promise<CalculatedChanges>;
|
|
275
277
|
dryRunChanges(assets: Assets): Promise<CalculatedChanges>;
|
|
@@ -372,6 +372,10 @@ class ConnectionsHandler extends default_1.default {
|
|
|
372
372
|
create: async (bodyParams) => this.scimHandler.createOverride(bodyParams),
|
|
373
373
|
},
|
|
374
374
|
});
|
|
375
|
+
// On import, limits getType()'s enabled_clients enrichment to connections in
|
|
376
|
+
// the local config, skipping an API call per remote-only connection on large
|
|
377
|
+
// tenants. Null on export, where every connection is enriched.
|
|
378
|
+
this.enabledClientsEnrichmentFilter = null;
|
|
375
379
|
// @ts-ignore
|
|
376
380
|
this.scimHandler = new scimHandler_1.default(this.config, this.client.connections, this.client.pool);
|
|
377
381
|
}
|
|
@@ -621,9 +625,19 @@ class ConnectionsHandler extends default_1.default {
|
|
|
621
625
|
})
|
|
622
626
|
.promise();
|
|
623
627
|
}
|
|
628
|
+
// Scopes getType()'s enabled_clients enrichment to the local config's
|
|
629
|
+
// connection names. Called on import; export leaves it unset (enrich all).
|
|
630
|
+
setEnabledClientsEnrichmentFilter(connections) {
|
|
631
|
+
this.enabledClientsEnrichmentFilter = new Set(connections
|
|
632
|
+
.map((connection) => connection.name)
|
|
633
|
+
.filter((name) => typeof name === 'string'));
|
|
634
|
+
}
|
|
624
635
|
async getType() {
|
|
625
636
|
if (this.existing)
|
|
626
637
|
return this.existing;
|
|
638
|
+
// Consume the import filter and reset it so it can't leak into a later call.
|
|
639
|
+
const enrichmentFilter = this.enabledClientsEnrichmentFilter;
|
|
640
|
+
this.enabledClientsEnrichmentFilter = null;
|
|
627
641
|
const [connections, directoryProvisioningConfigs] = await Promise.all([
|
|
628
642
|
(0, client_1.paginate)(this.client.connections.list, {
|
|
629
643
|
checkpoint: true,
|
|
@@ -640,7 +654,11 @@ class ConnectionsHandler extends default_1.default {
|
|
|
640
654
|
this.existing = filteredConnections;
|
|
641
655
|
if (this.existing === null)
|
|
642
656
|
return [];
|
|
643
|
-
|
|
657
|
+
// Import: enrich only local-config connections. Export (null): enrich all.
|
|
658
|
+
const shouldEnrich = (con) => enrichmentFilter === null || (con.name != null && enrichmentFilter.has(con.name));
|
|
659
|
+
const connectionTasks = filteredConnections
|
|
660
|
+
.map((con, index) => ({ con, index }))
|
|
661
|
+
.filter(({ con }) => shouldEnrich(con));
|
|
644
662
|
const connectionsWithEnabledClients = await this.client.pool
|
|
645
663
|
.addEachTask({
|
|
646
664
|
data: connectionTasks,
|
|
@@ -676,9 +694,9 @@ class ConnectionsHandler extends default_1.default {
|
|
|
676
694
|
},
|
|
677
695
|
})
|
|
678
696
|
.promise();
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
697
|
+
// Merge enriched connections back by index; skipped ones pass through.
|
|
698
|
+
const enrichedByIndex = new Map(connectionsWithEnabledClients.map(({ index, connection }) => [index, connection]));
|
|
699
|
+
this.existing = filteredConnections.map((con, index) => enrichedByIndex.get(index) ?? con);
|
|
682
700
|
// Apply `scim_configuration` to all the relevant `SCIM` connections. This method mutates `this.existing`.
|
|
683
701
|
await this.scimHandler.applyScimConfiguration(this.existing);
|
|
684
702
|
return this.existing;
|
|
@@ -693,6 +711,8 @@ class ConnectionsHandler extends default_1.default {
|
|
|
693
711
|
update: [],
|
|
694
712
|
conflicts: [],
|
|
695
713
|
};
|
|
714
|
+
// Scope getType() enrichment to the local config (see getType()).
|
|
715
|
+
this.setEnabledClientsEnrichmentFilter(connections);
|
|
696
716
|
// Convert enabled_clients by name to the id
|
|
697
717
|
const clients = await (0, client_1.paginate)(this.client.clients.list, {
|
|
698
718
|
paginate: true,
|
|
@@ -730,6 +750,8 @@ class ConnectionsHandler extends default_1.default {
|
|
|
730
750
|
conflicts: [],
|
|
731
751
|
};
|
|
732
752
|
}
|
|
753
|
+
// Scope getType() enrichment to the local config (see getType()).
|
|
754
|
+
this.setEnabledClientsEnrichmentFilter(connections);
|
|
733
755
|
const clients = await (0, client_1.paginate)(this.client.clients.list, {
|
|
734
756
|
paginate: true,
|
|
735
757
|
include_totals: true,
|
|
@@ -47,7 +47,18 @@ export declare const schema: {
|
|
|
47
47
|
type: string;
|
|
48
48
|
};
|
|
49
49
|
customText: {
|
|
50
|
+
$id: string;
|
|
50
51
|
type: string;
|
|
52
|
+
definitions: {
|
|
53
|
+
promptType: {
|
|
54
|
+
type: string;
|
|
55
|
+
properties: {};
|
|
56
|
+
};
|
|
57
|
+
language: {
|
|
58
|
+
type: string;
|
|
59
|
+
properties: {};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
51
62
|
properties: {};
|
|
52
63
|
};
|
|
53
64
|
partials: {
|
|
@@ -87,6 +98,12 @@ export default class PromptsHandler extends DefaultHandler {
|
|
|
87
98
|
constructor(options: DefaultHandler);
|
|
88
99
|
objString({ customText, screenRenderers }: Prompts): string;
|
|
89
100
|
getType(): Promise<Prompts | null>;
|
|
101
|
+
/**
|
|
102
|
+
* Narrows the tenant's enabled languages to those listed in AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES.
|
|
103
|
+
* Custom text is fetched with one rate-limited request per language and prompt type, so
|
|
104
|
+
* limiting languages is the only way to shorten this step on tenants with many languages.
|
|
105
|
+
*/
|
|
106
|
+
getCustomTextLanguages<T extends string>(enabledLanguages: T[]): T[];
|
|
90
107
|
getCustomTextSettings(): Promise<AllPromptsByLanguage>;
|
|
91
108
|
/**
|
|
92
109
|
* Error handler wrapper.
|
|
@@ -199,25 +199,35 @@ exports.schema = {
|
|
|
199
199
|
identifier_first: {
|
|
200
200
|
type: 'boolean',
|
|
201
201
|
},
|
|
202
|
+
// Every language/prompt shares the same shape, so the per-prompt and per-language schemas are
|
|
203
|
+
// defined once and referenced. Inlining them multiplies the schema by
|
|
204
|
+
// languages x promptTypes x screenTypes (~184k subschemas), which costs ~1 GB and ~2.5s of
|
|
205
|
+
// Ajv compile time on every `deploy`. The `$id` scopes the `#/definitions/...` refs to this
|
|
206
|
+
// subschema so they resolve wherever it is embedded.
|
|
202
207
|
customText: {
|
|
208
|
+
$id: 'prompts-custom-text',
|
|
203
209
|
type: 'object',
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
[language]: {
|
|
210
|
+
definitions: {
|
|
211
|
+
promptType: {
|
|
207
212
|
type: 'object',
|
|
208
|
-
properties:
|
|
209
|
-
...
|
|
210
|
-
[
|
|
213
|
+
properties: screenTypes.reduce((screenAcc, screenType) => ({
|
|
214
|
+
...screenAcc,
|
|
215
|
+
[screenType]: {
|
|
211
216
|
type: 'object',
|
|
212
|
-
properties: screenTypes.reduce((screenAcc, screenType) => ({
|
|
213
|
-
...screenAcc,
|
|
214
|
-
[screenType]: {
|
|
215
|
-
type: 'object',
|
|
216
|
-
},
|
|
217
|
-
}), {}),
|
|
218
217
|
},
|
|
219
218
|
}), {}),
|
|
220
219
|
},
|
|
220
|
+
language: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: promptTypes.reduce((promptAcc, promptType) => ({
|
|
223
|
+
...promptAcc,
|
|
224
|
+
[promptType]: { $ref: '#/definitions/promptType' },
|
|
225
|
+
}), {}),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
properties: types_1.languages.reduce((acc, language) => ({
|
|
229
|
+
...acc,
|
|
230
|
+
[language]: { $ref: '#/definitions/language' },
|
|
221
231
|
}), {}),
|
|
222
232
|
},
|
|
223
233
|
partials: {
|
|
@@ -306,12 +316,35 @@ class PromptsHandler extends default_1.default {
|
|
|
306
316
|
}
|
|
307
317
|
return prompts;
|
|
308
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Narrows the tenant's enabled languages to those listed in AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES.
|
|
321
|
+
* Custom text is fetched with one rate-limited request per language and prompt type, so
|
|
322
|
+
* limiting languages is the only way to shorten this step on tenants with many languages.
|
|
323
|
+
*/
|
|
324
|
+
getCustomTextLanguages(enabledLanguages) {
|
|
325
|
+
const configuredLanguages = this.config('AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES');
|
|
326
|
+
if (configuredLanguages === undefined)
|
|
327
|
+
return enabledLanguages;
|
|
328
|
+
if (!Array.isArray(configuredLanguages) ||
|
|
329
|
+
!configuredLanguages.every((language) => typeof language === 'string')) {
|
|
330
|
+
throw new Error('AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES must be an array of language codes, e.g. ["en", "fr"].');
|
|
331
|
+
}
|
|
332
|
+
const notEnabledLanguages = configuredLanguages.filter((language) => !enabledLanguages.includes(language));
|
|
333
|
+
if (notEnabledLanguages.length > 0) {
|
|
334
|
+
logger_1.default.warn(`Skipping custom text for languages listed in AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES that are not enabled on the tenant: ${notEnabledLanguages.join(', ')}`);
|
|
335
|
+
}
|
|
336
|
+
const languages = enabledLanguages.filter((language) => configuredLanguages.includes(language));
|
|
337
|
+
logger_1.default.info(`Retrieving prompts custom text only for languages in AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES: ${languages.join(', ') || '(none)'}`);
|
|
338
|
+
return languages;
|
|
339
|
+
}
|
|
309
340
|
async getCustomTextSettings() {
|
|
310
|
-
const
|
|
341
|
+
const enabledLanguages = await this.client.tenants.settings.get().then((res) => {
|
|
311
342
|
if (res.enabled_locales === undefined)
|
|
312
343
|
return []; // In rare cases, private cloud tenants may not have `enabled_locales` defined
|
|
313
344
|
return res.enabled_locales;
|
|
314
345
|
});
|
|
346
|
+
const supportedLanguages = this.getCustomTextLanguages(enabledLanguages);
|
|
347
|
+
logger_1.default.debug(`Fetching prompts custom text for ${supportedLanguages.length} language(s) x ${promptTypes.length} prompt type(s) (${supportedLanguages.length * promptTypes.length} requests). This endpoint is rate-limited by Auth0, so this may take a while on tenants with many languages enabled; set AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES to limit which languages are fetched.`);
|
|
315
348
|
return this.client.pool
|
|
316
349
|
.addEachTask({
|
|
317
350
|
data: supportedLanguages
|
|
@@ -124,6 +124,10 @@ exports.schema = {
|
|
|
124
124
|
description: 'The client ID of the client that this resource server is linked to (readonly)',
|
|
125
125
|
readOnly: true,
|
|
126
126
|
},
|
|
127
|
+
require_consent_non_repudiation: {
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
description: 'When true, the authorization server rejects consent decisions that do not include a valid signature and kid. Requires the my_account_consents entitlement.',
|
|
130
|
+
},
|
|
127
131
|
},
|
|
128
132
|
required: ['name', 'identifier'],
|
|
129
133
|
},
|
|
@@ -164,6 +168,7 @@ class ResourceServersHandler extends default_1.default {
|
|
|
164
168
|
'id',
|
|
165
169
|
'is_system',
|
|
166
170
|
'authorization_policy',
|
|
171
|
+
'require_consent_non_repudiation',
|
|
167
172
|
];
|
|
168
173
|
const sanitized = {};
|
|
169
174
|
allowedKeys.forEach((key) => {
|
|
@@ -208,14 +213,19 @@ class ResourceServersHandler extends default_1.default {
|
|
|
208
213
|
});
|
|
209
214
|
}
|
|
210
215
|
async updateResourceServer(id, update) {
|
|
211
|
-
// Exclude name from update as it cannot be modified for system resource servers like
|
|
212
|
-
|
|
216
|
+
// Exclude name from update as it cannot be modified for system resource servers like the
|
|
217
|
+
// Auth0 My Account API or the Auth0 My Organization API. `is_system` is listed in
|
|
218
|
+
// `stripUpdateFields`, so it has already been removed from `update` by the time this runs -
|
|
219
|
+
// read it off the existing resource server instead, which `getType()` retains it on.
|
|
220
|
+
const existing = this.existing?.find((resourceServer) => resourceServer.id === id);
|
|
221
|
+
if (existing?.is_system === true) {
|
|
213
222
|
const updateFields = {
|
|
214
223
|
token_lifetime: update.token_lifetime,
|
|
215
224
|
proof_of_possession: update.proof_of_possession,
|
|
216
225
|
skip_consent_for_verifiable_first_party_clients: update.skip_consent_for_verifiable_first_party_clients,
|
|
217
226
|
subject_type_authorization: update.subject_type_authorization,
|
|
218
227
|
authorization_policy: update.authorization_policy,
|
|
228
|
+
require_consent_non_repudiation: update.require_consent_non_repudiation,
|
|
219
229
|
};
|
|
220
230
|
return this.client.resourceServers.update(id, updateFields);
|
|
221
231
|
}
|
|
@@ -132,22 +132,30 @@ class RolesHandler extends default_1.default {
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
async updateRole(data, roles) {
|
|
135
|
-
|
|
135
|
+
// Match on id first so a renamed role (matched by id, new name) still resolves to its
|
|
136
|
+
// existing permissions; fall back to name for configs imported without ids.
|
|
137
|
+
const existingRole = roles.find((r) => r.id === data.id) || roles.find((r) => r.name === data.name);
|
|
136
138
|
const params = { id: data.id };
|
|
137
|
-
const newPermissions = data.permissions;
|
|
139
|
+
const newPermissions = data.permissions || [];
|
|
138
140
|
delete data.permissions;
|
|
139
141
|
delete data.id;
|
|
140
142
|
// deploy-cli only manages tenant-level roles, so `type` is stripped on write; this
|
|
141
143
|
// lets files that already carry it (from a prior export) be imported cleanly.
|
|
142
144
|
delete data.type;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const retryConfig = this.getRetryConfig();
|
|
146
|
+
await (0, default_1.retryWithExponentialBackoff)(() => this.client.roles.update(params.id, data), retryConfig);
|
|
147
|
+
// Only touch permissions that actually changed, and add before removing so an
|
|
148
|
+
// interrupted run can never leave a role with fewer permissions than it started with.
|
|
149
|
+
const existingPermissions = existingRole.permissions || [];
|
|
150
|
+
const samePermission = (a, b) => a.permission_name === b.permission_name &&
|
|
151
|
+
a.resource_server_identifier === b.resource_server_identifier;
|
|
152
|
+
const permissionsToAdd = newPermissions.filter((p) => !existingPermissions.some((e) => samePermission(e, p)));
|
|
153
|
+
const permissionsToRemove = existingPermissions.filter((e) => !newPermissions.some((p) => samePermission(e, p)));
|
|
154
|
+
if (permissionsToAdd.length > 0) {
|
|
155
|
+
await (0, default_1.retryWithExponentialBackoff)(() => this.client.roles.permissions.add(params.id, { permissions: permissionsToAdd }), retryConfig);
|
|
148
156
|
}
|
|
149
|
-
if (
|
|
150
|
-
await this.client.roles.permissions.
|
|
157
|
+
if (permissionsToRemove.length > 0) {
|
|
158
|
+
await (0, default_1.retryWithExponentialBackoff)(() => this.client.roles.permissions.delete(params.id, { permissions: permissionsToRemove }), retryConfig);
|
|
151
159
|
}
|
|
152
160
|
return params;
|
|
153
161
|
}
|
|
@@ -39,6 +39,10 @@ class ScimHandler {
|
|
|
39
39
|
* @param connections
|
|
40
40
|
*/
|
|
41
41
|
async createIdMap(connections) {
|
|
42
|
+
// No `read:scim_config` scope means nothing to fetch. Return before clearing
|
|
43
|
+
// to keep any existing map and skip a no-op pool task per connection.
|
|
44
|
+
if (!this.scimScopes.read)
|
|
45
|
+
return;
|
|
42
46
|
this.idMap.clear();
|
|
43
47
|
const logMsg = 'Reviewing connections for SCIM support. This may take a while...';
|
|
44
48
|
if (nconf_1.default.get('AUTH0_DRY_RUN')) {
|
|
@@ -47,14 +51,15 @@ class ScimHandler {
|
|
|
47
51
|
else {
|
|
48
52
|
logger_1.default.info(logMsg);
|
|
49
53
|
}
|
|
54
|
+
// Only SCIM-capable strategies can carry a SCIM configuration; filtering here
|
|
55
|
+
// keeps non-SCIM connections out of the throttled pool.
|
|
56
|
+
const scimConnections = (connections || []).filter((connection) => this.isScimStrategy(connection.strategy));
|
|
50
57
|
await this.poolClient
|
|
51
58
|
.addEachTask({
|
|
52
|
-
data:
|
|
59
|
+
data: scimConnections,
|
|
53
60
|
generator: (connection) => {
|
|
54
61
|
if (!this.scimScopes.read)
|
|
55
62
|
return Promise.resolve(null);
|
|
56
|
-
if (!this.isScimStrategy(connection.strategy))
|
|
57
|
-
return Promise.resolve(null);
|
|
58
63
|
this.idMap.set(connection.id, { strategy: connection.strategy });
|
|
59
64
|
return this.getScimConfiguration(connection.id)
|
|
60
65
|
.then((response) => {
|
package/lib/tools/constants.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ declare const constants: {
|
|
|
35
35
|
EMAIL_MFA_OOB_CODE: string;
|
|
36
36
|
EMAIL_USER_INVITATION: string;
|
|
37
37
|
EMAIL_ASYNC_APPROVAL: string;
|
|
38
|
+
EMAIL_AUTH_BY_CODE: string;
|
|
38
39
|
GUARDIAN_DIRECTORY: string;
|
|
39
40
|
GUARDIAN_FACTORS_DIRECTORY: string;
|
|
40
41
|
GUARDIAN_PROVIDERS_DIRECTORY: string;
|
package/lib/tools/constants.js
CHANGED
|
@@ -20,6 +20,7 @@ const EMAIL_PASSWORD_RESET = 'password_reset';
|
|
|
20
20
|
const EMAIL_MFA_OOB_CODE = 'mfa_oob_code';
|
|
21
21
|
const EMAIL_USER_INVITATION = 'user_invitation';
|
|
22
22
|
const EMAIL_ASYNC_APPROVAL = 'async_approval';
|
|
23
|
+
const EMAIL_AUTH_BY_CODE = 'auth_email_by_code';
|
|
23
24
|
const UNIVERSAL_LOGIN_TEMPLATE = 'universal_login';
|
|
24
25
|
const OBFUSCATED_SECRET_VALUE = '_VALUE_NOT_SHOWN_';
|
|
25
26
|
const constants = {
|
|
@@ -56,6 +57,7 @@ const constants = {
|
|
|
56
57
|
'password_reset',
|
|
57
58
|
'user_invitation',
|
|
58
59
|
'async_approval',
|
|
60
|
+
'auth_email_by_code',
|
|
59
61
|
],
|
|
60
62
|
ACTIONS_TRIGGERS: [
|
|
61
63
|
'post-login',
|
|
@@ -84,6 +86,7 @@ const constants = {
|
|
|
84
86
|
EMAIL_MFA_OOB_CODE,
|
|
85
87
|
EMAIL_USER_INVITATION,
|
|
86
88
|
EMAIL_ASYNC_APPROVAL,
|
|
89
|
+
EMAIL_AUTH_BY_CODE,
|
|
87
90
|
GUARDIAN_DIRECTORY: 'guardian',
|
|
88
91
|
GUARDIAN_FACTORS_DIRECTORY: 'factors',
|
|
89
92
|
GUARDIAN_PROVIDERS_DIRECTORY: 'providers',
|
|
@@ -181,6 +184,8 @@ const constants = {
|
|
|
181
184
|
`${EMAIL_USER_INVITATION}.html`,
|
|
182
185
|
`${EMAIL_ASYNC_APPROVAL}.json`,
|
|
183
186
|
`${EMAIL_ASYNC_APPROVAL}.html`,
|
|
187
|
+
`${EMAIL_AUTH_BY_CODE}.json`,
|
|
188
|
+
`${EMAIL_AUTH_BY_CODE}.html`,
|
|
184
189
|
],
|
|
185
190
|
SUPPORTED_BRANDING_TEMPLATES: [UNIVERSAL_LOGIN_TEMPLATE],
|
|
186
191
|
LOG_STREAMS_DIRECTORY: 'log-streams',
|
package/lib/types.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export type Config = {
|
|
|
77
77
|
AUTH0_EXPORT_IDENTIFIERS?: boolean;
|
|
78
78
|
AUTH0_EXPORT_ORDERED?: boolean;
|
|
79
79
|
AUTH0_EXPORT_SECRETS?: boolean;
|
|
80
|
+
AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES?: string[];
|
|
80
81
|
AUTH0_CONNECTIONS_DIRECTORY?: string;
|
|
81
82
|
AUTH0_DRY_RUN?: boolean | 'preview';
|
|
82
83
|
AUTH0_DRY_RUN_INTERACTIVE?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auth0-deploy-cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "A command line tool for deploying updates to your Auth0 tenant",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@clack/prompts": "1.4.0",
|
|
37
37
|
"ajv": "^6.12.6",
|
|
38
|
-
"auth0": "^7.
|
|
38
|
+
"auth0": "^7.3.0",
|
|
39
39
|
"chalk": "5.6.2",
|
|
40
40
|
"dot-prop": "^5.3.0",
|
|
41
41
|
"fs-extra": "^10.1.0",
|