auth0-deploy-cli 8.41.0 → 8.43.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 (42) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/lib/context/directory/handlers/actionModules.js +11 -12
  3. package/lib/context/directory/handlers/actions.js +11 -13
  4. package/lib/context/directory/handlers/databases.js +4 -4
  5. package/lib/context/directory/handlers/hooks.js +12 -1
  6. package/lib/context/directory/handlers/rules.js +12 -1
  7. package/lib/context/directory/index.js +7 -8
  8. package/lib/context/yaml/index.js +7 -8
  9. package/lib/tools/auth0/handlers/attackProtection.js +16 -5
  10. package/lib/tools/auth0/handlers/clientAuthCredentials.d.ts +2 -1
  11. package/lib/tools/auth0/handlers/clientAuthCredentials.js +7 -0
  12. package/lib/tools/auth0/handlers/clientAuthCredentialsPre.d.ts +2 -1
  13. package/lib/tools/auth0/handlers/clientAuthCredentialsPre.js +5 -0
  14. package/lib/tools/auth0/handlers/clientGrants.d.ts +1 -1
  15. package/lib/tools/auth0/handlers/clients.d.ts +46 -0
  16. package/lib/tools/auth0/handlers/clients.js +91 -1
  17. package/lib/tools/auth0/handlers/connections.d.ts +32 -6
  18. package/lib/tools/auth0/handlers/connections.js +39 -4
  19. package/lib/tools/auth0/handlers/databases.js +11 -2
  20. package/lib/tools/auth0/handlers/default.js +6 -6
  21. package/lib/tools/auth0/handlers/guardianFactorTemplates.js +13 -4
  22. package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +11 -9
  23. package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +11 -9
  24. package/lib/tools/auth0/handlers/hooks.js +1 -1
  25. package/lib/tools/auth0/handlers/networkACLs.d.ts +18 -0
  26. package/lib/tools/auth0/handlers/networkACLs.js +9 -0
  27. package/lib/tools/auth0/handlers/organizations.d.ts +28 -0
  28. package/lib/tools/auth0/handlers/organizations.js +127 -2
  29. package/lib/tools/auth0/handlers/phoneTemplates.d.ts +4 -1
  30. package/lib/tools/auth0/handlers/phoneTemplates.js +67 -10
  31. package/lib/tools/auth0/handlers/roles.d.ts +4 -0
  32. package/lib/tools/auth0/handlers/roles.js +10 -0
  33. package/lib/tools/auth0/handlers/rules.js +19 -7
  34. package/lib/tools/auth0/handlers/tenant.d.ts +23 -0
  35. package/lib/tools/auth0/handlers/tenant.js +24 -0
  36. package/lib/tools/auth0/handlers/themes.d.ts +37 -1
  37. package/lib/tools/auth0/handlers/themes.js +64 -0
  38. package/lib/tools/auth0/index.js +9 -0
  39. package/lib/tools/utils.d.ts +1 -0
  40. package/lib/tools/utils.js +25 -2
  41. package/lib/types.d.ts +1 -0
  42. package/package.json +2 -2
@@ -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 });
@@ -133,6 +137,9 @@ class RolesHandler extends default_1.default {
133
137
  const newPermissions = data.permissions;
134
138
  delete data.permissions;
135
139
  delete data.id;
140
+ // deploy-cli only manages tenant-level roles, so `type` is stripped on write; this
141
+ // lets files that already carry it (from a prior export) be imported cleanly.
142
+ delete data.type;
136
143
  await this.client.roles.update(params.id, data);
137
144
  if (typeof existingRole.permissions !== 'undefined' && existingRole.permissions.length > 0) {
138
145
  await this.client.roles.permissions.delete(params.id, {
@@ -164,9 +171,12 @@ class RolesHandler extends default_1.default {
164
171
  return this.existing;
165
172
  }
166
173
  try {
174
+ // Only manage tenant-level roles; exclude org-scoped roles surfaced when
175
+ // the api2_org_level_roles_ea flag is enabled.
167
176
  const roles = await (0, client_1.paginate)(this.client.roles.list, {
168
177
  paginate: true,
169
178
  include_totals: true,
179
+ type: 'tenant',
170
180
  });
171
181
  for (let index = 0; index < roles.length; index++) {
172
182
  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;
@@ -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/lib/types.d.ts CHANGED
@@ -31,6 +31,7 @@ type SharedPaginationParams = {
31
31
  include_totals?: boolean;
32
32
  id?: string;
33
33
  strategy?: Management.ConnectionStrategyEnum[];
34
+ type?: Management.RoleTypeEnum;
34
35
  };
35
36
  export type CheckpointPaginationParams = SharedPaginationParams & {
36
37
  from?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth0-deploy-cli",
3
- "version": "8.41.0",
3
+ "version": "8.43.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.2.0",
39
39
  "chalk": "5.6.2",
40
40
  "dot-prop": "^5.3.0",
41
41
  "fs-extra": "^10.1.0",