auth0-deploy-cli 8.42.0 → 8.44.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 +51 -1
- package/lib/context/defaults.d.ts +7 -0
- package/lib/context/defaults.js +74 -30
- package/lib/context/directory/handlers/actionModules.js +11 -12
- package/lib/context/directory/handlers/actions.js +11 -13
- package/lib/context/directory/handlers/attackProtection.js +1 -1
- package/lib/context/directory/handlers/branding.js +9 -1
- package/lib/context/directory/handlers/clients.js +9 -3
- package/lib/context/directory/handlers/connections.js +10 -4
- package/lib/context/directory/handlers/databases.js +4 -4
- package/lib/context/directory/handlers/emailTemplates.js +9 -0
- package/lib/context/directory/handlers/hooks.js +12 -1
- package/lib/context/directory/handlers/prompts.js +11 -3
- package/lib/context/directory/handlers/rules.js +12 -1
- package/lib/context/directory/index.js +7 -8
- package/lib/context/yaml/handlers/attackProtection.js +1 -1
- package/lib/context/yaml/handlers/branding.js +8 -1
- package/lib/context/yaml/handlers/clients.js +4 -1
- package/lib/context/yaml/handlers/connections.js +3 -1
- package/lib/context/yaml/handlers/flows.js +7 -0
- package/lib/context/yaml/handlers/forms.js +7 -0
- package/lib/context/yaml/handlers/prompts.js +7 -1
- package/lib/context/yaml/index.js +7 -8
- package/lib/readonly.js +5 -0
- package/lib/tools/auth0/handlers/attackProtection.js +3 -1
- package/lib/tools/auth0/handlers/clients.d.ts +21 -0
- package/lib/tools/auth0/handlers/clients.js +21 -0
- package/lib/tools/auth0/handlers/connectionProfiles.d.ts +27 -0
- package/lib/tools/auth0/handlers/connectionProfiles.js +30 -0
- package/lib/tools/auth0/handlers/connections.d.ts +11 -6
- package/lib/tools/auth0/handlers/connections.js +33 -7
- package/lib/tools/auth0/handlers/databases.js +12 -3
- package/lib/tools/auth0/handlers/default.d.ts +23 -0
- package/lib/tools/auth0/handlers/default.js +21 -10
- package/lib/tools/auth0/handlers/organizations.d.ts +29 -1
- package/lib/tools/auth0/handlers/organizations.js +165 -17
- package/lib/tools/auth0/handlers/phoneProvider.js +10 -2
- package/lib/tools/auth0/handlers/phoneTemplates.d.ts +4 -1
- package/lib/tools/auth0/handlers/phoneTemplates.js +67 -10
- package/lib/tools/auth0/handlers/roles.d.ts +4 -0
- package/lib/tools/auth0/handlers/roles.js +10 -0
- package/lib/tools/calculateDryRunChanges.js +12 -8
- package/lib/types.d.ts +1 -0
- package/lib/utils.js +5 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [8.44.0] - 2026-08-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Support `third_party_client_access` and `cross_app_access_resource_app` on clients and connections. [#1471]
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Warn on path traversal in file references and fix double-path resolution in YAML connections handler. [#1477]
|
|
19
|
+
- Strip `form_submission_mode` and `form_submission_behavior` from `bruteForceProtection` on export and import. [#1476]
|
|
20
|
+
- Include empty credentials for custom phone provider on import and export to prevent 400 errors. [#1474]
|
|
21
|
+
- Preserve keyword placeholder strings for all email provider credentials. [#1470]
|
|
22
|
+
- Preserve `idpinitiated.enabled:false` on SAML connection export instead of emitting `undefined_clientId`. [#1465]
|
|
23
|
+
- Apply 429 backoff to organization connection writes to prevent rate-limit failures. [#1464]
|
|
24
|
+
- Resolve dry-run reporting bugs across multiple asset types. [#1462]
|
|
25
|
+
- Skip organization PATCH when no top-level properties have changed. [#1461]
|
|
26
|
+
- Sort `attackProtection` arrays on export to prevent spurious diffs. [#1460]
|
|
27
|
+
|
|
28
|
+
## [8.43.0] - 2026-08-12
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Add org-to-app entitlement (EA) support for organizations. [#1454]
|
|
33
|
+
- Support full group metadata for Google Workspace directory sync (GA). [#1456]
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Create phone templates on import instead of skipping when they have no ID. [#1457]
|
|
38
|
+
- Address path traversal vulnerability and false warnings in config file handlers. [#1449]
|
|
39
|
+
- Exclude org-scoped roles from tenant export by filtering `type='tenant'`. [#1453]
|
|
40
|
+
- Strip read-only field from roles to fix export/import round-trip. [#1445]
|
|
41
|
+
|
|
10
42
|
## [8.42.0] - 2026-07-30
|
|
11
43
|
|
|
12
44
|
### Added
|
|
@@ -1907,7 +1939,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1907
1939
|
[#1436]: https://github.com/auth0/auth0-deploy-cli/issues/1436
|
|
1908
1940
|
[#1437]: https://github.com/auth0/auth0-deploy-cli/issues/1437
|
|
1909
1941
|
[#1438]: https://github.com/auth0/auth0-deploy-cli/issues/1438
|
|
1910
|
-
[
|
|
1942
|
+
[#1445]: https://github.com/auth0/auth0-deploy-cli/issues/1445
|
|
1943
|
+
[#1449]: https://github.com/auth0/auth0-deploy-cli/issues/1449
|
|
1944
|
+
[#1453]: https://github.com/auth0/auth0-deploy-cli/issues/1453
|
|
1945
|
+
[#1454]: https://github.com/auth0/auth0-deploy-cli/issues/1454
|
|
1946
|
+
[#1456]: https://github.com/auth0/auth0-deploy-cli/issues/1456
|
|
1947
|
+
[#1457]: https://github.com/auth0/auth0-deploy-cli/issues/1457
|
|
1948
|
+
[#1460]: https://github.com/auth0/auth0-deploy-cli/issues/1460
|
|
1949
|
+
[#1461]: https://github.com/auth0/auth0-deploy-cli/issues/1461
|
|
1950
|
+
[#1462]: https://github.com/auth0/auth0-deploy-cli/issues/1462
|
|
1951
|
+
[#1464]: https://github.com/auth0/auth0-deploy-cli/issues/1464
|
|
1952
|
+
[#1465]: https://github.com/auth0/auth0-deploy-cli/issues/1465
|
|
1953
|
+
[#1470]: https://github.com/auth0/auth0-deploy-cli/issues/1470
|
|
1954
|
+
[#1471]: https://github.com/auth0/auth0-deploy-cli/issues/1471
|
|
1955
|
+
[#1474]: https://github.com/auth0/auth0-deploy-cli/issues/1474
|
|
1956
|
+
[#1476]: https://github.com/auth0/auth0-deploy-cli/issues/1476
|
|
1957
|
+
[#1477]: https://github.com/auth0/auth0-deploy-cli/issues/1477
|
|
1958
|
+
[Unreleased]: https://github.com/auth0/auth0-deploy-cli/compare/v8.44.0...HEAD
|
|
1959
|
+
[8.44.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.43.0...v8.44.0
|
|
1960
|
+
[8.43.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.42.0...v8.43.0
|
|
1911
1961
|
[8.42.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.41.0...v8.42.0
|
|
1912
1962
|
[8.41.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.40.0...v8.41.0
|
|
1913
1963
|
[8.40.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.39.0...v8.40.0
|
|
@@ -6,4 +6,11 @@ export declare function phoneTemplatesDefaults(phoneTemplate: any): any;
|
|
|
6
6
|
export declare function connectionDefaults(connection: any, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): any;
|
|
7
7
|
export declare function logStreamDefaults(logStreams: any, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): any;
|
|
8
8
|
export declare function eventStreamDefaults(eventStreams: any[], config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): any[];
|
|
9
|
+
/**
|
|
10
|
+
* Sorts primitive arrays (e.g. `shields`, `admin_notification_frequency`) within the
|
|
11
|
+
* attackProtection subtree in place, so repeated exports produce deterministic output.
|
|
12
|
+
* These are unordered, string-typed enum sets — ordering is cosmetic. Object arrays are
|
|
13
|
+
* left as-is.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sortAttackProtectionArrays(attackProtection: AttackProtection): AttackProtection;
|
|
9
16
|
export declare function attackProtectionDefaults(attackProtection: AttackProtection, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): AttackProtection;
|
package/lib/context/defaults.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.phoneTemplatesDefaults = phoneTemplatesDefaults;
|
|
|
6
6
|
exports.connectionDefaults = connectionDefaults;
|
|
7
7
|
exports.logStreamDefaults = logStreamDefaults;
|
|
8
8
|
exports.eventStreamDefaults = eventStreamDefaults;
|
|
9
|
+
exports.sortAttackProtectionArrays = sortAttackProtectionArrays;
|
|
9
10
|
exports.attackProtectionDefaults = attackProtectionDefaults;
|
|
10
11
|
const utils_1 = require("../tools/utils");
|
|
11
12
|
// env vars arrive as strings, so check both forms
|
|
@@ -22,45 +23,57 @@ function emailProviderDefaults(emailProvider, config) {
|
|
|
22
23
|
// Add placeholder for credentials as they cannot be exported
|
|
23
24
|
const { name } = updated;
|
|
24
25
|
if (apiKeyProviders.includes(name)) {
|
|
25
|
-
updated.credentials
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if (typeof updated.credentials !== 'string') {
|
|
27
|
+
updated.credentials = {
|
|
28
|
+
api_key: `##${name.toUpperCase()}_API_KEY##`,
|
|
29
|
+
...(updated.credentials || {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
if (name === 'smtp') {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
// If credentials is a keyword placeholder string (e.g. @@SMTP_CREDENTIALS@@), preserve it as-is.
|
|
35
|
+
// The `in` operator requires an object and would throw a TypeError on a string.
|
|
36
|
+
if (typeof updated.credentials !== 'string') {
|
|
37
|
+
// This is to mask smtp_user to '##SMTP_USER##'
|
|
38
|
+
if (updated.credentials && 'smtp_user' in updated.credentials) {
|
|
39
|
+
delete updated.credentials.smtp_user;
|
|
40
|
+
}
|
|
41
|
+
updated.credentials = {
|
|
42
|
+
smtp_host: '##SMTP_HOSTNAME##',
|
|
43
|
+
smtp_port: '##SMTP_PORT##',
|
|
44
|
+
smtp_user: '##SMTP_USER##',
|
|
45
|
+
smtp_pass: '##SMTP_PASS##',
|
|
46
|
+
...(updated.credentials || {}),
|
|
47
|
+
};
|
|
34
48
|
}
|
|
35
|
-
updated.credentials = {
|
|
36
|
-
smtp_host: '##SMTP_HOSTNAME##',
|
|
37
|
-
smtp_port: '##SMTP_PORT##',
|
|
38
|
-
smtp_user: '##SMTP_USER##',
|
|
39
|
-
smtp_pass: '##SMTP_PASS##',
|
|
40
|
-
...(updated.credentials || {}),
|
|
41
|
-
};
|
|
42
49
|
}
|
|
43
50
|
if (name === 'ses') {
|
|
44
|
-
updated.credentials
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
if (typeof updated.credentials !== 'string') {
|
|
52
|
+
updated.credentials = {
|
|
53
|
+
accessKeyId: '##SES_ACCESS_KEY_ID##',
|
|
54
|
+
secretAccessKey: '##SES_ACCESS_SECRET_KEY##',
|
|
55
|
+
region: '##SES_AWS_REGION##',
|
|
56
|
+
...(updated.credentials || {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
50
59
|
}
|
|
51
60
|
if (name === 'azure_cs') {
|
|
52
|
-
updated.credentials
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
if (typeof updated.credentials !== 'string') {
|
|
62
|
+
updated.credentials = {
|
|
63
|
+
connectionString: '##AZURE_CS_CONNECTION_KEY##',
|
|
64
|
+
...(updated.credentials || {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
56
67
|
}
|
|
57
68
|
if (name === 'ms365') {
|
|
58
|
-
updated.credentials
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
if (typeof updated.credentials !== 'string') {
|
|
70
|
+
updated.credentials = {
|
|
71
|
+
tenantId: '##MS365_TENANT_ID##',
|
|
72
|
+
clientId: '##MS365_CLIENT_ID##',
|
|
73
|
+
clientSecret: '##MS365_CLIENT_SECRET##',
|
|
74
|
+
...(updated.credentials || {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
return updated;
|
|
66
79
|
}
|
|
@@ -87,6 +100,12 @@ function phoneProviderDefaults(phoneProvider) {
|
|
|
87
100
|
auth_token: `##${name.toUpperCase()}_AUTH_TOKEN##`,
|
|
88
101
|
};
|
|
89
102
|
}
|
|
103
|
+
else if (name === 'custom') {
|
|
104
|
+
// The `custom` provider has no exportable secrets, but the API requires a
|
|
105
|
+
// `credentials` object on import even when empty. Emit an empty object so
|
|
106
|
+
// exported configs round-trip cleanly.
|
|
107
|
+
updated.credentials = {};
|
|
108
|
+
}
|
|
90
109
|
return updated;
|
|
91
110
|
}
|
|
92
111
|
function phoneTemplatesDefaults(phoneTemplate) {
|
|
@@ -193,6 +212,31 @@ function eventStreamDefaults(eventStreams, config) {
|
|
|
193
212
|
};
|
|
194
213
|
});
|
|
195
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Sorts primitive arrays (e.g. `shields`, `admin_notification_frequency`) within the
|
|
217
|
+
* attackProtection subtree in place, so repeated exports produce deterministic output.
|
|
218
|
+
* These are unordered, string-typed enum sets — ordering is cosmetic. Object arrays are
|
|
219
|
+
* left as-is.
|
|
220
|
+
*/
|
|
221
|
+
function sortAttackProtectionArrays(attackProtection) {
|
|
222
|
+
const sortArraysDeep = (value) => {
|
|
223
|
+
if (Array.isArray(value)) {
|
|
224
|
+
const isPrimitiveArray = value.every((item) => item === null || typeof item !== 'object');
|
|
225
|
+
if (isPrimitiveArray) {
|
|
226
|
+
value.sort();
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
value.forEach(sortArraysDeep);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (value && typeof value === 'object') {
|
|
234
|
+
Object.values(value).forEach(sortArraysDeep);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
sortArraysDeep(attackProtection);
|
|
238
|
+
return attackProtection;
|
|
239
|
+
}
|
|
196
240
|
function attackProtectionDefaults(attackProtection, config) {
|
|
197
241
|
if (isExportSecrets(config))
|
|
198
242
|
return attackProtection;
|
|
@@ -20,20 +20,19 @@ function parse(context) {
|
|
|
20
20
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
21
21
|
}),
|
|
22
22
|
};
|
|
23
|
-
const moduleFolder = path_1.default.join(tools_1.constants.ACTION_MODULES_DIRECTORY, `${module.name}`);
|
|
24
23
|
if (module.code) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
logger_1.default.warn(`
|
|
30
|
-
`
|
|
31
|
-
`
|
|
32
|
-
module.code = context.loadFile(unixPath, moduleFolder);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
module.code = context.loadFile(path_1.default.join(context.filePath, module.code), moduleFolder);
|
|
24
|
+
const normalizedCode = module.code.replace(/\\/g, '/');
|
|
25
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
26
|
+
const resolvedPath = path_1.default.resolve(context.filePath, normalizedCode);
|
|
27
|
+
if (!resolvedPath.startsWith(configRoot + path_1.default.sep)) {
|
|
28
|
+
logger_1.default.warn(`Path "${module.code}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
29
|
+
`This will be blocked as an error in the next major release. ` +
|
|
30
|
+
`Move the file inside your config directory.`);
|
|
36
31
|
}
|
|
32
|
+
module.code = (0, tools_1.loadFileAndReplaceKeywords)(resolvedPath, {
|
|
33
|
+
mappings: context.mappings,
|
|
34
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
35
|
+
});
|
|
37
36
|
}
|
|
38
37
|
return module;
|
|
39
38
|
});
|
|
@@ -22,21 +22,19 @@ function parse(context) {
|
|
|
22
22
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
23
23
|
}),
|
|
24
24
|
};
|
|
25
|
-
const actionFolder = path_1.default.join(tools_1.constants.ACTIONS_DIRECTORY, `${action.name}`);
|
|
26
25
|
if (action.code) {
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
logger_1.default.warn(`
|
|
32
|
-
`
|
|
33
|
-
`
|
|
34
|
-
action.code = context.loadFile(unixPath, actionFolder);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
// Otherwise, load the file from the context's file path
|
|
38
|
-
action.code = context.loadFile(path_1.default.join(context.filePath, action.code), actionFolder);
|
|
26
|
+
const normalizedCode = action.code.replace(/\\/g, '/');
|
|
27
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
28
|
+
const resolvedPath = path_1.default.resolve(context.filePath, normalizedCode);
|
|
29
|
+
if (!resolvedPath.startsWith(configRoot + path_1.default.sep)) {
|
|
30
|
+
logger_1.default.warn(`Path "${action.code}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
31
|
+
`This will be blocked as an error in the next major release. ` +
|
|
32
|
+
`Move the file inside your config directory.`);
|
|
39
33
|
}
|
|
34
|
+
action.code = (0, tools_1.loadFileAndReplaceKeywords)(resolvedPath, {
|
|
35
|
+
mappings: context.mappings,
|
|
36
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
37
|
+
});
|
|
40
38
|
}
|
|
41
39
|
return action;
|
|
42
40
|
});
|
|
@@ -65,7 +65,7 @@ async function dump(context) {
|
|
|
65
65
|
return;
|
|
66
66
|
const files = attackProtectionFiles(context.filePath);
|
|
67
67
|
fs_extra_1.default.ensureDirSync(files.directory);
|
|
68
|
-
const maskedAttackProtection = (0, defaults_1.attackProtectionDefaults)(attackProtection, context.config);
|
|
68
|
+
const maskedAttackProtection = (0, defaults_1.sortAttackProtectionArrays)((0, defaults_1.attackProtectionDefaults)(attackProtection, context.config));
|
|
69
69
|
if (maskedAttackProtection.botDetection) {
|
|
70
70
|
(0, utils_1.dumpJSON)(files.botDetection, maskedAttackProtection.botDetection);
|
|
71
71
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const path_1 = __importDefault(require("path"));
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
8
|
const tools_1 = require("../../../tools");
|
|
9
|
+
const logger_1 = __importDefault(require("../../../logger"));
|
|
9
10
|
const utils_1 = require("../../../utils");
|
|
10
11
|
function parse(context) {
|
|
11
12
|
const brandingDirectory = path_1.default.join(context.filePath, tools_1.constants.BRANDING_DIRECTORY);
|
|
@@ -31,7 +32,14 @@ function parse(context) {
|
|
|
31
32
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
32
33
|
});
|
|
33
34
|
const normalizedPathArray = (0, utils_1.nomalizedYAMLPath)(definition.body);
|
|
34
|
-
|
|
35
|
+
const resolvedBodyFile = path_1.default.resolve(brandingTemplatesFolder, ...normalizedPathArray);
|
|
36
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
37
|
+
if (!resolvedBodyFile.startsWith(configRoot + path_1.default.sep)) {
|
|
38
|
+
logger_1.default.warn(`Branding template body path "${definition.body}" resolves to "${resolvedBodyFile}" which is outside the config directory "${configRoot}". ` +
|
|
39
|
+
`This will be blocked as an error in the next major release. ` +
|
|
40
|
+
`Move the file inside your config directory.`);
|
|
41
|
+
}
|
|
42
|
+
definition.body = (0, tools_1.loadFileAndReplaceKeywords)(resolvedBodyFile, {
|
|
35
43
|
mappings: context.mappings,
|
|
36
44
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
37
45
|
});
|
|
@@ -20,9 +20,15 @@ function parse(context) {
|
|
|
20
20
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
21
21
|
});
|
|
22
22
|
if (client.custom_login_page) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
24
|
+
const resolvedLoginPage = path_1.default.resolve(clientsFolder, client.custom_login_page);
|
|
25
|
+
if ((0, utils_1.isFile)(resolvedLoginPage)) {
|
|
26
|
+
if (!resolvedLoginPage.startsWith(configRoot + path_1.default.sep)) {
|
|
27
|
+
logger_1.default.warn(`Path "${client.custom_login_page}" resolves to "${resolvedLoginPage}" which is outside the config directory "${configRoot}". ` +
|
|
28
|
+
`This will be blocked as an error in the next major release. ` +
|
|
29
|
+
`Move the file inside your config directory.`);
|
|
30
|
+
}
|
|
31
|
+
client.custom_login_page = (0, tools_1.loadFileAndReplaceKeywords)(resolvedLoginPage, {
|
|
26
32
|
mappings: context.mappings,
|
|
27
33
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
28
34
|
});
|
|
@@ -23,11 +23,17 @@ function parse(context) {
|
|
|
23
23
|
});
|
|
24
24
|
if (connection.strategy === 'email') {
|
|
25
25
|
(0, utils_1.ensureProp)(connection, 'options.email.body');
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
27
|
+
const resolvedHtmlFile = path_1.default.resolve(connectionsFolder, connection.options.email.body);
|
|
28
|
+
if (!(0, utils_1.isFile)(resolvedHtmlFile)) {
|
|
29
|
+
throw new Error(`Passwordless email template purportedly located at ${resolvedHtmlFile} does not exist for connection. Ensure the existence of this file to proceed with deployment.`);
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
if (!resolvedHtmlFile.startsWith(configRoot + path_1.default.sep)) {
|
|
32
|
+
logger_1.default.warn(`Path "${connection.options.email.body}" resolves to "${resolvedHtmlFile}" which is outside the config directory "${configRoot}". ` +
|
|
33
|
+
`This will be blocked as an error in the next major release. ` +
|
|
34
|
+
`Move the file inside your config directory.`);
|
|
35
|
+
}
|
|
36
|
+
connection.options.email.body = (0, tools_1.loadFileAndReplaceKeywords)(resolvedHtmlFile, {
|
|
31
37
|
mappings: context.mappings,
|
|
32
38
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
33
39
|
});
|
|
@@ -41,11 +41,11 @@ function getDatabase(folder, configRoot, mappingOpts) {
|
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
43
|
const resolvedBase = path_1.default.resolve(configRoot);
|
|
44
|
-
const toLoad = path_1.default.resolve(folder, script);
|
|
44
|
+
const toLoad = path_1.default.resolve(folder, script.replace(/\\/g, '/'));
|
|
45
45
|
if (!toLoad.startsWith(resolvedBase + path_1.default.sep)) {
|
|
46
|
-
logger_1.default.warn(`
|
|
47
|
-
`
|
|
48
|
-
`
|
|
46
|
+
logger_1.default.warn(`Path "${script}" resolves to "${toLoad}" which is outside the config directory "${resolvedBase}". ` +
|
|
47
|
+
`This will be blocked as an error in the next major release. ` +
|
|
48
|
+
`Move the file inside your config directory.`);
|
|
49
49
|
}
|
|
50
50
|
database.options.customScripts[name] = (0, tools_1.loadFileAndReplaceKeywords)(toLoad, mappingOpts);
|
|
51
51
|
}
|
|
@@ -34,6 +34,15 @@ function parse(context) {
|
|
|
34
34
|
logger_1.default.warn(`Skipping email template file ${meta.body} as missing the corresponding '.json' file`);
|
|
35
35
|
return [];
|
|
36
36
|
}
|
|
37
|
+
if (meta.body !== undefined) {
|
|
38
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
39
|
+
const resolvedTemplatePath = path_1.default.resolve(templateFilePath);
|
|
40
|
+
if (!resolvedTemplatePath.startsWith(configRoot + path_1.default.sep)) {
|
|
41
|
+
logger_1.default.warn(`Email template body path "${meta.body}" resolves to "${resolvedTemplatePath}" which is outside the config directory "${configRoot}". ` +
|
|
42
|
+
`This will be blocked as an error in the next major release. ` +
|
|
43
|
+
`Move the file inside your config directory.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
return {
|
|
38
47
|
...meta,
|
|
39
48
|
body: (0, tools_1.loadFileAndReplaceKeywords)(templateFilePath, {
|
|
@@ -21,7 +21,18 @@ function parse(context) {
|
|
|
21
21
|
}),
|
|
22
22
|
};
|
|
23
23
|
if (hook.script) {
|
|
24
|
-
|
|
24
|
+
const normalizedScript = hook.script.replace(/\\/g, '/');
|
|
25
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
26
|
+
const resolvedPath = path_1.default.resolve(context.filePath, tools_1.constants.HOOKS_DIRECTORY, normalizedScript);
|
|
27
|
+
if (!resolvedPath.startsWith(configRoot + path_1.default.sep)) {
|
|
28
|
+
logger_1.default.warn(`Path "${hook.script}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
29
|
+
`This will be blocked as an error in the next major release. ` +
|
|
30
|
+
`Move the file inside your config directory.`);
|
|
31
|
+
}
|
|
32
|
+
hook.script = (0, tools_1.loadFileAndReplaceKeywords)(resolvedPath, {
|
|
33
|
+
mappings: context.mappings,
|
|
34
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
35
|
+
});
|
|
25
36
|
}
|
|
26
37
|
hook.name = hook.name.toLowerCase().replace(/\s/g, '-');
|
|
27
38
|
return hook;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const path_1 = __importDefault(require("path"));
|
|
7
7
|
const fs_extra_1 = require("fs-extra");
|
|
8
8
|
const tools_1 = require("../../../tools");
|
|
9
|
+
const logger_1 = __importDefault(require("../../../logger"));
|
|
9
10
|
const utils_1 = require("../../../utils");
|
|
10
11
|
const getPromptsDirectory = (filePath) => path_1.default.join(filePath, tools_1.constants.PROMPTS_DIRECTORY);
|
|
11
12
|
const getPromptsSettingsFile = (promptsDirectory) => path_1.default.join(promptsDirectory, 'prompts.json');
|
|
@@ -46,9 +47,16 @@ function parse(context) {
|
|
|
46
47
|
const screensObject = screensArray[0];
|
|
47
48
|
acc[promptName] = Object.entries(screensObject).reduce((screenAcc, [screenName, items]) => {
|
|
48
49
|
screenAcc[screenName] = items.reduce((insertionAcc, { name, template }) => {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
51
|
+
const resolvedTemplatePath = path_1.default.resolve(promptsDirectory, template);
|
|
52
|
+
if ((0, utils_1.isFile)(resolvedTemplatePath) &&
|
|
53
|
+
!resolvedTemplatePath.startsWith(configRoot + path_1.default.sep)) {
|
|
54
|
+
logger_1.default.warn(`Prompt partial template "${template}" resolves to "${resolvedTemplatePath}" which is outside the config directory "${configRoot}". ` +
|
|
55
|
+
`This will be blocked as an error in the next major release. ` +
|
|
56
|
+
`Move the file inside your config directory.`);
|
|
57
|
+
}
|
|
58
|
+
insertionAcc[name] = (0, utils_1.isFile)(resolvedTemplatePath)
|
|
59
|
+
? (0, tools_1.loadFileAndReplaceKeywords)(resolvedTemplatePath, {
|
|
52
60
|
mappings: context.mappings,
|
|
53
61
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
54
62
|
}).trim()
|
|
@@ -21,7 +21,18 @@ function parse(context) {
|
|
|
21
21
|
}),
|
|
22
22
|
};
|
|
23
23
|
if (rule.script) {
|
|
24
|
-
|
|
24
|
+
const normalizedScript = rule.script.replace(/\\/g, '/');
|
|
25
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
26
|
+
const resolvedPath = path_1.default.resolve(context.filePath, tools_1.constants.RULES_DIRECTORY, normalizedScript);
|
|
27
|
+
if (!resolvedPath.startsWith(configRoot + path_1.default.sep)) {
|
|
28
|
+
logger_1.default.warn(`Path "${rule.script}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
29
|
+
`This will be blocked as an error in the next major release. ` +
|
|
30
|
+
`Move the file inside your config directory.`);
|
|
31
|
+
}
|
|
32
|
+
rule.script = (0, tools_1.loadFileAndReplaceKeywords)(resolvedPath, {
|
|
33
|
+
mappings: context.mappings,
|
|
34
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
35
|
+
});
|
|
25
36
|
}
|
|
26
37
|
return rule;
|
|
27
38
|
});
|
|
@@ -68,14 +68,13 @@ class DirectoryContext {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
loadFile(f, folder) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
toLoad
|
|
76
|
-
|
|
77
|
-
`
|
|
78
|
-
`Current absolute path used: ["${f}"]`);
|
|
71
|
+
const configRoot = path.resolve(this.filePath);
|
|
72
|
+
const basePath = path.resolve(this.filePath, folder);
|
|
73
|
+
const toLoad = path.resolve(basePath, f.replace(/\\/g, '/'));
|
|
74
|
+
if (!toLoad.startsWith(configRoot + path.sep)) {
|
|
75
|
+
logger_1.default.warn(`Path "${f}" resolves to "${toLoad}" which is outside the config directory "${configRoot}". ` +
|
|
76
|
+
`This will be blocked as an error in the next major release. ` +
|
|
77
|
+
`Move the file inside your config directory.`);
|
|
79
78
|
}
|
|
80
79
|
return (0, tools_1.loadFileAndReplaceKeywords)(toLoad, {
|
|
81
80
|
mappings: this.mappings,
|
|
@@ -27,7 +27,7 @@ async function dump(context) {
|
|
|
27
27
|
}
|
|
28
28
|
const maskedAttackProtection = (0, defaults_1.attackProtectionDefaults)(attackProtectionConfig, context.config);
|
|
29
29
|
return {
|
|
30
|
-
attackProtection: maskedAttackProtection,
|
|
30
|
+
attackProtection: (0, defaults_1.sortAttackProtectionArrays)(maskedAttackProtection),
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
const attackProtectionHandler = {
|
|
@@ -18,9 +18,16 @@ async function parse(context) {
|
|
|
18
18
|
const parsedTemplates = templates.map((templateDefinition) => {
|
|
19
19
|
const normalizedPathArray = (0, utils_1.nomalizedYAMLPath)(templateDefinition.body);
|
|
20
20
|
const markupFile = path_1.default.join(context.basePath, ...normalizedPathArray);
|
|
21
|
+
const configRoot = path_1.default.resolve(context.basePath);
|
|
22
|
+
const resolvedMarkupFile = path_1.default.resolve(markupFile);
|
|
23
|
+
if (!resolvedMarkupFile.startsWith(configRoot + path_1.default.sep)) {
|
|
24
|
+
logger_1.default.warn(`Branding template body path "${templateDefinition.body}" resolves to "${resolvedMarkupFile}" which is outside the config directory "${configRoot}". ` +
|
|
25
|
+
`This will be blocked as an error in the next major release. ` +
|
|
26
|
+
`Move the file inside your config directory.`);
|
|
27
|
+
}
|
|
21
28
|
return {
|
|
22
29
|
template: templateDefinition.template,
|
|
23
|
-
body: (0, tools_1.loadFileAndReplaceKeywords)(
|
|
30
|
+
body: (0, tools_1.loadFileAndReplaceKeywords)(resolvedMarkupFile, {
|
|
24
31
|
mappings: context.mappings,
|
|
25
32
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
26
33
|
}),
|
|
@@ -21,7 +21,10 @@ async function parse(context) {
|
|
|
21
21
|
if (client.custom_login_page) {
|
|
22
22
|
const htmlFileName = path_1.default.join(clientsFolder, client.custom_login_page);
|
|
23
23
|
if ((0, utils_1.isFile)(htmlFileName)) {
|
|
24
|
-
|
|
24
|
+
// Pass a path relative to basePath so context.loadFile() resolves it correctly.
|
|
25
|
+
// Passing htmlFileName (which already includes basePath) would cause double resolution
|
|
26
|
+
// when basePath is a relative path (e.g. when AUTH0_INPUT_FILE is a relative path).
|
|
27
|
+
client.custom_login_page = context.loadFile(path_1.default.join(tools_1.constants.CLIENTS_DIRECTORY, client.custom_login_page));
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
return client;
|
|
@@ -26,7 +26,9 @@ async function parse(context) {
|
|
|
26
26
|
logger_1.default.error(missingTemplateErrorMessage);
|
|
27
27
|
throw new Error(missingTemplateErrorMessage);
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// Pass a path relative to basePath so context.loadFile() resolves it correctly.
|
|
30
|
+
// Passing htmlFileName (which already includes basePath) would cause double resolution.
|
|
31
|
+
connection.options.email.body = context.loadFile(path_1.default.join(tools_1.constants.CONNECTIONS_DIRECTORY, connection.options.email.body));
|
|
30
32
|
}
|
|
31
33
|
return connection;
|
|
32
34
|
}),
|
|
@@ -14,6 +14,13 @@ async function parse(context) {
|
|
|
14
14
|
return { flows: null };
|
|
15
15
|
const parsedFlows = flows.map((flow) => {
|
|
16
16
|
const flowFile = path_1.default.join(context.basePath, flow.body);
|
|
17
|
+
const configRoot = path_1.default.resolve(context.basePath);
|
|
18
|
+
const resolvedFlowFile = path_1.default.resolve(flowFile);
|
|
19
|
+
if (!resolvedFlowFile.startsWith(configRoot + path_1.default.sep)) {
|
|
20
|
+
logger_1.default.warn(`Flow body file "${flow.body}" resolves to "${resolvedFlowFile}" which is outside the config directory "${configRoot}". ` +
|
|
21
|
+
`This will be blocked as an error in the next major release. ` +
|
|
22
|
+
`Move the file inside your config directory.`);
|
|
23
|
+
}
|
|
17
24
|
const parsedFlowBody = (0, utils_1.loadJSON)(flowFile, {
|
|
18
25
|
mappings: context.mappings,
|
|
19
26
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
@@ -14,6 +14,13 @@ async function parse(context) {
|
|
|
14
14
|
return { forms: null };
|
|
15
15
|
const parsedForms = forms.map((form) => {
|
|
16
16
|
const formFile = path_1.default.join(context.basePath, form.body);
|
|
17
|
+
const configRoot = path_1.default.resolve(context.basePath);
|
|
18
|
+
const resolvedFormFile = path_1.default.resolve(formFile);
|
|
19
|
+
if (!resolvedFormFile.startsWith(configRoot + path_1.default.sep)) {
|
|
20
|
+
logger_1.default.warn(`Form body file "${form.body}" resolves to "${resolvedFormFile}" which is outside the config directory "${configRoot}". ` +
|
|
21
|
+
`This will be blocked as an error in the next major release. ` +
|
|
22
|
+
`Move the file inside your config directory.`);
|
|
23
|
+
}
|
|
17
24
|
const parsedFormBody = (0, utils_1.loadJSON)(formFile, {
|
|
18
25
|
mappings: context.mappings,
|
|
19
26
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
@@ -19,7 +19,13 @@ const loadScreenRenderers = (context, screenRenderArray) => {
|
|
|
19
19
|
Object.entries(screens).forEach(([, fileName]) => {
|
|
20
20
|
const filePath = fileName;
|
|
21
21
|
try {
|
|
22
|
-
const
|
|
22
|
+
const configRoot = path_1.default.resolve(context.basePath);
|
|
23
|
+
const rendererFile = path_1.default.resolve(context.basePath, filePath);
|
|
24
|
+
if (!rendererFile.startsWith(configRoot + path_1.default.sep)) {
|
|
25
|
+
logger_1.default.warn(`Screen renderer file "${filePath}" resolves to "${rendererFile}" which is outside the config directory "${configRoot}". ` +
|
|
26
|
+
`This will be blocked as an error in the next major release. ` +
|
|
27
|
+
`Move the file inside your config directory.`);
|
|
28
|
+
}
|
|
23
29
|
const rendererData = (0, utils_1.loadJSON)(rendererFile, {
|
|
24
30
|
mappings: context.mappings,
|
|
25
31
|
disableKeywordReplacement: context.disableKeywordReplacement,
|