auth0-deploy-cli 9.0.0-beta.2 → 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.
Files changed (129) hide show
  1. package/AGENTS.md +2 -357
  2. package/CHANGELOG.md +185 -10
  3. package/CLAUDE.md +138 -2
  4. package/README.md +1 -1
  5. package/lib/context/defaults.d.ts +7 -0
  6. package/lib/context/defaults.js +74 -30
  7. package/lib/context/directory/handlers/actionModules.js +1 -4
  8. package/lib/context/directory/handlers/actions.js +1 -4
  9. package/lib/context/directory/handlers/attackProtection.js +11 -1
  10. package/lib/context/directory/handlers/branding.js +4 -1
  11. package/lib/context/directory/handlers/clientAuthCredentials.d.ts +5 -0
  12. package/lib/context/directory/handlers/clientAuthCredentials.js +13 -0
  13. package/lib/context/directory/handlers/clientGrants.js +78 -24
  14. package/lib/context/directory/handlers/clients.js +20 -3
  15. package/lib/context/directory/handlers/connections.js +19 -5
  16. package/lib/context/directory/handlers/databases.js +1 -4
  17. package/lib/context/directory/handlers/emailTemplates.js +5 -0
  18. package/lib/context/directory/handlers/guardianEmailFactorSettings.d.ts +5 -0
  19. package/lib/context/directory/handlers/guardianEmailFactorSettings.js +38 -0
  20. package/lib/context/directory/handlers/guardianPhoneFactorSettings.d.ts +5 -0
  21. package/lib/context/directory/handlers/guardianPhoneFactorSettings.js +38 -0
  22. package/lib/context/directory/handlers/guardianSettings.d.ts +5 -0
  23. package/lib/context/directory/handlers/guardianSettings.js +38 -0
  24. package/lib/context/directory/handlers/hooks.js +1 -4
  25. package/lib/context/directory/handlers/index.js +12 -0
  26. package/lib/context/directory/handlers/networkACLKeys.d.ts +6 -0
  27. package/lib/context/directory/handlers/networkACLKeys.js +54 -0
  28. package/lib/context/directory/handlers/prompts.js +7 -3
  29. package/lib/context/directory/handlers/rateLimitPolicies.d.ts +6 -0
  30. package/lib/context/directory/handlers/rateLimitPolicies.js +53 -0
  31. package/lib/context/directory/handlers/rules.js +1 -4
  32. package/lib/context/directory/index.js +1 -4
  33. package/lib/context/index.js +32 -31
  34. package/lib/context/yaml/handlers/attackProtection.js +5 -2
  35. package/lib/context/yaml/handlers/branding.js +4 -1
  36. package/lib/context/yaml/handlers/clientAuthCredentials.d.ts +5 -0
  37. package/lib/context/yaml/handlers/clientAuthCredentials.js +13 -0
  38. package/lib/context/yaml/handlers/clients.js +19 -1
  39. package/lib/context/yaml/handlers/connections.js +8 -1
  40. package/lib/context/yaml/handlers/flows.js +3 -0
  41. package/lib/context/yaml/handlers/forms.js +3 -0
  42. package/lib/context/yaml/handlers/guardianEmailFactorSettings.d.ts +5 -0
  43. package/lib/context/yaml/handlers/guardianEmailFactorSettings.js +15 -0
  44. package/lib/context/yaml/handlers/guardianPhoneFactorSettings.d.ts +5 -0
  45. package/lib/context/yaml/handlers/guardianPhoneFactorSettings.js +15 -0
  46. package/lib/context/yaml/handlers/guardianSettings.d.ts +5 -0
  47. package/lib/context/yaml/handlers/guardianSettings.js +15 -0
  48. package/lib/context/yaml/handlers/index.js +12 -0
  49. package/lib/context/yaml/handlers/networkACLKeys.d.ts +6 -0
  50. package/lib/context/yaml/handlers/networkACLKeys.js +42 -0
  51. package/lib/context/yaml/handlers/prompts.js +3 -1
  52. package/lib/context/yaml/handlers/rateLimitPolicies.d.ts +6 -0
  53. package/lib/context/yaml/handlers/rateLimitPolicies.js +27 -0
  54. package/lib/context/yaml/index.js +85 -7
  55. package/lib/keywordPreservation.d.ts +1 -1
  56. package/lib/keywordPreservation.js +2 -1
  57. package/lib/readonly.js +5 -0
  58. package/lib/tools/auth0/handlers/attackProtection.d.ts +12 -0
  59. package/lib/tools/auth0/handlers/attackProtection.js +66 -6
  60. package/lib/tools/auth0/handlers/clientAuthCredentials.d.ts +27 -0
  61. package/lib/tools/auth0/handlers/clientAuthCredentials.js +234 -0
  62. package/lib/tools/auth0/handlers/clientAuthCredentialsPre.d.ts +38 -0
  63. package/lib/tools/auth0/handlers/clientAuthCredentialsPre.js +123 -0
  64. package/lib/tools/auth0/handlers/clientGrants.d.ts +1 -1
  65. package/lib/tools/auth0/handlers/clients.d.ts +106 -0
  66. package/lib/tools/auth0/handlers/clients.js +207 -3
  67. package/lib/tools/auth0/handlers/connectionProfiles.d.ts +27 -0
  68. package/lib/tools/auth0/handlers/connectionProfiles.js +30 -0
  69. package/lib/tools/auth0/handlers/connections.d.ts +34 -6
  70. package/lib/tools/auth0/handlers/connections.js +75 -11
  71. package/lib/tools/auth0/handlers/databases.d.ts +28 -0
  72. package/lib/tools/auth0/handlers/databases.js +58 -24
  73. package/lib/tools/auth0/handlers/default.d.ts +23 -0
  74. package/lib/tools/auth0/handlers/default.js +47 -29
  75. package/lib/tools/auth0/handlers/eventStreams.js +10 -2
  76. package/lib/tools/auth0/handlers/guardianEmailFactorSettings.d.ts +22 -0
  77. package/lib/tools/auth0/handlers/guardianEmailFactorSettings.js +77 -0
  78. package/lib/tools/auth0/handlers/guardianFactorTemplates.js +13 -4
  79. package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +11 -9
  80. package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +11 -9
  81. package/lib/tools/auth0/handlers/guardianPhoneFactorSettings.d.ts +22 -0
  82. package/lib/tools/auth0/handlers/guardianPhoneFactorSettings.js +77 -0
  83. package/lib/tools/auth0/handlers/guardianPolicies.js +11 -1
  84. package/lib/tools/auth0/handlers/guardianSettings.d.ts +30 -0
  85. package/lib/tools/auth0/handlers/guardianSettings.js +85 -0
  86. package/lib/tools/auth0/handlers/hooks.js +1 -1
  87. package/lib/tools/auth0/handlers/index.js +16 -0
  88. package/lib/tools/auth0/handlers/networkACLKeys.d.ts +45 -0
  89. package/lib/tools/auth0/handlers/networkACLKeys.js +177 -0
  90. package/lib/tools/auth0/handlers/networkACLs.d.ts +132 -2
  91. package/lib/tools/auth0/handlers/networkACLs.js +100 -16
  92. package/lib/tools/auth0/handlers/organizations.d.ts +33 -1
  93. package/lib/tools/auth0/handlers/organizations.js +177 -19
  94. package/lib/tools/auth0/handlers/phoneProvider.js +10 -2
  95. package/lib/tools/auth0/handlers/phoneTemplates.d.ts +4 -1
  96. package/lib/tools/auth0/handlers/phoneTemplates.js +67 -10
  97. package/lib/tools/auth0/handlers/prompts.d.ts +17 -0
  98. package/lib/tools/auth0/handlers/prompts.js +47 -14
  99. package/lib/tools/auth0/handlers/rateLimitPolicies.d.ts +81 -0
  100. package/lib/tools/auth0/handlers/rateLimitPolicies.js +203 -0
  101. package/lib/tools/auth0/handlers/resourceServers.d.ts +48 -0
  102. package/lib/tools/auth0/handlers/resourceServers.js +56 -2
  103. package/lib/tools/auth0/handlers/riskAssessment.d.ts +1 -1
  104. package/lib/tools/auth0/handlers/riskAssessment.js +19 -1
  105. package/lib/tools/auth0/handlers/roles.d.ts +4 -0
  106. package/lib/tools/auth0/handlers/roles.js +27 -9
  107. package/lib/tools/auth0/handlers/rules.js +19 -7
  108. package/lib/tools/auth0/handlers/scimHandler.js +8 -3
  109. package/lib/tools/auth0/handlers/tenant.d.ts +45 -0
  110. package/lib/tools/auth0/handlers/tenant.js +48 -0
  111. package/lib/tools/auth0/handlers/themes.d.ts +37 -1
  112. package/lib/tools/auth0/handlers/themes.js +64 -0
  113. package/lib/tools/auth0/handlers/tokenExchangeProfiles.js +3 -1
  114. package/lib/tools/auth0/index.js +12 -6
  115. package/lib/tools/calculateDryRunChanges.js +12 -8
  116. package/lib/tools/constants.d.ts +3 -0
  117. package/lib/tools/constants.js +8 -0
  118. package/lib/tools/utils.d.ts +3 -0
  119. package/lib/tools/utils.js +41 -2
  120. package/lib/types.d.ts +10 -1
  121. package/lib/utils.d.ts +6 -0
  122. package/lib/utils.js +17 -2
  123. package/package.json +2 -2
  124. package/references/code-style.md +133 -0
  125. package/references/commands.md +98 -0
  126. package/references/docs-update.md +34 -0
  127. package/references/git-workflow.md +135 -0
  128. package/references/pitfalls.md +62 -0
  129. package/references/testing.md +118 -0
