auth0-deploy-cli 8.43.0 → 8.45.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/README.md +1 -1
  3. package/lib/context/defaults.d.ts +7 -0
  4. package/lib/context/defaults.js +74 -30
  5. package/lib/context/directory/handlers/attackProtection.js +1 -1
  6. package/lib/context/directory/handlers/branding.js +9 -1
  7. package/lib/context/directory/handlers/clients.js +9 -3
  8. package/lib/context/directory/handlers/connections.js +10 -4
  9. package/lib/context/directory/handlers/emailTemplates.js +9 -0
  10. package/lib/context/directory/handlers/index.js +2 -0
  11. package/lib/context/directory/handlers/networkACLKeys.d.ts +6 -0
  12. package/lib/context/directory/handlers/networkACLKeys.js +54 -0
  13. package/lib/context/directory/handlers/prompts.js +11 -3
  14. package/lib/context/yaml/handlers/attackProtection.js +1 -1
  15. package/lib/context/yaml/handlers/branding.js +8 -1
  16. package/lib/context/yaml/handlers/clients.js +4 -1
  17. package/lib/context/yaml/handlers/connections.js +3 -1
  18. package/lib/context/yaml/handlers/flows.js +7 -0
  19. package/lib/context/yaml/handlers/forms.js +7 -0
  20. package/lib/context/yaml/handlers/index.js +2 -0
  21. package/lib/context/yaml/handlers/networkACLKeys.d.ts +6 -0
  22. package/lib/context/yaml/handlers/networkACLKeys.js +42 -0
  23. package/lib/context/yaml/handlers/prompts.js +7 -1
  24. package/lib/keywordPreservation.d.ts +1 -1
  25. package/lib/keywordPreservation.js +2 -1
  26. package/lib/readonly.js +5 -0
  27. package/lib/tools/auth0/handlers/attackProtection.js +3 -1
  28. package/lib/tools/auth0/handlers/clients.d.ts +40 -0
  29. package/lib/tools/auth0/handlers/clients.js +58 -1
  30. package/lib/tools/auth0/handlers/connectionProfiles.d.ts +27 -0
  31. package/lib/tools/auth0/handlers/connectionProfiles.js +30 -0
  32. package/lib/tools/auth0/handlers/connections.js +10 -3
  33. package/lib/tools/auth0/handlers/databases.d.ts +28 -0
  34. package/lib/tools/auth0/handlers/databases.js +47 -22
  35. package/lib/tools/auth0/handlers/default.d.ts +23 -0
  36. package/lib/tools/auth0/handlers/default.js +21 -10
  37. package/lib/tools/auth0/handlers/index.js +2 -0
  38. package/lib/tools/auth0/handlers/networkACLKeys.d.ts +45 -0
  39. package/lib/tools/auth0/handlers/networkACLKeys.js +177 -0
  40. package/lib/tools/auth0/handlers/networkACLs.d.ts +114 -2
  41. package/lib/tools/auth0/handlers/networkACLs.js +91 -16
  42. package/lib/tools/auth0/handlers/organizations.d.ts +1 -1
  43. package/lib/tools/auth0/handlers/organizations.js +49 -20
  44. package/lib/tools/auth0/handlers/phoneProvider.js +10 -2
  45. package/lib/tools/calculateDryRunChanges.js +12 -8
  46. package/lib/tools/constants.d.ts +1 -0
  47. package/lib/tools/constants.js +1 -0
  48. package/lib/types.d.ts +3 -1
  49. package/lib/utils.js +5 -2
  50. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [8.45.0] - 2026-09-09
