auth0-deploy-cli 8.41.0 → 8.42.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 CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [8.42.0] - 2026-07-30
11
+
12
+ ### Added
13
+
14
+ - Add UL identifier input support for themes and tenant settings. [#1433]
15
+ - Add `token_vault_privileged_access` support for clients. [#1430]
16
+ - Add `auth0_managed` field support for network ACLs (Curated Blocklists - EA). [#1435]
17
+ - Add Cross App Access (XAA) resource app support for connections and clients. [#1431]
18
+ - Add `cross_app_access_requesting_app` support for connections (EA). [#1422]
19
+
20
+ ### Fixed
21
+
22
+ - Prevent dry-run crash on `clientAuthCredentials` handler. [#1438]
23
+ - Resolve spurious dry-run diffs for rules, themes, and hooks. [#1437]
24
+ - Strip unresolved placeholders on deploy instead of throwing. [#1436]
25
+ - Handle 403 entitlement errors for bot detection and captcha on export. [#1434]
26
+ - Warn and skip deprecated guardian phone provider on 403 during import. [#1429]
27
+ - Add `use_scope_descriptions_for_consent` to allowed tenant flags. [#1428]
28
+
10
29
  ## [8.41.0] - 2026-07-16
11
30
 
12
31
  ### Added
@@ -1876,8 +1895,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1876
1895
  [#1416]: https://github.com/auth0/auth0-deploy-cli/issues/1416
1877
1896
  [#1417]: https://github.com/auth0/auth0-deploy-cli/issues/1417
1878
1897
  [#1421]: https://github.com/auth0/auth0-deploy-cli/issues/1421
1898
+ [#1422]: https://github.com/auth0/auth0-deploy-cli/issues/1422
1879
1899
  [#1425]: https://github.com/auth0/auth0-deploy-cli/issues/1425
1880
- [Unreleased]: https://github.com/auth0/auth0-deploy-cli/compare/v8.41.0...HEAD
1900
+ [#1428]: https://github.com/auth0/auth0-deploy-cli/issues/1428
1901
+ [#1429]: https://github.com/auth0/auth0-deploy-cli/issues/1429
1902
+ [#1430]: https://github.com/auth0/auth0-deploy-cli/issues/1430
1903
+ [#1431]: https://github.com/auth0/auth0-deploy-cli/issues/1431
1904
+ [#1433]: https://github.com/auth0/auth0-deploy-cli/issues/1433
1905
+ [#1434]: https://github.com/auth0/auth0-deploy-cli/issues/1434
1906
+ [#1435]: https://github.com/auth0/auth0-deploy-cli/issues/1435
1907
+ [#1436]: https://github.com/auth0/auth0-deploy-cli/issues/1436
1908
+ [#1437]: https://github.com/auth0/auth0-deploy-cli/issues/1437
1909
+ [#1438]: https://github.com/auth0/auth0-deploy-cli/issues/1438
1910
+ [Unreleased]: https://github.com/auth0/auth0-deploy-cli/compare/v8.42.0...HEAD
1911
+ [8.42.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.41.0...v8.42.0
1881
1912
  [8.41.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.40.0...v8.41.0
1882
1913
  [8.40.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.39.0...v8.40.0
1883
1914
  [8.39.0]: https://github.com/auth0/auth0-deploy-cli/compare/v8.38.0...v8.39.0
@@ -243,14 +243,25 @@ class AttackProtectionHandler extends default_1.default {
243
243
  let botDetection = null;
244
244
  let captcha = null;
245
245
  try {
246
- [botDetection, captcha] = await Promise.all([
247
- this.client.attackProtection.botDetection.get(),
248
- this.client.attackProtection.captcha.get(),
249
- ]);
246
+ botDetection = await this.client.attackProtection.botDetection.get();
250
247
  }
251
248
  catch (err) {
252
249
  if (err.statusCode === 403) {
253
- logger_1.default.warn('Bot Detection API are not enabled for this tenant. Please verify `scope` or contact Auth0 support to enable this feature.');
250
+ logger_1.default.warn("Bot detection is not available on this tenant's current plan. Skipping bot detection settings.");
251
+ }
252
+ else {
253
+ throw err;
254
+ }
255
+ }
256
+ try {
257
+ captcha = await this.client.attackProtection.captcha.get();
258
+ }
259
+ catch (err) {
260
+ if (err.statusCode === 403) {
261
+ logger_1.default.warn("Captcha is not available on this tenant's current plan. Skipping captcha settings.");
262
+ }
263
+ else {
264
+ throw err;
254
265
  }
255
266
  }
256
267
  const attackProtection = {
@@ -1,4 +1,4 @@
1
- import { Assets, Auth0APIClient } from '../../../types';
1
+ import { Assets, Auth0APIClient, CalculatedChanges } from '../../../types';
2
2
  import { ConfigFunction } from '../../../configFactory';
3
3
  export declare const schema: {
4
4
  type: string;
@@ -20,6 +20,7 @@ export default class ClientAuthCredentialsHandler {
20
20
  [x: string]: null;
21
21
  }>;
22
22
  validate(): Promise<null>;
23
+ dryRunChanges(_assets: Assets): Promise<CalculatedChanges>;
23
24
  processChanges(assets: Assets): Promise<void>;
24
25
  private inferCredentialType;
25
26
  private inferMethodFromType;
@@ -39,6 +39,13 @@ class ClientAuthCredentialsHandler {
39
39
  async validate() {
40
40
  return null;
41
41
  }
42
+ // Credentials are per-client sub-resources with no top-level asset list, so they are not
43
+ // modeled in the dry-run diff. Return empty changes so the dry-run loop (which calls
44
+ // dryRunChanges on every handler) does not crash. Credential changes are still applied
45
+ // during a real import via processChanges.
46
+ async dryRunChanges(_assets) {
47
+ return { del: [], create: [], conflicts: [], update: [] };
48
+ }
42
49
  async processChanges(assets) {
43
50
  const { clients } = assets;
44
51
  if (!clients)
@@ -1,4 +1,4 @@
1
- import { Assets, Auth0APIClient } from '../../../types';
1
+ import { Assets, Auth0APIClient, CalculatedChanges } from '../../../types';
2
2
  import { ConfigFunction } from '../../../configFactory';
3
3
  export declare const schema: {
4
4
  type: string;
@@ -33,5 +33,6 @@ export default class ClientAuthCredentialsPreHandler {
33
33
  [x: string]: null;
34
34
  }>;
35
35
  validate(): Promise<null>;
36
+ dryRunChanges(_assets: Assets): Promise<CalculatedChanges>;
36
37
  processChanges(assets: Assets): Promise<void>;
37
38
  }
@@ -48,6 +48,11 @@ class ClientAuthCredentialsPreHandler {
48
48
  async validate() {
49
49
  return null;
50
50
  }
51
+ // Deploy-only pre-pass handler with no dry-run representation. Return empty changes so the
52
+ // dry-run loop (which calls dryRunChanges on every registered handler) does not crash.
53
+ async dryRunChanges(_assets) {
54
+ return { del: [], create: [], conflicts: [], update: [] };
55
+ }
51
56
  async processChanges(assets) {
52
57
  const { clients } = assets;
53
58
  if (!clients)
@@ -22,7 +22,7 @@ export declare const schema: {
22
22
  };
23
23
  subject_type: {
24
24
  type: string;
25
- enum: ("client" | "user")[];
25
+ enum: ("client" | "user" | "anonymous_user")[];
26
26
  description: string;
27
27
  };
28
28
  authorization_details_types: {
@@ -193,6 +193,18 @@ export declare const schema: {
193
193
  };
194
194
  additionalProperties: boolean;
195
195
  };
196
+ identity_assertion_authorization_grant: {
197
+ type: string;
198
+ description: string;
199
+ properties: {
200
+ active: {
201
+ type: string;
202
+ description: string;
203
+ };
204
+ };
205
+ required: string[];
206
+ additionalProperties: boolean;
207
+ };
196
208
  app_type: {
197
209
  type: string;
198
210
  description: string;
@@ -328,6 +340,40 @@ export declare const schema: {
328
340
  };
329
341
  };
330
342
  };
343
+ token_vault_privileged_access: {
344
+ type: string[];
345
+ description: string;
346
+ properties: {
347
+ ip_allowlist: {
348
+ type: string;
349
+ description: string;
350
+ items: {
351
+ type: string;
352
+ };
353
+ };
354
+ grants: {
355
+ type: string;
356
+ description: string;
357
+ items: {
358
+ type: string;
359
+ properties: {
360
+ connection: {
361
+ type: string;
362
+ description: string;
363
+ };
364
+ scopes: {
365
+ type: string;
366
+ description: string;
367
+ items: {
368
+ type: string;
369
+ };
370
+ };
371
+ };
372
+ required: string[];
373
+ };
374
+ };
375
+ };
376
+ };
331
377
  third_party_security_mode: {
332
378
  type: string;
333
379
  enum: string[];
@@ -66,6 +66,40 @@ const myOrganizationConfigurationSchema = {
66
66
  },
67
67
  required: ['allowed_strategies', 'connection_deletion_behavior'],
68
68
  };
69
+ const tokenVaultPrivilegedAccessSchema = {
70
+ type: ['object', 'null'],
71
+ description: 'Settings for Token Vault Privileged Access, hardening a privileged client by restricting the caller IPs, connections, and scopes it may use at runtime. Early Access, gated by the token_vault_subject_type_jwt_ea_rollout feature flag. Export-only: this object is exported for visibility but is not deployed by the Deploy CLI (it is stripped from create/update payloads because the required credentials are tenant-specific ids). Manage it directly on the tenant.',
72
+ properties: {
73
+ ip_allowlist: {
74
+ type: 'array',
75
+ description: 'IPv4/IPv6 addresses or CIDR ranges permitted to call token exchange on behalf of this privileged client. When the EA flag is on, this is required (non-empty) on create if token_vault_privileged_access is being set.',
76
+ items: {
77
+ type: 'string',
78
+ },
79
+ },
80
+ grants: {
81
+ type: 'array',
82
+ description: 'Connection/scope pin objects restricting which federated connections and OAuth scopes the privileged client may use at runtime. Maximum 5 connections; maximum 20 scopes in total across all connections.',
83
+ items: {
84
+ type: 'object',
85
+ properties: {
86
+ connection: {
87
+ type: 'string',
88
+ description: 'The connection name (e.g. google-oauth2). Validated at runtime; the connection need not exist at configuration time.',
89
+ },
90
+ scopes: {
91
+ type: 'array',
92
+ description: 'The OAuth scopes permitted for that connection.',
93
+ items: {
94
+ type: 'string',
95
+ },
96
+ },
97
+ },
98
+ required: ['connection', 'scopes'],
99
+ },
100
+ },
101
+ },
102
+ };
69
103
  exports.schema = {
70
104
  type: 'array',
71
105
  items: {
@@ -221,6 +255,18 @@ exports.schema = {
221
255
  },
222
256
  additionalProperties: true,
223
257
  },
258
+ identity_assertion_authorization_grant: {
259
+ type: 'object',
260
+ description: 'Settings for Cross App Access (ID-JAG) token exchange. Early Access. Enables the client to request identity assertion authorization grants.',
261
+ properties: {
262
+ active: {
263
+ type: 'boolean',
264
+ description: 'Indicates whether the client can request an identity assertion authorization grant (ID-JAG) via token exchange.',
265
+ },
266
+ },
267
+ required: ['active'],
268
+ additionalProperties: false,
269
+ },
224
270
  app_type: {
225
271
  type: 'string',
226
272
  description: 'The type of application this client represents',
@@ -332,6 +378,7 @@ exports.schema = {
332
378
  },
333
379
  },
334
380
  },
381
+ token_vault_privileged_access: tokenVaultPrivilegedAccessSchema,
335
382
  third_party_security_mode: {
336
383
  type: 'string',
337
384
  enum: ['strict', 'permissive'],
@@ -460,6 +507,40 @@ const createClientSanitizer = (clients) => {
460
507
  },
461
508
  };
462
509
  };
510
+ /**
511
+ * On export, remove `credentials` from `token_vault_privileged_access`.
512
+ *
513
+ * Auth0 returns these as tenant-specific credential references (`{ id }`) — never
514
+ * names or key material — so they are not portable across tenants and must never be
515
+ * written to disk. `ip_allowlist` and `grants` are kept for visibility. Mutates and
516
+ * returns the same client.
517
+ */
518
+ const stripTokenVaultCredentialsOnExport = (client) => {
519
+ const tvpa = client
520
+ .token_vault_privileged_access;
521
+ if (tvpa && 'credentials' in tvpa) {
522
+ delete tvpa.credentials;
523
+ }
524
+ return client;
525
+ };
526
+ /**
527
+ * On write (create/update), remove the entire `token_vault_privileged_access` object.
528
+ *
529
+ * The Management API requires `credentials` whenever `token_vault_privileged_access`
530
+ * is present in the payload, but those credentials are tenant-specific ids that the
531
+ * Deploy CLI intentionally never persists (see stripTokenVaultCredentialsOnExport).
532
+ * Sending the object without `credentials` fails validation ("Missing required
533
+ * property: credentials"); sending it with exported ids would re-send stale ids on a
534
+ * cross-tenant deploy. The Deploy CLI therefore does not manage this field — it is
535
+ * export-only. Manage `token_vault_privileged_access` directly on the tenant.
536
+ * Mutates and returns the same client.
537
+ */
538
+ const stripTokenVaultPrivilegedAccessOnWrite = (client) => {
539
+ if ('token_vault_privileged_access' in client) {
540
+ delete client.token_vault_privileged_access;
541
+ }
542
+ return client;
543
+ };
463
544
  class ClientHandler extends default_1.default {
464
545
  constructor(config) {
465
546
  super({
@@ -577,6 +658,10 @@ class ClientHandler extends default_1.default {
577
658
  delete item.token_endpoint_auth_method;
578
659
  }
579
660
  delete item.client_authentication_methods;
661
+ // token_vault_privileged_access requires tenant-specific credential ids that
662
+ // the Deploy CLI never persists. Strip the whole object on write so we neither
663
+ // re-send stale ids nor fail validation for the missing credentials property.
664
+ stripTokenVaultPrivilegedAccessOnWrite(item);
580
665
  return item;
581
666
  });
582
667
  };
@@ -598,7 +683,12 @@ class ClientHandler extends default_1.default {
598
683
  is_global: false,
599
684
  ...((0, utils_1.shouldExcludeThirdPartyClients)(this.config) && { is_first_party: true }),
600
685
  });
601
- const sanitized = createClientSanitizer(clients).sanitizeCrossOriginAuth(false).get();
686
+ const sanitized = createClientSanitizer(clients)
687
+ .sanitizeCrossOriginAuth(false)
688
+ .get()
689
+ // token_vault_privileged_access.credentials are returned as tenant-specific
690
+ // ids — strip them so they are never exported to disk.
691
+ .map(stripTokenVaultCredentialsOnExport);
602
692
  // Enrich credential stubs with name and credential_type for export.
603
693
  // Auth0 does not return pem on read — it is never exported.
604
694
  await Promise.all(sanitized.map(async (client) => {
@@ -103,6 +103,27 @@ export declare const schema: {
103
103
  required: string[];
104
104
  additionalProperties: boolean;
105
105
  };
106
+ cross_app_access_requesting_app: {
107
+ type: string;
108
+ properties: {
109
+ active: {
110
+ type: string;
111
+ };
112
+ };
113
+ required: string[];
114
+ additionalProperties: boolean;
115
+ };
116
+ cross_app_access_resource_app: {
117
+ type: string;
118
+ properties: {
119
+ status: {
120
+ type: string;
121
+ enum: string[];
122
+ };
123
+ };
124
+ required: string[];
125
+ additionalProperties: boolean;
126
+ };
106
127
  directory_provisioning_configuration: {
107
128
  type: string;
108
129
  properties: {
@@ -129,6 +129,22 @@ exports.schema = {
129
129
  required: ['active'],
130
130
  additionalProperties: false,
131
131
  },
132
+ cross_app_access_requesting_app: {
133
+ type: 'object',
134
+ properties: {
135
+ active: { type: 'boolean' },
136
+ },
137
+ required: ['active'],
138
+ additionalProperties: false,
139
+ },
140
+ cross_app_access_resource_app: {
141
+ type: 'object',
142
+ properties: {
143
+ status: { type: 'string', enum: ['enabled', 'disabled'] },
144
+ },
145
+ required: ['status'],
146
+ additionalProperties: false,
147
+ },
132
148
  directory_provisioning_configuration: {
133
149
  type: 'object',
134
150
  properties: {
@@ -328,8 +328,8 @@ class APIHandler {
328
328
  const updateFN = this.getClientFN(this.functions.update);
329
329
  const updatePayload = (() => {
330
330
  const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
331
- const stripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
332
- return (0, utils_1.validateNoUnresolvedPlaceholders)(stripped, this.type, this.objString(updateItem));
331
+ const obfuscatedStripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
332
+ return (0, utils_1.stripUnresolvedPlaceholders)(obfuscatedStripped, this.type, this.objString(updateItem));
333
333
  })();
334
334
  return updateFN(updateItem[this.id], updatePayload);
335
335
  }, retryConfig)
@@ -347,8 +347,8 @@ class APIHandler {
347
347
  const createFunction = this.getClientFN(this.functions.create);
348
348
  const createPayload = (() => {
349
349
  const strippedPayload = (0, utils_1.stripFields)(createItem, this.stripCreateFields);
350
- const stripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(strippedPayload, this.sensitiveFieldsToObfuscate);
351
- return (0, utils_1.validateNoUnresolvedPlaceholders)(stripped, this.type, this.objString(createItem));
350
+ const obfuscatedStripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(strippedPayload, this.sensitiveFieldsToObfuscate);
351
+ return (0, utils_1.stripUnresolvedPlaceholders)(obfuscatedStripped, this.type, this.objString(createItem));
352
352
  })();
353
353
  return createFunction(createPayload);
354
354
  }, retryConfig)
@@ -369,8 +369,8 @@ class APIHandler {
369
369
  const updateFN = this.getClientFN(this.functions.update);
370
370
  const updatePayload = (() => {
371
371
  const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
372
- const stripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
373
- return (0, utils_1.validateNoUnresolvedPlaceholders)(stripped, this.type, this.objString(updateItem));
372
+ const obfuscatedStripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
373
+ return (0, utils_1.stripUnresolvedPlaceholders)(obfuscatedStripped, this.type, this.objString(updateItem));
374
374
  })();
375
375
  return updateFN(updateItem[this.id], updatePayload);
376
376
  }, retryConfig)
@@ -60,11 +60,20 @@ class GuardianFactorTemplatesHandler extends default_1.default {
60
60
  await Promise.all(guardianFactorTemplates.map(async (fatorTemplates) => {
61
61
  const { name, ...data } = fatorTemplates;
62
62
  const params = { name: fatorTemplates.name };
63
- if (name === 'sms') {
64
- await this.client.guardian.factors.sms.setTemplates(data);
63
+ try {
64
+ if (name === 'sms') {
65
+ await this.client.guardian.factors.sms.setTemplates(data);
66
+ }
67
+ else if (name === 'phone') {
68
+ await this.client.guardian.factors.phone.setTemplates(data);
69
+ }
65
70
  }
66
- else if (name === 'phone') {
67
- await this.client.guardian.factors.phone.setTemplates(data);
71
+ catch (err) {
72
+ if ((0, utils_1.isForbiddenFeatureError)(err, this.type)) {
73
+ // Feature is deprecated/disabled on this tenant; warn and skip instead of failing the import.
74
+ return;
75
+ }
76
+ throw err;
68
77
  }
69
78
  this.didUpdate(params);
70
79
  this.updated += 1;
@@ -25,14 +25,7 @@ const isFeatureUnavailableError = (err) => {
25
25
  // Older Management API version where the endpoint is not available.
26
26
  return true;
27
27
  }
28
- if (err.statusCode === 403 &&
29
- err.originalError &&
30
- err.originalError.response &&
31
- err.originalError.response.body &&
32
- err.originalError.response.body.errorCode === 'voice_mfa_not_allowed') {
33
- // Recent Management API version, but with feature explicitly disabled.
34
- return true;
35
- }
28
+ // 403s (feature explicitly disabled) are handled by isForbiddenFeatureError.
36
29
  return false;
37
30
  };
38
31
  class GuardianPhoneMessageTypesHandler extends default_1.default {
@@ -75,7 +68,16 @@ class GuardianPhoneMessageTypesHandler extends default_1.default {
75
68
  }
76
69
  }
77
70
  const data = guardianPhoneFactorMessageTypes;
78
- await this.client.guardian.factors.phone.setMessageTypes(data);
71
+ try {
72
+ await this.client.guardian.factors.phone.setMessageTypes(data);
73
+ }
74
+ catch (err) {
75
+ if (isFeatureUnavailableError(err) || (0, utils_1.isForbiddenFeatureError)(err, this.type)) {
76
+ // Feature is deprecated/disabled on this tenant; warn and skip instead of failing the import.
77
+ return;
78
+ }
79
+ throw err;
80
+ }
79
81
  this.updated += 1;
80
82
  this.didUpdate(guardianPhoneFactorMessageTypes);
81
83
  }
@@ -22,14 +22,7 @@ const isFeatureUnavailableError = (err) => {
22
22
  // Older Management API version where the endpoint is not available.
23
23
  return true;
24
24
  }
25
- if (err.statusCode === 403 &&
26
- err.originalError &&
27
- err.originalError.response &&
28
- err.originalError.response.body &&
29
- err.originalError.response.body.errorCode === 'hooks_not_allowed') {
30
- // Recent Management API version, but with feature explicitly disabled.
31
- return true;
32
- }
25
+ // 403s (feature explicitly disabled) are handled by isForbiddenFeatureError.
33
26
  return false;
34
27
  };
35
28
  class GuardianPhoneSelectedProviderHandler extends default_1.default {
@@ -72,7 +65,16 @@ class GuardianPhoneSelectedProviderHandler extends default_1.default {
72
65
  }
73
66
  }
74
67
  const data = guardianPhoneFactorSelectedProvider;
75
- await this.client.guardian.factors.phone.setProvider(data);
68
+ try {
69
+ await this.client.guardian.factors.phone.setProvider(data);
70
+ }
71
+ catch (err) {
72
+ if (isFeatureUnavailableError(err) || (0, utils_1.isForbiddenFeatureError)(err, this.type)) {
73
+ // Feature is deprecated/disabled on this tenant; warn and skip instead of failing the import.
74
+ return;
75
+ }
76
+ throw err;
77
+ }
76
78
  this.updated += 1;
77
79
  this.didUpdate(guardianPhoneFactorSelectedProvider);
78
80
  }
@@ -151,7 +151,7 @@ class HooksHandler extends default_1.default {
151
151
  .get(hook.id)
152
152
  .then((hookWithCode) => this.client.hooks.secrets
153
153
  .get(hook.id)
154
- .then(({ data: secrets }) => ({ ...hookWithCode, secrets })))));
154
+ .then((secrets) => ({ ...hookWithCode, secrets })))));
155
155
  return this.existing;
156
156
  }
157
157
  catch (err) {
@@ -85,6 +85,15 @@ export declare const schema: {
85
85
  uniqueItems: boolean;
86
86
  minItems: number;
87
87
  };
88
+ auth0_managed: {
89
+ type: string;
90
+ items: {
91
+ type: string;
92
+ pattern: string;
93
+ };
94
+ uniqueItems: boolean;
95
+ minItems: number;
96
+ };
88
97
  geo_country_codes: {
89
98
  type: string;
90
99
  items: {
@@ -176,6 +185,15 @@ export declare const schema: {
176
185
  uniqueItems: boolean;
177
186
  minItems: number;
178
187
  };
188
+ auth0_managed: {
189
+ type: string;
190
+ items: {
191
+ type: string;
192
+ pattern: string;
193
+ };
194
+ uniqueItems: boolean;
195
+ minItems: number;
196
+ };
179
197
  geo_country_codes: {
180
198
  type: string;
181
199
  items: {
@@ -69,6 +69,15 @@ const MatchSchema = {
69
69
  uniqueItems: true,
70
70
  minItems: 1,
71
71
  },
72
+ auth0_managed: {
73
+ type: 'array',
74
+ items: {
75
+ type: 'string',
76
+ pattern: '^auth0\\.[^.\\s]+$',
77
+ },
78
+ uniqueItems: true,
79
+ minItems: 1,
80
+ },
72
81
  geo_country_codes: {
73
82
  type: 'array',
74
83
  items: {
@@ -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;
@@ -79,6 +79,29 @@ 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
+ };
82
105
  };
83
106
  };
84
107
  export interface Tenant extends Management.GetTenantSettingsResponseContent {
@@ -106,6 +106,29 @@ 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
+ },
109
132
  },
110
133
  };
111
134
  const blockPageKeys = [
@@ -147,6 +170,7 @@ exports.allowedTenantFlags = [
147
170
  'require_pushed_authorization_requests',
148
171
  'mfa_show_factor_list_on_enrollment',
149
172
  'improved_signup_bot_detection_in_classic',
173
+ 'use_scope_descriptions_for_consent',
150
174
  ];
151
175
  const removeUnallowedTenantFlags = (proposedFlags) => {
152
176
  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();
@@ -14,6 +14,7 @@ const handlers_1 = __importDefault(require("./handlers"));
14
14
  const calculateDryRunChanges_1 = require("../calculateDryRunChanges");
15
15
  const utils_1 = require("../../utils");
16
16
  const utils_2 = require("../utils");
17
+ const logger_1 = __importDefault(require("../../logger"));
17
18
  /**
18
19
  * Sorts handlers by their @order decorator metadata for a given stage.
19
20
  * Handlers are sorted in ascending order (lower values execute first).
@@ -145,6 +146,14 @@ class Auth0 {
145
146
  data: this.handlers,
146
147
  generator: (handler) => (async () => {
147
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
+ }
148
157
  const detailedChanges = [];
149
158
  let created = 0;
150
159
  let updated = 0;
@@ -27,6 +27,7 @@ export declare function areArraysEquals(x: any[], y: any[]): boolean;
27
27
  export declare const obfuscateSensitiveValues: (data: Asset | Asset[] | null, sensitiveFieldsToObfuscate: string[]) => Asset | Asset[] | null;
28
28
  export declare const validateNoUnresolvedPlaceholders: (data: Asset | null, resourceType: string, resourceName: string) => Asset | null;
29
29
  export declare const stripObfuscatedFieldsFromPayload: (data: Asset | Asset[] | null, obfuscatedFields: string[]) => Asset | Asset[] | null;
30
+ export declare const stripUnresolvedPlaceholders: (data: Asset | null, resourceType: string, resourceName: string) => Asset | null;
30
31
  export declare const detectInsufficientScopeError: <T>(fn: Function) => Promise<{
31
32
  hadSufficientScopes: true;
32
33
  data: T;
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.shouldExcludeThirdPartyClients = exports.isForbiddenFeatureError = exports.isDeprecatedError = exports.detectInsufficientScopeError = exports.stripObfuscatedFieldsFromPayload = exports.validateNoUnresolvedPlaceholders = exports.obfuscateSensitiveValues = exports.keywordReplaceStringRegExp = exports.keywordReplaceArrayRegExp = void 0;
39
+ exports.shouldExcludeThirdPartyClients = exports.isForbiddenFeatureError = exports.isDeprecatedError = exports.detectInsufficientScopeError = exports.stripUnresolvedPlaceholders = exports.stripObfuscatedFieldsFromPayload = exports.validateNoUnresolvedPlaceholders = exports.obfuscateSensitiveValues = exports.keywordReplaceStringRegExp = exports.keywordReplaceArrayRegExp = void 0;
40
40
  exports.keywordArrayReplace = keywordArrayReplace;
41
41
  exports.keywordStringReplace = keywordStringReplace;
42
42
  exports.keywordReplace = keywordReplace;
@@ -309,6 +309,24 @@ const stripObfuscatedFieldsFromPayload = (data, obfuscatedFields) => {
309
309
  return newAsset;
310
310
  };
311
311
  exports.stripObfuscatedFieldsFromPayload = stripObfuscatedFieldsFromPayload;
312
+ // Strips unresolved ##...## / @@...@@ placeholders before sending to Auth0.
313
+ // Keyword replacement runs first; any remaining placeholder means no mapping was
314
+ // provided. The field is dropped so Auth0 preserves its existing value.
315
+ const stripUnresolvedPlaceholders = (data, resourceType, resourceName) => {
316
+ if (data === null)
317
+ return data;
318
+ const unresolved = collectUnresolvedPlaceholders(data);
319
+ if (unresolved.length === 0)
320
+ return data;
321
+ const newAsset = { ...data };
322
+ unresolved.forEach(({ path, value }) => {
323
+ logger_1.default.warn(`Stripping unresolved placeholder for ${resourceType} "${resourceName}" field "${path}": ${value}. ` +
324
+ `To use this value, define ${value.replace(/^##|##$|^@@|@@$/g, '')} in AUTH0_KEYWORD_REPLACE_MAPPINGS. The existing value on Auth0 will be preserved.`);
325
+ dot_prop_1.default.delete(newAsset, path);
326
+ });
327
+ return newAsset;
328
+ };
329
+ exports.stripUnresolvedPlaceholders = stripUnresolvedPlaceholders;
312
330
  const detectInsufficientScopeError = async (fn) => {
313
331
  try {
314
332
  const data = await fn();
@@ -342,7 +360,12 @@ const isDeprecatedError = (err) => {
342
360
  exports.isDeprecatedError = isDeprecatedError;
343
361
  const isForbiddenFeatureError = (err, type) => {
344
362
  if (err.statusCode === 403) {
345
- logger_1.default.warn(`${err.message};${err.errorCode ?? ''} - Skipping ${type}`);
363
+ // The SDK error's top-level `message` is the full serialized response body; the clean,
364
+ // human-readable message and errorCode live on the response body itself.
365
+ const body = err.originalError?.response?.body ?? {};
366
+ const message = body.message ?? err.message;
367
+ const errorCode = body.errorCode ?? err.errorCode ?? '';
368
+ logger_1.default.warn(`${message}${errorCode ? ` (${errorCode})` : ''} - Skipping ${type}`);
346
369
  return true;
347
370
  }
348
371
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth0-deploy-cli",
3
- "version": "8.41.0",
3
+ "version": "8.42.0",
4
4
  "description": "A command line tool for deploying updates to your Auth0 tenant",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@clack/prompts": "1.4.0",
37
37
  "ajv": "^6.12.6",
38
- "auth0": "^6.0.0",
38
+ "auth0": "^6.1.0",
39
39
  "chalk": "5.6.2",
40
40
  "dot-prop": "^5.3.0",
41
41
  "fs-extra": "^10.1.0",