@@ -34,6 +34,6 @@ export type RiskAssessment = {
34
34
  export default class RiskAssessmentHandler extends DefaultAPIHandler {
35
35
  existing: RiskAssessment;
36
36
  constructor(config: DefaultAPIHandler);
37
- getType(): Promise<RiskAssessment>;
37
+ getType(): Promise<RiskAssessment | null>;
38
38
  processChanges(assets: Assets): Promise<void>;
39
39
  }
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.schema = void 0;
7
7
  const default_1 = __importDefault(require("./default"));
8
8
  const auth0_1 = require("auth0");
9
+ const logger_1 = __importDefault(require("../../../logger"));
10
+ const utils_1 = require("../../utils");
9
11
  exports.schema = {
10
12
  type: 'object',
11
13
  properties: {
@@ -50,6 +52,9 @@ class RiskAssessmentHandler extends default_1.default {
50
52
  if (err instanceof auth0_1.ManagementError && err?.statusCode === 404) {
51
53
  return { remember_for: 0 };
52
54
  }
55
+ if ((0, utils_1.isInsufficientEntitlementError)(err)) {
56
+ return { remember_for: 0 };
57
+ }
53
58
  throw err;
54
59
  }),
55
60
  ]);
@@ -71,6 +76,10 @@ class RiskAssessmentHandler extends default_1.default {
71
76
  this.existing = riskAssessment;
72
77
  return this.existing;
73
78
  }