11
+
12
+ ### Added
13
+
14
+ - Support `b2b_integration_configuration` for Enterprise Connect (EA). [#1486]
15
+ - Add `networkACLKeys` handler for HMAC signature key management. [#1480]
16
+ - Support `match_all` in network ACL rules for unconditional block/allow. [#1483]
17
+ - Add `otp_settings` support for database connections. [#1481]
18
+
19
+ ### Fixed
20
+
21
+ - Prevent export crash when a handler has no identifiers during keyword preservation. [#1487]
22
+ - Catch `feature_not_enabled` via `err.body.errorCode` and 400 status in organizations handler. [#1482]
23
+ - Prevent false `enabled_clients` diff in database dry-run. [#1479]
24
+
25
+ ## [8.44.0] - 2026-08-26
26
+
27
+ ### Added
28
+
29
+ - Support `third_party_client_access` and `cross_app_access_resource_app` on clients and connections. [#1471]
30
+
31
+ ### Fixed
32
+
33
+ - Warn on path traversal in file references and fix double-path resolution in YAML connections handler. [#1477]
34
+ - Strip `form_submission_mode` and `form_submission_behavior` from `bruteForceProtection` on export and import. [#1476]
35
+ - Include empty credentials for custom phone provider on import and export to prevent 400 errors. [#1474]
36
+ - Preserve keyword placeholder strings for all email provider credentials. [#1470]
37
+ - Preserve `idpinitiated.enabled:false` on SAML connection export instead of emitting `undefined_clientId`. [#1465]
38
+ - Apply 429 backoff to organization connection writes to prevent rate-limit failures. [#1464]
39
+ - Resolve dry-run reporting bugs across multiple asset types. [#1462]
40
+ - Skip organization PATCH when no top-level properties have changed. [#1461]
41
+ - Sort `attackProtection` arrays on export to prevent spurious diffs. [#1460]
42
+
10
43
  ## [8.43.0] - 2026-08-12
11
44
 
12
45
  ### Added
@@ -1927,7 +1960,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1927
1960
  [#1454]: https://github.com/auth0/auth0-deploy-cli/issues/1454
1928
1961
  [#1456]: https://github.com/auth0/auth0-deploy-cli/issues/1456
1929
1962
  [#1457]: https://github.com/auth0/auth0-deploy-cli/issues/1457
1930
- [Unreleased]: https://github.com/auth0/auth0-deploy-cli/compare/v8.43.0...HEAD
1963
+ [#1460]: https://github.com/auth0/auth0-deploy-cli/issues/1460
1964
+ [#1461]: https://github.com/auth0/auth0-deploy-cli/issues/1461
1965
+ [#1462]: https://github.com/auth0/auth0-deploy-cli/issues/1462
1966
+ [#1464]: https://github.com/auth0/auth0-deploy-cli/issues/1464
1967
+ [#1465]: https://github.com/auth0/auth0-deploy-cli/issues/1465
1968
+ [#1470]: https://github.com/auth0/auth0-deploy-cli/issues/1470
1969
+ [#1471]: https://github.com/auth0/auth0-deploy-cli/issues/1471
1970
+ [#1474]: https://github.com/auth0/auth0-deploy-cli/issues/1474
1971
+ [#1476]: https://github.com/auth0/auth0-deploy-cli/issues/1476
1972
+ [#1477]: https://github.com/auth0/auth0-deploy-cli/issues/1477
1973
+ [#1479]: https://github.com/auth0/auth0-deploy-cli/issues/1479
1974
+ [#1480]: https://github.com/auth0/auth0-deploy-cli/issues/1480
1975
+ [#1481]: https://github.com/auth0/auth0-deploy-cli/issues/1481
1976
+ [#1482]: https://github.com/auth0/auth0-deploy-cli/issues/1482
1977
+ [#1483]: https://github.com/auth0/auth0-deploy-cli/issues/1483
1978
+ [#1486]: https://github.com/auth0/auth0-deploy-cli/issues/1486
1979
+ [#1487]: https://github.com/auth0/auth0-deploy-cli/issues/1487
1980
+ [Unreleased]: https://github.com/auth0/auth0-deploy-cli/compare/v8.45.0...HEAD
1981
+ [8.45.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.44.0...v8.45.0
1982
+ [8.44.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.43.0...v8.44.0
1931
1983
  [8.43.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.42.0...v8.43.0
1932
1984
  [8.42.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.41.0...v8.42.0
1933
1985
  [8.41.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.40.0...v8.41.0
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  The Auth0 Deploy CLI is a tool that helps you manage your Auth0 tenant configuration. It integrates into your development workflows as a standalone CLI or as a node module.
10
10
 
11
- **Supported resource types:** actions, branding, client grants, clients (applications), connections, databases, custom domains, email templates, emails, grants, guardian, hook secrets, log streams, organizations, pages, prompts, resource servers (APIs), roles, tenant settings, themes, forms, flows, self-service profiles, network ACLs.
11
+ **Supported resource types:** actions, branding, client grants, clients (applications), connections, databases, custom domains, email templates, emails, grants, guardian, hook secrets, log streams, organizations, pages, prompts, resource servers (APIs), roles, tenant settings, themes, forms, flows, self-service profiles, network ACLs, network ACL keys.
12
12
 
13
13
  🎢 [Highlights](#highlights) • 📚 [Documentation](#documentation) • 🚀 [Getting Started](#getting-started) • 📝 [Feedback](#feedback) • 💬 [Discussions](https://github.com/auth0/auth0-deploy-cli/discussions)
14
14
 
@@ -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;
@@ -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
- api_key: `##${name.toUpperCase()}_API_KEY##`,
27
- ...(updated.credentials || {}),
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
- // This is to mask smtp_user to '##SMTP_USER##'
32
- if (updated.credentials && 'smtp_user' in updated.credentials) {
33
- delete updated.credentials.smtp_user;
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
- accessKeyId: '##SES_ACCESS_KEY_ID##',
46
- secretAccessKey: '##SES_ACCESS_SECRET_KEY##',
47
- region: '##SES_AWS_REGION##',
48
- ...(updated.credentials || {}),
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
- connectionString: '##AZURE_CS_CONNECTION_KEY##',
54
- ...(updated.credentials || {}),
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
- tenantId: '##MS365_TENANT_ID##',
60
- clientId: '##MS365_CLIENT_ID##',
61
- clientSecret: '##MS365_CLIENT_SECRET##',
62
- ...(updated.credentials || {}),
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;
@@ -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
- definition.body = (0, tools_1.loadFileAndReplaceKeywords)(path_1.default.join(brandingTemplatesFolder, ...normalizedPathArray), {
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 htmlFileName = path_1.default.join(clientsFolder, client.custom_login_page);
24
- if ((0, utils_1.isFile)(htmlFileName)) {
25
- client.custom_login_page = (0, tools_1.loadFileAndReplaceKeywords)(htmlFileName, {
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 htmlFileName = path_1.default.join(connectionsFolder, connection.options.email.body);
27
- if (!(0, utils_1.isFile)(htmlFileName)) {
28
- throw new Error(`Passwordless email template purportedly located at ${htmlFileName} does not exist for connection. Ensure the existence of this file to proceed with deployment.`);
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
- connection.options.email.body = (0, tools_1.loadFileAndReplaceKeywords)(htmlFileName, {
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
  });
@@ -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, {
@@ -38,6 +38,7 @@ const rulesConfigs_1 = __importDefault(require("./rulesConfigs"));
38
38
  const forms_1 = __importDefault(require("./forms"));
39
39
  const flows_1 = __importDefault(require("./flows"));
40
40
  const flowVaultConnections_1 = __importDefault(require("./flowVaultConnections"));
41
+ const networkACLKeys_1 = __importDefault(require("./networkACLKeys"));
41
42
  const networkACLs_1 = __importDefault(require("./networkACLs"));
42
43
  const userAttributeProfiles_1 = __importDefault(require("./userAttributeProfiles"));
43
44
  const connectionProfiles_1 = __importDefault(require("./connectionProfiles"));
@@ -84,6 +85,7 @@ const directoryHandlers = {
84
85
  flows: flows_1.default,
85
86
  flowVaultConnections: flowVaultConnections_1.default,
86
87
  selfServiceProfiles: selfServiceProfiles_1.default,
88
+ networkACLKeys: networkACLKeys_1.default,
87
89
  networkACLs: networkACLs_1.default,
88
90
  userAttributeProfiles: userAttributeProfiles_1.default,
89
91
  connectionProfiles: connectionProfiles_1.default,
@@ -0,0 +1,6 @@
1
+ import { DirectoryHandler } from '.';
2
+ import { ParsedAsset } from '../../../types';
3
+ import { NetworkAclKey } from '../../../tools/auth0/handlers/networkACLKeys';
4
+ type ParsedNetworkACLKeys = ParsedAsset<'networkACLKeys', NetworkAclKey[]>;
5
+ declare const networkACLKeysHandler: DirectoryHandler<ParsedNetworkACLKeys>;
6
+ export default networkACLKeysHandler;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const tools_1 = require("../../../tools");
9
+ const utils_1 = require("../../../utils");
10
+ const logger_1 = __importDefault(require("../../../logger"));
11
+ function parse(context) {
12
+ const networkACLKeysDirectory = path_1.default.join(context.filePath, tools_1.constants.NETWORK_ACL_KEYS_DIRECTORY);
13
+ if (!(0, utils_1.existsMustBeDir)(networkACLKeysDirectory))
14
+ return { networkACLKeys: null }; // Skip
15
+ const foundFiles = (0, utils_1.getFiles)(networkACLKeysDirectory, ['.json']);
16
+ const networkACLKeys = foundFiles
17
+ .map((f) => (0, utils_1.loadJSON)(f, {
18
+ mappings: context.mappings,
19
+ disableKeywordReplacement: context.disableKeywordReplacement,
20
+ }))
21
+ .filter((p) => Object.keys(p).length > 0); // Filter out empty configs
22
+ return {
23
+ networkACLKeys,
24
+ };
25
+ }
26
+ async function dump(context) {
27
+ const { networkACLKeys } = context.assets;
28
+ if (!networkACLKeys)
29
+ return; // Skip, nothing to dump
30
+ if (Array.isArray(networkACLKeys) && networkACLKeys.length === 0) {
31
+ logger_1.default.info('No network ACL keys available, skipping dump');
32
+ return;
33
+ }
34
+ const networkACLKeysDirectory = path_1.default.join(context.filePath, tools_1.constants.NETWORK_ACL_KEYS_DIRECTORY);
35
+ fs_extra_1.default.ensureDirSync(networkACLKeysDirectory);
36
+ // value is write-only — never returned by the API and must not be exported.
37
+ // created_at and updated_at are API-generated metadata; not needed in config.
38
+ const removeKeysFromOutput = ['value', 'created_at', 'updated_at'];
39
+ networkACLKeys.forEach((networkACLKey) => {
40
+ const out = { ...networkACLKey };
41
+ removeKeysFromOutput.forEach((key) => {
42
+ if (key in out)
43
+ delete out[key];
44
+ });
45
+ const fileName = (0, utils_1.sanitize)(networkACLKey.name);
46
+ const filePath = path_1.default.join(networkACLKeysDirectory, `${fileName}.json`);
47
+ (0, utils_1.dumpJSON)(filePath, out);
48
+ });
49
+ }
50
+ const networkACLKeysHandler = {
51
+ parse,
52
+ dump,
53
+ };
54
+ exports.default = networkACLKeysHandler;
@@ -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 templateFilePath = path_1.default.join(promptsDirectory, template);
50
- insertionAcc[name] = (0, utils_1.isFile)(templateFilePath)
51
- ? (0, tools_1.loadFileAndReplaceKeywords)(templateFilePath, {
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()
@@ -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)(markupFile, {
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
- client.custom_login_page = context.loadFile(htmlFileName);
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
- connection.options.email.body = context.loadFile(htmlFileName);
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,
@@ -38,6 +38,7 @@ const hooks_1 = __importDefault(require("./hooks"));
38
38
  const forms_1 = __importDefault(require("./forms"));
39
39
  const flows_1 = __importDefault(require("./flows"));
40
40
  const flowVaultConnections_1 = __importDefault(require("./flowVaultConnections"));
41
+ const networkACLKeys_1 = __importDefault(require("./networkACLKeys"));
41
42
  const networkACLs_1 = __importDefault(require("./networkACLs"));
42
43
  const userAttributeProfiles_1 = __importDefault(require("./userAttributeProfiles"));
43
44
  const connectionProfiles_1 = __importDefault(require("./connectionProfiles"));
@@ -84,6 +85,7 @@ const yamlHandlers = {
84
85
  flows: flows_1.default,
85
86
  flowVaultConnections: flowVaultConnections_1.default,
86
87
  selfServiceProfiles: selfServiceProfiles_1.default,
88
+ networkACLKeys: networkACLKeys_1.default,
87
89
  networkACLs: networkACLs_1.default,
88
90
  userAttributeProfiles: userAttributeProfiles_1.default,
89
91
  connectionProfiles: connectionProfiles_1.default,
@@ -0,0 +1,6 @@
1
+ import { YAMLHandler } from '.';
2
+ import { ParsedAsset } from '../../../types';
3
+ import { NetworkAclKey } from '../../../tools/auth0/handlers/networkACLKeys';
4
+ type ParsedNetworkACLKeys = ParsedAsset<'networkACLKeys', NetworkAclKey[]>;
5
+ declare const networkACLKeysHandler: YAMLHandler<ParsedNetworkACLKeys>;
6
+ export default networkACLKeysHandler;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const logger_1 = __importDefault(require("../../../logger"));
7
+ async function parse(context) {
8
+ const { networkACLKeys } = context.assets;
9
+ if (!networkACLKeys)
10
+ return { networkACLKeys: null };
11
+ return {
12
+ networkACLKeys,
13
+ };
14
+ }
15
+ async function dump(context) {
16
+ let { networkACLKeys } = context.assets;
17
+ if (!networkACLKeys)
18
+ return { networkACLKeys: null };
19
+ if (Array.isArray(networkACLKeys) && networkACLKeys.length === 0) {
20
+ logger_1.default.info('No network ACL keys available, skipping dump');
21
+ return { networkACLKeys: null };
22
+ }
23
+ // value is write-only — never returned by the API and must not be exported.
24
+ // created_at and updated_at are API-generated metadata; not needed in config.
25
+ const removeKeysFromOutput = ['value', 'created_at', 'updated_at'];
26
+ networkACLKeys = networkACLKeys.map((key) => {
27
+ const out = { ...key };
28
+ removeKeysFromOutput.forEach((k) => {
29
+ if (k in out)
30
+ delete out[k];
31
+ });
32
+ return out;
33
+ });
34
+ return {
35
+ networkACLKeys,
36
+ };
37
+ }
38
+ const networkACLKeysHandler = {
39
+ parse,
40
+ dump,
41
+ };
42
+ exports.default = networkACLKeysHandler;
@@ -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 rendererFile = path_1.default.join(context.basePath, filePath);
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,
@@ -10,6 +10,6 @@ export declare const preserveKeywords: ({ localAssets, remoteAssets, keywordMapp
10
10
  remoteAssets: object;
11
11
  keywordMappings: KeywordMappings;
12
12
  auth0Handlers: (Pick<APIHandler, "id" | "type"> & {
13
- identifiers: (string | string[])[];
13
+ identifiers?: (string | string[])[];
14
14
  })[];
15
15
  }) => object;
@@ -164,7 +164,8 @@ const preserveKeywords = ({ localAssets, remoteAssets, keywordMappings, auth0Han
164
164
  if (Object.keys(keywordMappings).length === 0)
165
165
  return remoteAssets;
166
166
  const resourceSpecificIdentifiers = auth0Handlers.reduce((acc, handler) => {
167
- acc[handler.type] = handler.identifiers.flat();
167
+ // Standalone handlers (e.g. clientAuthCredentials) don't extend APIHandler and have no identifiers.
168
+ acc[handler.type] = (handler.identifiers ?? []).flat();
168
169
  return acc;
169
170
  }, {});
170
171
  const addresses = (0, exports.getPreservableFieldsFromAssets)(localAssets, keywordMappings, resourceSpecificIdentifiers, '');