79
+ if ((0, utils_1.isInsufficientEntitlementError)(err)) {
80
+ logger_1.default.warn('Skipping riskAssessment export: tenant does not have Adaptive MFA entitlement');
81
+ return null;
82
+ }
74
83
  throw err;
75
84
  }
76
85
  }
@@ -87,7 +96,16 @@ class RiskAssessmentHandler extends default_1.default {
87
96
  if (riskAssessment.new_device) {
88
97
  updates.push(this.client.riskAssessments.settings.newDevice.update(riskAssessment.new_device));
89
98
  }
90
- await Promise.all(updates);
99
+ try {
100
+ await Promise.all(updates);
101
+ }
102
+ catch (err) {
103
+ if ((0, utils_1.isInsufficientEntitlementError)(err)) {
104
+ logger_1.default.warn('Skipping riskAssessment deploy: tenant does not have Adaptive MFA entitlement');
105
+ return;
106
+ }
107
+ throw err;
108
+ }
91
109
  this.updated += 1;
92
110
  this.didUpdate(riskAssessment);
93
111
  }
@@ -14,6 +14,10 @@ export declare const schema: {
14
14
  description: {
15
15
  type: string;
16
16
  };
17
+ type: {
18
+ type: string;
19
+ enum: string[];
20
+ };
17
21
  permissions: {
18
22
  type: string;
19
23
  items: {
@@ -56,6 +56,7 @@ exports.schema = {
56
56
  name: { type: 'string' },
57
57
  id: { type: 'string' },
58
58
  description: { type: 'string' },
59
+ type: { type: 'string', enum: ['tenant', 'organization'] },
59
60
  permissions: {
60
61
  type: 'array',
61
62
  items: {
@@ -81,6 +82,9 @@ class RolesHandler extends default_1.default {
81
82
  async createRole(data) {
82
83
  const role = { ...data };
83
84
  delete role.permissions;
85
+ // deploy-cli only manages tenant-level roles, so `type` is stripped on write; it
86
+ // defaults to `tenant` on create. This keeps exports (which carry `type`) round-trippable.
87
+ delete role.type;
84
88
  const created = await this.client.roles.create(role);
85
89
  if (created.id && typeof data.permissions !== 'undefined' && data.permissions.length > 0) {
86
90
  await this.client.roles.permissions.add(created.id, { permissions: data.permissions });
@@ -128,19 +132,30 @@ class RolesHandler extends default_1.default {
128
132
  }
129
133
  }
130
134
  async updateRole(data, roles) {
131
- const existingRole = await roles.find((roleDataForUpdate) => roleDataForUpdate.name === data.name);
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);
132
138
  const params = { id: data.id };
133
- const newPermissions = data.permissions;
139
+ const newPermissions = data.permissions || [];
134
140
  delete data.permissions;
135
141
  delete data.id;
136
- await this.client.roles.update(params.id, data);
137
- if (typeof existingRole.permissions !== 'undefined' && existingRole.permissions.length > 0) {
138
- await this.client.roles.permissions.delete(params.id, {
139
- permissions: existingRole.permissions,
140
- });
142
+ // deploy-cli only manages tenant-level roles, so `type` is stripped on write; this
143
+ // lets files that already carry it (from a prior export) be imported cleanly.
144
+ delete data.type;
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);
141
156
  }
142
- if (typeof newPermissions !== 'undefined' && newPermissions.length > 0) {
143
- await this.client.roles.permissions.add(params.id, { permissions: newPermissions });
157
+ if (permissionsToRemove.length > 0) {
158
+ await (0, default_1.retryWithExponentialBackoff)(() => this.client.roles.permissions.delete(params.id, { permissions: permissionsToRemove }), retryConfig);
144
159
  }
145
160
  return params;
146
161
  }
@@ -164,9 +179,12 @@ class RolesHandler extends default_1.default {
164
179
  return this.existing;
165
180
  }
166
181
  try {
182
+ // Only manage tenant-level roles; exclude org-scoped roles surfaced when
183
+ // the api2_org_level_roles_ea flag is enabled.
167
184
  const roles = await (0, client_1.paginate)(this.client.roles.list, {
168
185
  paginate: true,
169
186
  include_totals: true,
187
+ type: 'tenant',
170
188
  });
171
189
  for (let index = 0; index < roles.length; index++) {
172
190
  const allPermission = [];
@@ -9,6 +9,7 @@ const utils_1 = require("../../utils");
9
9
  const default_1 = __importDefault(require("./default"));
10
10
  const logger_1 = __importDefault(require("../../../logger"));
11
11
  const calculateChanges_1 = require("../../calculateChanges");
12
+ const calculateDryRunChanges_1 = require("../../calculateDryRunChanges");
12
13
  const client_1 = require("../client");
13
14
  exports.excludeSchema = {
14
15
  type: 'array',
@@ -135,13 +136,24 @@ class RulesHandler extends default_1.default {
135
136
  };
136
137
  }
137
138
  async dryRunChanges(assets) {
138
- const { del, update, create, conflicts } = await this.calcChanges(assets);
139
- return {
140
- del,
141
- update,
142
- create,
143
- conflicts,
144
- };
139
+ let { rules } = assets;
140
+ if (!rules) {
141
+ return { del: [], create: [], conflicts: [], update: [] };
142
+ }
143
+ const excludedRules = (assets.exclude && assets.exclude.rules) || [];
144
+ rules = rules.filter((r) => !excludedRules.includes(r.name));
145
+ let existing = await this.getType();
146
+ if (existing === null) {
147
+ return { del: [], create: [], conflicts: [], update: [] };
148
+ }
149
+ existing = existing.filter((r) => !excludedRules.includes(r.name));
150
+ return (0, calculateDryRunChanges_1.calculateDryRunChanges)({
151
+ type: this.type,
152
+ assets: rules,
153
+ existing,
154
+ identifiers: this.identifiers,
155
+ ignoreDryRunFields: this.getEffectiveIgnoreDryRunFields(),
156
+ });
145
157
  }
146
158
  async validate(assets) {
147
159
  const { rules } = assets;
@@ -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: connections || [],
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) => {
@@ -79,6 +79,51 @@ export declare const schema: {
79
79
  enum: string[];
80
80
  description: string;
81
81
  };
82
+ country_codes: {
83
+ type: string[];
84
+ description: string;
85
+ properties: {
86
+ list: {
87
+ type: string;
88
+ description: string;
89
+ items: {
90
+ type: string;
91
+ pattern: string;
92
+ };
93
+ minItems: number;
94
+ uniqueItems: boolean;
95
+ };
96
+ mode: {
97
+ type: string;
98
+ enum: string[];
99
+ description: string;
100
+ };
101
+ };
102
+ required: string[];
103
+ additionalProperties: boolean;
104
+ };
105
+ sessions: {
106
+ type: string;
107
+ description: string;
108
+ properties: {
109
+ anonymous: {
110
+ type: string;
111
+ description: string;
112
+ properties: {
113
+ lifetime_in_minutes: {
114
+ type: string;
115
+ description: string;
116
+ };
117
+ activate_cookie: {
118
+ type: string;
119
+ description: string;
120
+ };
121
+ };
122
+ additionalProperties: boolean;
123
+ };
124
+ };
125
+ additionalProperties: boolean;
126
+ };
82
127
  };
83
128
  };
84
129
  export interface Tenant extends Management.GetTenantSettingsResponseContent {
@@ -106,6 +106,53 @@ exports.schema = {
106
106
  enum: ['strict', 'permissive'],
107
107
  description: 'Indicates the security mode for new clients created through the Dynamic Client Registration endpoint.',
108
108
  },
109
+ country_codes: {
110
+ type: ['object', 'null'],
111
+ description: 'Phone country code configuration for identifier input. Set to `null` to remove filtering (allow all countries). Requires the `tenant_country_codes_filtering` feature flag to be enabled on the tenant.',
112
+ properties: {
113
+ list: {
114
+ type: 'array',
115
+ description: 'ISO 3166-1 alpha-2 country codes (e.g. `US`, `GB`).',
116
+ items: {
117
+ type: 'string',
118
+ pattern: '^[A-Z]{2}$',
119
+ },
120
+ minItems: 1,
121
+ uniqueItems: true,
122
+ },
123
+ mode: {
124
+ type: 'string',
125
+ enum: ['allow', 'deny'],
126
+ description: 'Whether the list is an allowlist or denylist.',
127
+ },
128
+ },
129
+ required: ['list', 'mode'],
130
+ additionalProperties: false,
131
+ },
132
+ sessions: {
133
+ type: 'object',
134
+ description: 'Sessions related settings for the tenant.',
135
+ properties: {
136
+ anonymous: {
137
+ type: 'object',
138
+ description: 'Anonymous session settings for the tenant.',
139
+ properties: {
140
+ lifetime_in_minutes: {
141
+ type: 'integer',
142
+ description: 'The lifetime of an anonymous session, in minutes.',
143
+ },
144
+ activate_cookie: {
145
+ type: 'boolean',
146
+ description: 'Whether to activate the anonymous session cookie.',
147
+ },
148
+ },
149
+ // Permissive to match the parent `sessions` object so new Management API
150
+ // session fields round-trip through export/import without validation failure.
151
+ additionalProperties: true,
152
+ },
153
+ },
154
+ additionalProperties: true,
155
+ },
109
156
  },
110
157
  };
111
158
  const blockPageKeys = [
@@ -147,6 +194,7 @@ exports.allowedTenantFlags = [
147
194
  'require_pushed_authorization_requests',
148
195
  'mfa_show_factor_list_on_enrollment',
149
196
  'improved_signup_bot_detection_in_classic',
197
+ 'use_scope_descriptions_for_consent',
150
198
  ];
151
199
  const removeUnallowedTenantFlags = (proposedFlags) => {
152
200
  if (proposedFlags === undefined)
@@ -1,5 +1,5 @@
1
1
  import { Management } from 'auth0';
2
- import { Assets } from '../../../types';
2
+ import { Assets, CalculatedChanges } from '../../../types';
3
3
  import DefaultHandler from './default';
4
4
  /**
5
5
  * Schema
@@ -179,6 +179,41 @@ export declare const schema: {
179
179
  pattern: string;
180
180
  type: string;
181
181
  };
182
+ identifiers: {
183
+ additionalProperties: boolean;
184
+ description: string;
185
+ properties: {
186
+ login_display: {
187
+ description: string;
188
+ enum: string[];
189
+ type: string;
190
+ };
191
+ otp_autocomplete: {
192
+ description: string;
193
+ type: string;
194
+ };
195
+ phone_display: {
196
+ additionalProperties: boolean;
197
+ description: string;
198
+ properties: {
199
+ formatting: {
200
+ description: string;
201
+ enum: string[];
202
+ type: string;
203
+ };
204
+ masking: {
205
+ description: string;
206
+ enum: string[];
207
+ type: string;
208
+ };
209
+ };
210
+ required: string[];
211
+ type: string;
212
+ };
213
+ };
214
+ required: string[];
215
+ type: string;
216
+ };
182
217
  fonts: {
183
218
  additionalProperties: boolean;
184
219
  properties: {
@@ -375,6 +410,7 @@ export default class ThemesHandler extends DefaultHandler {
375
410
  existing: Theme[] | null;
376
411
  constructor(options: DefaultHandler);
377
412
  objString(theme: Theme): string;
413
+ dryRunChanges(assets: Assets): Promise<CalculatedChanges>;
378
414
  getType(): Promise<Theme[] | null>;
379
415
  processChanges(assets: Assets): Promise<void>;
380
416
  deleteThemes(): Promise<void>;
@@ -46,6 +46,7 @@ exports.schema = void 0;
46
46
  const logger_1 = __importDefault(require("../../../logger"));
47
47
  const default_1 = __importStar(require("./default"));
48
48
  const utils_1 = require("../../utils");
49
+ const calculateDryRunChanges_1 = require("../../calculateDryRunChanges");
49
50
  /**
50
51
  * Schema
51
52
  */
@@ -251,6 +252,41 @@ exports.schema = {
251
252
  pattern: '^[^<>]*$',
252
253
  type: 'string',
253
254
  },
255
+ identifiers: {
256
+ additionalProperties: false,
257
+ description: 'Identifier display settings on the theme. Requires the `universal_login_theme_identifiers` feature flag to be enabled on the tenant.',
258
+ properties: {
259
+ login_display: {
260
+ description: 'Login display mode',
261
+ enum: ['separate', 'unified'],
262
+ type: 'string',
263
+ },
264
+ otp_autocomplete: {
265
+ description: 'Whether OTP autocomplete is enabled',
266
+ type: 'boolean',
267
+ },
268
+ phone_display: {
269
+ additionalProperties: false,
270
+ description: 'Phone number display settings',
271
+ properties: {
272
+ formatting: {
273
+ description: 'Phone number formatting style',
274
+ enum: ['international', 'regional'],
275
+ type: 'string',
276
+ },
277
+ masking: {
278
+ description: 'Phone number masking strategy',
279
+ enum: ['hide_country_code', 'mask_digits', 'show_all'],
280
+ type: 'string',
281
+ },
282
+ },
283
+ required: ['formatting', 'masking'],
284
+ type: 'object',
285
+ },
286
+ },
287
+ required: ['login_display', 'otp_autocomplete', 'phone_display'],
288
+ type: 'object',
289
+ },
254
290
  fonts: {
255
291
  additionalProperties: false,
256
292
  properties: {
@@ -470,6 +506,34 @@ class ThemesHandler extends default_1.default {
470
506
  objString(theme) {
471
507
  return theme.displayName || JSON.stringify(theme);
472
508
  }
509
+ async dryRunChanges(assets) {
510
+ const { themes } = assets;
511
+ if (!themes) {
512
+ return { del: [], create: [], conflicts: [], update: [] };
513
+ }
514
+ const existing = await this.getType();
515
+ // getType() returns null when Universal Login customizations are not enabled
516
+ // on the tenant (the 400 "no-code not enabled" path in getThemes).
517
+ if (existing === null) {
518
+ return { del: [], create: [], conflicts: [], update: [] };
519
+ }
520
+ // Themes are singletons — at most one per tenant. If the local theme has no
521
+ // themeId (stripped during export when --export_ids is not set), backfill it
522
+ // from the remote so the matcher can find a match without requiring --export_ids.
523
+ const normalizedThemes = themes.map((theme) => {
524
+ if (!theme.themeId && existing.length > 0 && existing[0].themeId) {
525
+ return { ...theme, themeId: existing[0].themeId };
526
+ }
527
+ return theme;
528
+ });
529
+ return (0, calculateDryRunChanges_1.calculateDryRunChanges)({
530
+ type: this.type,
531
+ assets: normalizedThemes,
532
+ existing,
533
+ identifiers: this.identifiers,
534
+ ignoreDryRunFields: this.getEffectiveIgnoreDryRunFields(),
535
+ });
536
+ }
473
537
  async getType() {
474
538
  if (!this.existing) {
475
539
  this.existing = await this.getThemes();
@@ -138,7 +138,9 @@ class TokenExchangeProfilesHandler extends default_1.default {
138
138
  }
139
139
  catch (err) {
140
140
  if (err.statusCode === 403) {
141
- logger_1.default.warn('Token Exchange Profiles feature is not available on this tenant. Please contact Auth0 support to enable this feature.');
141
+ logger_1.default.warn(`Cannot retrieve tokenExchangeProfiles: ${err.message}. ` +
142
+ `Verify the M2M application has the 'read:token_exchange_profiles' scope and ` +
143
+ `your subscription plan includes Token Exchange Profiles.`);
142
144
  return [];
143
145
  }
144
146
  throw err;
@@ -11,13 +11,10 @@ const promise_pool_executor_1 = require("promise-pool-executor");
11
11
  const client_1 = __importDefault(require("./client"));
12
12
  const schema_1 = __importDefault(require("./schema"));
13
13
  const handlers_1 = __importDefault(require("./handlers"));
14
- // Cache the compiled AJV validator to avoid recompiling the schema on every validate() call.
15
- // Schema compilation is expensive (~5s) due to the complexity of the combined resource schema.
16
- const ajv = new ajv_1.default({ useDefaults: true, nullable: true });
17
- const validateSchema = ajv.compile(schema_1.default);
18
14
  const calculateDryRunChanges_1 = require("../calculateDryRunChanges");
19
15
  const utils_1 = require("../../utils");
20
16
  const utils_2 = require("../utils");
17
+ const logger_1 = __importDefault(require("../../logger"));
21
18
  /**
22
19
  * Sorts handlers by their @order decorator metadata for a given stage.
23
20
  * Handlers are sorted in ascending order (lower values execute first).
@@ -109,12 +106,13 @@ class Auth0 {
109
106
  }
110
107
  }
111
108
  async validate() {
109
+ const ajv = new ajv_1.default({ useDefaults: true, nullable: true });
112
110
  const nonNullAssets = Object.keys(this.assets)
113
111
  .filter((k) => this.assets[k] != null)
114
112
  .reduce((a, k) => ({ ...a, [k]: this.assets[k] }), {});
115
- const valid = validateSchema(nonNullAssets);
113
+ const valid = ajv.validate(schema_1.default, nonNullAssets);
116
114
  if (!valid) {
117
- throw new Error(`Schema validation failed loading ${JSON.stringify(validateSchema.errors, null, 4)}`);
115
+ throw new Error(`Schema validation failed loading ${JSON.stringify(ajv.errors, null, 4)}`);
118
116
  }
119
117
  await this.runStage('validate');
120
118
  }
@@ -148,6 +146,14 @@ class Auth0 {
148
146
  data: this.handlers,
149
147
  generator: (handler) => (async () => {
150
148
  try {
149
+ // Every handler in the dry-run loop is expected to implement dryRunChanges.
150
+ // Guard against non-conforming handlers (e.g. deploy-only handlers that don't
151
+ // extend the base APIHandler) so a missing method skips the handler instead of
152
+ // crashing the whole dry run. Logged loudly so an omitted preview is visible.
153
+ if (typeof handler.dryRunChanges !== 'function') {
154
+ logger_1.default.warn(`Handler "${handler.type}" does not implement dryRunChanges; skipping it in the dry-run preview. Changes for this resource will still be applied during a real import.`);
155
+ return;
156
+ }
151
157
  const detailedChanges = [];
152
158
  let created = 0;
153
159
  let updated = 0;
@@ -108,6 +108,13 @@ function normalizeArrayValues(values) {
108
108
  .map((value) => normalizeValue(value))
109
109
  .sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
110
110
  }
111
+ /**
112
+ * Serializes a value to a human-readable string for use in diff messages.
113
+ * Objects and arrays are JSON-stringified to avoid "[object Object]" output.
114
+ */
115
+ function formatDiffValue(value) {
116
+ return typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value);
117
+ }
111
118
  /**
112
119
  * Writes the accumulated diff log to a JSON file on disk.
113
120
  * Useful for CI pipelines that want a machine-readable dry-run report.
@@ -179,7 +186,7 @@ function getObjectDifferences(localObj, remoteObj, keyObjPath = '', resourceType
179
186
  differences.push(...nestedDifferences);
180
187
  }
181
188
  else if (item !== normalizedRemoteValue[index]) {
182
- const message = `Array item difference at [${currentPath}[${index}]]: local:${item} vs remote:${normalizedRemoteValue[index]}`;
189
+ const message = `Array item difference at [${currentPath}[${index}]]: local:${formatDiffValue(item)} vs remote:${formatDiffValue(normalizedRemoteValue[index])}`;
183
190
  differences.push(message);
184
191
  }
185
192
  });
@@ -204,13 +211,10 @@ function getObjectDifferences(localObj, remoteObj, keyObjPath = '', resourceType
204
211
  differences.push(...nestedDifferences);
205
212
  return;
206
213
  }
207
- // Compare primitive values — omit array indices from path to reduce log noise
214
+ // Compare primitive values
208
215
  if (localValue !== remoteValue) {
209
- let arrayPathRegex = new RegExp(/\[\d+\]/g);
210
- if (!arrayPathRegex.test(currentPath)) {
211
- const message = `Value difference for [${currentPath}]: local:${localValue} vs remote:${remoteValue}`;
212
- differences.push(message);
213
- }
216
+ const message = `Value difference for [${currentPath}]: local:${localValue} vs remote:${remoteValue}`;
217
+ differences.push(message);
214
218
  }
215
219
  });
216
220
  return differences;
@@ -242,7 +246,7 @@ function calculateDryRunChanges({ type, assets, existing, identifiers = ['id', '
242
246
  const create = [];
243
247
  const conflicts = [];
244
248
  const localAssets = (Array.isArray(assets) ? [...assets] : [assets]).map((asset) => type === 'tenant' ? normalizeTenantForDryRun(asset) : asset);
245
- const remoteAssets = (Array.isArray(existing) ? [...existing] : [existing]).map((asset) => (type === 'tenant' ? normalizeTenantForDryRun(asset) : asset));
249
+ const remoteAssets = (Array.isArray(existing) ? [...existing] : existing ? [existing] : []).map((asset) => (type === 'tenant' ? normalizeTenantForDryRun(asset) : asset));
246
250
  // Helper: returns true if a local and remote asset share at least one identifier value
247
251
  const assetsMatch = (localAsset, remoteAsset) => identifiers.some((id) => {
248
252
  if (Array.isArray(id)) {
@@ -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;
@@ -86,10 +87,12 @@ declare const constants: {
86
87
  FLOWS_VAULT_DIRECTORY: string;
87
88
  SELF_SERVICE_PROFILE_DIRECTORY: string;
88
89
  NETWORK_ACLS_DIRECTORY: string;
90
+ NETWORK_ACL_KEYS_DIRECTORY: string;
89
91
  USER_ATTRIBUTE_PROFILES_DIRECTORY: string;
90
92
  CONNECTION_PROFILES_DIRECTORY: string;
91
93
  TOKEN_EXCHANGE_PROFILES_DIRECTORY: string;
92
94
  SUPPLEMENTAL_SIGNALS_DIRECTORY: string;
95
+ RATE_LIMIT_POLICIES_DIRECTORY: string;
93
96
  EVENT_STREAMS_DIRECTORY: string;
94
97
  };
95
98
  export default constants;
@@ -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',
@@ -68,6 +70,7 @@ const constants = {
68
70
  'custom-token-exchange',
69
71
  'login-post-identifier',
70
72
  'signup-post-identifier',
73
+ 'post-credential-validation',
71
74
  ],
72
75
  EMAIL_TEMPLATES_DIRECTORY: 'emails',
73
76
  EMAIL_VERIFY,
@@ -83,6 +86,7 @@ const constants = {
83
86
  EMAIL_MFA_OOB_CODE,
84
87
  EMAIL_USER_INVITATION,
85
88
  EMAIL_ASYNC_APPROVAL,
89
+ EMAIL_AUTH_BY_CODE,
86
90
  GUARDIAN_DIRECTORY: 'guardian',
87
91
  GUARDIAN_FACTORS_DIRECTORY: 'factors',
88
92
  GUARDIAN_PROVIDERS_DIRECTORY: 'providers',
@@ -180,6 +184,8 @@ const constants = {
180
184
  `${EMAIL_USER_INVITATION}.html`,
181
185
  `${EMAIL_ASYNC_APPROVAL}.json`,
182
186
  `${EMAIL_ASYNC_APPROVAL}.html`,
187
+ `${EMAIL_AUTH_BY_CODE}.json`,
188
+ `${EMAIL_AUTH_BY_CODE}.html`,
183
189
  ],
184
190
  SUPPORTED_BRANDING_TEMPLATES: [UNIVERSAL_LOGIN_TEMPLATE],
185
191
  LOG_STREAMS_DIRECTORY: 'log-streams',
@@ -216,10 +222,12 @@ const constants = {
216
222
  FLOWS_VAULT_DIRECTORY: 'flow-vault-connections',
217
223
  SELF_SERVICE_PROFILE_DIRECTORY: 'self-service-profiles',
218
224
  NETWORK_ACLS_DIRECTORY: 'network-acls',
225
+ NETWORK_ACL_KEYS_DIRECTORY: 'network-acl-keys',
219
226
  USER_ATTRIBUTE_PROFILES_DIRECTORY: 'user-attribute-profiles',
220
227
  CONNECTION_PROFILES_DIRECTORY: 'connection-profiles',
221
228
  TOKEN_EXCHANGE_PROFILES_DIRECTORY: 'token-exchange-profiles',
222
229
  SUPPLEMENTAL_SIGNALS_DIRECTORY: 'supplemental-signals',
230
+ RATE_LIMIT_POLICIES_DIRECTORY: 'rate-limit-policies',
223
231
  EVENT_STREAMS_DIRECTORY: 'event-streams',
224
232
  };
225
233
  exports.default = constants;