auth0-deploy-cli 8.31.0 → 8.32.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 (73) hide show
  1. package/CHANGELOG.md +40 -419
  2. package/lib/args.d.ts +4 -0
  3. package/lib/args.js +18 -0
  4. package/lib/commands/import.js +40 -3
  5. package/lib/context/directory/handlers/clients.js +14 -0
  6. package/lib/context/directory/handlers/connections.js +1 -0
  7. package/lib/context/directory/handlers/guardianFactors.js +8 -6
  8. package/lib/context/directory/handlers/tenant.d.ts +1 -6
  9. package/lib/context/directory/handlers/tenant.js +0 -1
  10. package/lib/context/index.js +5 -0
  11. package/lib/context/yaml/handlers/clients.js +12 -0
  12. package/lib/context/yaml/handlers/connections.js +1 -17
  13. package/lib/context/yaml/handlers/guardianFactors.js +3 -1
  14. package/lib/context/yaml/handlers/tenant.js +1 -1
  15. package/lib/sessionDurationsToMinutes.d.ts +9 -7
  16. package/lib/sessionDurationsToMinutes.js +11 -10
  17. package/lib/tools/auth0/handlers/actions.d.ts +1 -0
  18. package/lib/tools/auth0/handlers/actions.js +36 -0
  19. package/lib/tools/auth0/handlers/attackProtection.js +15 -0
  20. package/lib/tools/auth0/handlers/branding.js +7 -0
  21. package/lib/tools/auth0/handlers/clientGrants.js +1 -0
  22. package/lib/tools/auth0/handlers/clients.d.ts +52 -2
  23. package/lib/tools/auth0/handlers/clients.js +175 -22
  24. package/lib/tools/auth0/handlers/connections.d.ts +1 -0
  25. package/lib/tools/auth0/handlers/connections.js +42 -5
  26. package/lib/tools/auth0/handlers/databases.d.ts +91 -0
  27. package/lib/tools/auth0/handlers/databases.js +162 -2
  28. package/lib/tools/auth0/handlers/default.d.ts +4 -0
  29. package/lib/tools/auth0/handlers/default.js +82 -4
  30. package/lib/tools/auth0/handlers/emailProvider.js +8 -0
  31. package/lib/tools/auth0/handlers/emailTemplates.js +7 -0
  32. package/lib/tools/auth0/handlers/flowVaultConnections.js +6 -1
  33. package/lib/tools/auth0/handlers/flows.js +13 -4
  34. package/lib/tools/auth0/handlers/forms.js +10 -1
  35. package/lib/tools/auth0/handlers/guardianFactors.js +7 -1
  36. package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +8 -1
  37. package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +8 -1
  38. package/lib/tools/auth0/handlers/guardianPolicies.js +7 -0
  39. package/lib/tools/auth0/handlers/hooks.d.ts +1 -0
  40. package/lib/tools/auth0/handlers/hooks.js +10 -0
  41. package/lib/tools/auth0/handlers/organizations.d.ts +11 -1
  42. package/lib/tools/auth0/handlers/organizations.js +48 -52
  43. package/lib/tools/auth0/handlers/pages.js +7 -0
  44. package/lib/tools/auth0/handlers/phoneProvider.js +7 -0
  45. package/lib/tools/auth0/handlers/prompts.js +7 -0
  46. package/lib/tools/auth0/handlers/resourceServers.d.ts +1 -2
  47. package/lib/tools/auth0/handlers/resourceServers.js +9 -25
  48. package/lib/tools/auth0/handlers/roles.js +7 -0
  49. package/lib/tools/auth0/handlers/rules.d.ts +1 -0
  50. package/lib/tools/auth0/handlers/rules.js +9 -0
  51. package/lib/tools/auth0/handlers/rulesConfigs.d.ts +1 -0
  52. package/lib/tools/auth0/handlers/rulesConfigs.js +3 -0
  53. package/lib/tools/auth0/handlers/scimHandler.js +8 -1
  54. package/lib/tools/auth0/handlers/selfServiceProfiles.d.ts +1 -1
  55. package/lib/tools/auth0/handlers/selfServiceProfiles.js +10 -17
  56. package/lib/tools/auth0/handlers/tenant.d.ts +13 -1
  57. package/lib/tools/auth0/handlers/tenant.js +43 -4
  58. package/lib/tools/auth0/handlers/themes.js +11 -2
  59. package/lib/tools/auth0/handlers/triggers.js +6 -0
  60. package/lib/tools/auth0/handlers/userAttributeProfiles.js +16 -10
  61. package/lib/tools/auth0/index.d.ts +3 -0
  62. package/lib/tools/auth0/index.js +204 -0
  63. package/lib/tools/calculateChanges.d.ts +2 -2
  64. package/lib/tools/calculateChanges.js +21 -6
  65. package/lib/tools/calculateDryRunChanges.d.ts +71 -0
  66. package/lib/tools/calculateDryRunChanges.js +492 -0
  67. package/lib/tools/deploy.js +25 -1
  68. package/lib/tools/utils.d.ts +5 -0
  69. package/lib/tools/utils.js +64 -3
  70. package/lib/types.d.ts +16 -0
  71. package/lib/utils.d.ts +7 -0
  72. package/lib/utils.js +33 -1
  73. package/package.json +7 -4
@@ -49,6 +49,7 @@ const constants_1 = __importDefault(require("../../constants"));
49
49
  const utils_1 = require("../../utils");
50
50
  const client_1 = require("../client");
51
51
  const logger_1 = __importDefault(require("../../../logger"));
52
+ const calculateDryRunChanges_1 = require("../../calculateDryRunChanges");
52
53
  const connections_1 = require("./connections");
53
54
  exports.schema = {
54
55
  type: 'array',
@@ -73,7 +74,8 @@ exports.schema = {
73
74
  type: 'object',
74
75
  properties: {
75
76
  enabled: { type: 'boolean' },
76
- api_behavior: { type: 'string' },
77
+ api_behavior: { type: 'string', enum: ['required', 'optional'] },
78
+ signup_behavior: { type: 'string', enum: ['allow', 'block'] },
77
79
  },
78
80
  },
79
81
  email_otp: {
@@ -179,6 +181,54 @@ exports.schema = {
179
181
  action_id: { type: 'string' },
180
182
  },
181
183
  },
184
+ password_options: {
185
+ type: 'object',
186
+ properties: {
187
+ complexity: {
188
+ type: 'object',
189
+ properties: {
190
+ min_length: { type: 'integer', minimum: 1, maximum: 72 },
191
+ character_types: {
192
+ type: 'array',
193
+ items: {
194
+ type: 'string',
195
+ enum: ['uppercase', 'lowercase', 'number', 'special'],
196
+ },
197
+ },
198
+ character_type_rule: { type: 'string', enum: ['all', 'three_of_four'] },
199
+ identical_characters: { type: 'string', enum: ['allow', 'block'] },
200
+ sequential_characters: { type: 'string', enum: ['allow', 'block'] },
201
+ max_length_exceeded: { type: 'string', enum: ['truncate', 'error'] },
202
+ },
203
+ },
204
+ profile_data: {
205
+ type: 'object',
206
+ properties: {
207
+ active: { type: 'boolean' },
208
+ blocked_fields: {
209
+ type: 'array',
210
+ items: { type: 'string' },
211
+ maxItems: 12,
212
+ },
213
+ },
214
+ },
215
+ history: {
216
+ type: 'object',
217
+ properties: {
218
+ active: { type: 'boolean' },
219
+ size: { type: 'integer', minimum: 1, maximum: 24 },
220
+ },
221
+ },
222
+ dictionary: {
223
+ type: 'object',
224
+ properties: {
225
+ active: { type: 'boolean' },
226
+ default: { type: 'string', enum: ['en_10k', 'en_100k'] },
227
+ custom: { type: 'array', items: { type: 'string' } },
228
+ },
229
+ },
230
+ },
231
+ },
182
232
  },
183
233
  },
184
234
  },
@@ -220,10 +270,45 @@ class DatabaseHandler extends default_1.default {
220
270
  // Validate each database
221
271
  databases.forEach((database) => {
222
272
  this.validateEmailUniqueConstraints(database);
223
- // this.validatePasswordlessSettings(database); // Enable only the feature is GA PR:#1282
273
+ this.validatePasswordOptions(database);
274
+ // this.validatePasswordlessSettings(database); // Enable only when feature is GA PR:#1282
224
275
  });
225
276
  await super.validate(assets);
226
277
  }
278
+ validatePasswordOptions(payload) {
279
+ const options = payload?.options;
280
+ if (!options?.password_options)
281
+ return;
282
+ const legacyFields = [
283
+ 'passwordPolicy',
284
+ 'password_complexity_options',
285
+ 'password_history',
286
+ 'password_no_personal_info',
287
+ 'password_dictionary',
288
+ ];
289
+ const conflictingField = legacyFields.find((field) => field in options);
290
+ if (conflictingField) {
291
+ throw new Error(`Database "${payload.name}": Cannot use both legacy password policy field "${conflictingField}" and "password_options". Please migrate fully to "password_options".`);
292
+ }
293
+ const complexity = options.password_options?.complexity;
294
+ if (complexity?.character_type_rule === 'three_of_four') {
295
+ const allTypes = ['uppercase', 'lowercase', 'number', 'special'];
296
+ const hasAllTypes = allTypes.every((t) => complexity?.character_types?.includes(t));
297
+ if (!hasAllTypes) {
298
+ throw new Error(`Database "${payload.name}": "character_type_rule" can only be set to "three_of_four" when all four character types (uppercase, lowercase, number, special) are specified in "character_types".`);
299
+ }
300
+ }
301
+ const blockedFields = options.password_options?.profile_data?.blocked_fields;
302
+ if (blockedFields) {
303
+ if (blockedFields.length > 12) {
304
+ throw new Error(`Database "${payload.name}": "profile_data.blocked_fields" cannot contain more than 12 items.`);
305
+ }
306
+ const tooLong = blockedFields.find((f) => f.length > 100);
307
+ if (tooLong) {
308
+ throw new Error(`Database "${payload.name}": Each item in "profile_data.blocked_fields" must not exceed 100 characters.`);
309
+ }
310
+ }
311
+ }
227
312
  validatePasswordlessSettings(payload) {
228
313
  const options = payload?.options;
229
314
  if (!options)
@@ -238,6 +323,11 @@ class DatabaseHandler extends default_1.default {
238
323
  if (passwordEnabled === true && disableSelfServiceChangePassword === true) {
239
324
  throw new Error(`Database "${payload.name}": disable_self_service_change_password must be false when password authentication is enabled.`);
240
325
  }
326
+ const apiBehavior = options?.authentication_methods?.password?.api_behavior;
327
+ const signupBehavior = options?.authentication_methods?.password?.signup_behavior;
328
+ if (signupBehavior === 'block' && apiBehavior !== 'optional') {
329
+ throw new Error(`Database "${payload.name}": When "signup_behavior" is "block", "api_behavior" must be "optional".`);
330
+ }
241
331
  }
242
332
  validateEmailUniqueConstraints(payload) {
243
333
  const attributes = payload?.options?.attributes;
@@ -288,6 +378,35 @@ class DatabaseHandler extends default_1.default {
288
378
  else if (requiresUsername || validation) {
289
379
  delete connection.options?.attributes;
290
380
  }
381
+ // When switching between flexible and legacy password policy, strip the conflicting
382
+ // group from the existing state before merging to avoid a 400 from the API.
383
+ const payloadHasPasswordOptions = !!payload?.options?.password_options;
384
+ const payloadHasLegacyPasswordFields = payload?.options?.passwordPolicy ||
385
+ payload?.options?.password_complexity_options ||
386
+ payload?.options?.password_history ||
387
+ payload?.options?.password_no_personal_info ||
388
+ payload?.options?.password_dictionary;
389
+ if (payloadHasPasswordOptions) {
390
+ delete connection.options?.passwordPolicy;
391
+ delete connection.options?.password_complexity_options;
392
+ delete connection.options?.password_history;
393
+ delete connection.options?.password_no_personal_info;
394
+ delete connection.options?.password_dictionary;
395
+ }
396
+ else if (payloadHasLegacyPasswordFields) {
397
+ delete connection.options?.password_options;
398
+ }
399
+ // If signup_behavior is being set to "block", api_behavior must be "optional".
400
+ // Check the merged result upfront so the user gets a clear error rather than a
401
+ // cryptic 400 from the API.
402
+ const mergedApiBehavior = payload?.options?.authentication_methods?.password?.api_behavior ??
403
+ connection.options?.authentication_methods?.password?.api_behavior;
404
+ const payloadSignupBehavior = payload?.options?.authentication_methods?.password?.signup_behavior;
405
+ if (payloadSignupBehavior === 'block' && mergedApiBehavior !== 'optional') {
406
+ throw new Error(`Database "${payload.name}": Cannot set "signup_behavior" to "block" without "api_behavior" set to "optional". ` +
407
+ `The existing tenant value for "api_behavior" is "${mergedApiBehavior ?? 'required (default)'}". ` +
408
+ `Please explicitly set "api_behavior" to "optional" in your config.`);
409
+ }
291
410
  payload.options = { ...connection.options, ...payload.options };
292
411
  if (payload.options && Object.keys(payload.options).length === 0) {
293
412
  delete payload.options;
@@ -388,6 +507,47 @@ class DatabaseHandler extends default_1.default {
388
507
  });
389
508
  return super.calcChanges({ ...assets, databases: formatted });
390
509
  }
510
+ async dryRunChanges(assets) {
511
+ const { databases } = assets;
512
+ if (!databases) {
513
+ return {
514
+ del: [],
515
+ create: [],
516
+ update: [],
517
+ conflicts: [],
518
+ };
519
+ }
520
+ const [clients, existingDatabasesConnections, actions] = await Promise.all([
521
+ (0, client_1.paginate)(this.client.clients.list, {
522
+ paginate: true,
523
+ }),
524
+ (0, client_1.paginate)(this.client.connections.list, {
525
+ strategy: [auth0_1.Management.ConnectionStrategyEnum.Auth0],
526
+ checkpoint: true,
527
+ include_totals: true,
528
+ }),
529
+ (0, client_1.paginate)(this.client.actions.list, {
530
+ paginate: true,
531
+ include_totals: true,
532
+ }),
533
+ ]);
534
+ const formatted = databases.map((db) => {
535
+ const { options, ...rest } = db;
536
+ const formattedOptions = this.getFormattedOptions(options, actions);
537
+ const formattedDb = { ...rest, options: formattedOptions };
538
+ if (db.enabled_clients) {
539
+ formattedDb.enabled_clients = (0, utils_1.getEnabledClients)(assets, db, existingDatabasesConnections, clients);
540
+ }
541
+ return formattedDb;
542
+ });
543
+ return (0, calculateDryRunChanges_1.calculateDryRunChanges)({
544
+ type: this.type,
545
+ assets: formatted,
546
+ existing: existingDatabasesConnections,
547
+ identifiers: this.identifiers,
548
+ ignoreDryRunFields: this.ignoreDryRunFields,
549
+ });
550
+ }
391
551
  // Run after clients are updated so we can convert all the enabled_clients names to id's
392
552
  async processChanges(assets) {
393
553
  const { databases } = assets;
@@ -23,6 +23,7 @@ export default class APIHandler {
23
23
  create: ApiMethodOverride;
24
24
  delete: ApiMethodOverride;
25
25
  };
26
+ ignoreDryRunFields: string[];
26
27
  constructor(options: {
27
28
  id?: APIHandler['id'];
28
29
  config: ConfigFunction;
@@ -39,17 +40,20 @@ export default class APIHandler {
39
40
  create?: ApiMethodOverride;
40
41
  delete?: ApiMethodOverride;
41
42
  };
43
+ ignoreDryRunFields: APIHandler['ignoreDryRunFields'];
42
44
  });
43
45
  getClientFN(fn: ApiMethodOverride): Function;
44
46
  didDelete(item: Asset): void;
45
47
  didCreate(item: Asset): void;
46
48
  didUpdate(item: Asset): void;
47
49
  objString(item: Asset): string;
50
+ getResourceName(item: Asset): string;
48
51
  getType(): Promise<Asset | Asset[] | null>;
49
52
  load(): Promise<{
50
53
  [key: string]: Asset | Asset[] | null;
51
54
  }>;
52
55
  calcChanges(assets: Assets): Promise<CalculatedChanges>;
56
+ dryRunChanges(assets: Assets): Promise<CalculatedChanges>;
53
57
  validate(assets: Assets): Promise<void>;
54
58
  processChanges(assets: Assets, changes: CalculatedChanges): Promise<void>;
55
59
  }
@@ -9,6 +9,7 @@ const validationError_1 = __importDefault(require("../../validationError"));
9
9
  const utils_1 = require("../../utils");
10
10
  const logger_1 = __importDefault(require("../../../logger"));
11
11
  const calculateChanges_1 = require("../../calculateChanges");
12
+ const calculateDryRunChanges_1 = require("../../calculateDryRunChanges");
12
13
  function order(value) {
13
14
  return function decorator(t, n, descriptor) {
14
15
  const numericValue = Number(value);
@@ -75,6 +76,7 @@ class APIHandler {
75
76
  this.stripUpdateFields = [...(options.stripUpdateFields || []), this.id];
76
77
  this.sensitiveFieldsToObfuscate = options.sensitiveFieldsToObfuscate || [];
77
78
  this.stripCreateFields = options.stripCreateFields || [];
79
+ this.ignoreDryRunFields = [...(options.ignoreDryRunFields || [])].filter((field, index, allFields) => allFields.indexOf(field) === index);
78
80
  this.functions = {
79
81
  list: 'list',
80
82
  create: 'create',
@@ -115,6 +117,44 @@ class APIHandler {
115
117
  objString(item) {
116
118
  return (0, utils_1.convertJsonToString)(item);
117
119
  }
120
+ getResourceName(item) {
121
+ // Get a human-readable identifier for the resource
122
+ if (item.name)
123
+ return item.name;
124
+ if (item.display_name)
125
+ return item.display_name;
126
+ if (item.template)
127
+ return item.template;
128
+ if (item.email)
129
+ return item.email;
130
+ // For some resources, create a descriptive name
131
+ if (this.type === 'clientGrants') {
132
+ const clientLabel = item._clientName || item.client_id;
133
+ return `${clientLabel} -> ${item.audience}`;
134
+ }
135
+ if (this.type === 'guardianFactors') {
136
+ return item.name || item.type || 'unknown factor';
137
+ }
138
+ if (this.type === 'emailTemplates') {
139
+ return item.template || 'unknown template';
140
+ }
141
+ if (this.type === 'networkACLs') {
142
+ return item.description || `priority:${item.priority}`;
143
+ }
144
+ if (this.type === 'customDomains') {
145
+ return item.domain || 'unnamed custom domain';
146
+ }
147
+ if (this.type === 'tenant' ||
148
+ this.type === 'attackProtection' ||
149
+ this.type === 'branding' ||
150
+ this.type === 'emailProvider' ||
151
+ this.type === 'guardianPhoneFactorSelectedProvider' ||
152
+ this.type === 'guardianPolicies' ||
153
+ this.type === 'riskAssessment') {
154
+ return `${this.type} settings`;
155
+ }
156
+ return 'unnamed resource';
157
+ }
118
158
  async getType() {
119
159
  // Each type to impl how to get the existing as its not consistent across the mgnt api.
120
160
  throw new Error(`Must implement getType for type ${this.type}`);
@@ -145,14 +185,49 @@ class APIHandler {
145
185
  };
146
186
  }
147
187
  const existing = await this.getType();
188
+ // if we are in dry run mode, calculate the changes
189
+ // Figure out what needs to be updated vs created
190
+ if (this.config('AUTH0_DRY_RUN') === true || this.config('AUTH0_DRY_RUN') === 'true') {
191
+ return (0, calculateDryRunChanges_1.calculateDryRunChanges)({
192
+ type: this.type,
193
+ assets: typeAssets,
194
+ // @ts-ignore TODO: investigate what happens when `existing` is null
195
+ existing,
196
+ identifiers: this.identifiers,
197
+ ignoreDryRunFields: this.ignoreDryRunFields,
198
+ });
199
+ }
200
+ // if we are not in dry run mode, calculate the changes
148
201
  // Figure out what needs to be updated vs created
149
202
  return (0, calculateChanges_1.calculateChanges)({
150
203
  handler: this,
151
204
  assets: typeAssets,
152
205
  allowDelete: !!this.config('AUTH0_ALLOW_DELETE'),
153
- //@ts-ignore TODO: investigate what happens when `existing` is null
206
+ // @ts-ignore TODO: investigate what happens when `existing` is null
207
+ existing,
208
+ identifiers: this.identifiers,
209
+ });
210
+ }
211
+ async dryRunChanges(assets) {
212
+ const typeAssets = assets[this.type];
213
+ // Do nothing if not set
214
+ if (!typeAssets) {
215
+ return {
216
+ del: [],
217
+ create: [],
218
+ conflicts: [],
219
+ update: [],
220
+ };
221
+ }
222
+ const existing = await this.getType();
223
+ // Figure out what needs to be created, updated or deleted
224
+ return (0, calculateDryRunChanges_1.calculateDryRunChanges)({
225
+ type: this.type,
226
+ assets: typeAssets,
227
+ // @ts-ignore TODO: investigate what happens when `existing` is null
154
228
  existing,
155
229
  identifiers: this.identifiers,
230
+ ignoreDryRunFields: this.ignoreDryRunFields,
156
231
  });
157
232
  }
158
233
  async validate(assets) {
@@ -234,7 +309,8 @@ class APIHandler {
234
309
  const updateFN = this.getClientFN(this.functions.update);
235
310
  const updatePayload = (() => {
236
311
  const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
237
- return (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
312
+ const stripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
313
+ return (0, utils_1.validateNoUnresolvedPlaceholders)(stripped, this.type, this.objString(updateItem));
238
314
  })();
239
315
  return updateFN(updateItem[this.id], updatePayload);
240
316
  }, retryConfig)
@@ -252,7 +328,8 @@ class APIHandler {
252
328
  const createFunction = this.getClientFN(this.functions.create);
253
329
  const createPayload = (() => {
254
330
  const strippedPayload = (0, utils_1.stripFields)(createItem, this.stripCreateFields);
255
- return (0, utils_1.stripObfuscatedFieldsFromPayload)(strippedPayload, this.sensitiveFieldsToObfuscate);
331
+ const stripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(strippedPayload, this.sensitiveFieldsToObfuscate);
332
+ return (0, utils_1.validateNoUnresolvedPlaceholders)(stripped, this.type, this.objString(createItem));
256
333
  })();
257
334
  return createFunction(createPayload);
258
335
  }, retryConfig)
@@ -273,7 +350,8 @@ class APIHandler {
273
350
  const updateFN = this.getClientFN(this.functions.update);
274
351
  const updatePayload = (() => {
275
352
  const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
276
- return (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
353
+ const stripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
354
+ return (0, utils_1.validateNoUnresolvedPlaceholders)(stripped, this.type, this.objString(updateItem));
277
355
  })();
278
356
  return updateFN(updateItem[this.id], updatePayload);
279
357
  }, retryConfig)
@@ -41,6 +41,7 @@ var __importStar = (this && this.__importStar) || (function () {
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.schema = void 0;
43
43
  const lodash_1 = require("lodash");
44
+ const utils_1 = require("../../utils");
44
45
  const default_1 = __importStar(require("./default"));
45
46
  exports.schema = { type: 'object' };
46
47
  // The Management API requires the fields to be specified
@@ -50,6 +51,7 @@ class EmailProviderHandler extends default_1.default {
50
51
  super({
51
52
  ...options,
52
53
  type: 'emailProvider',
54
+ ignoreDryRunFields: ['smtp.credentials.smtp_pass', 'mandrill.credentials.api_key'],
53
55
  });
54
56
  }
55
57
  async getType() {
@@ -73,6 +75,12 @@ class EmailProviderHandler extends default_1.default {
73
75
  const { emailProvider } = assets;
74
76
  if (!emailProvider)
75
77
  return;
78
+ if ((0, utils_1.isDryRun)(this.config)) {
79
+ const { del, update, create } = await this.calcChanges(assets);
80
+ if (create.length === 0 && update.length === 0 && del.length === 0) {
81
+ return;
82
+ }
83
+ }
76
84
  const existing = await this.getType();
77
85
  // HTTP DELETE on emails/provider is not supported, as this is not part of our vNext SDK.
78
86
  if (Object.keys(emailProvider).length === 0) {
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.schema = exports.supportedTemplates = void 0;
46
46
  const default_1 = __importStar(require("./default"));
47
47
  const constants_1 = __importDefault(require("../../constants"));
48
+ const utils_1 = require("../../utils");
48
49
  exports.supportedTemplates = constants_1.default.EMAIL_TEMPLATES_NAMES.filter((p) => p.includes('.json')).map((p) => p.replace('.json', ''));
49
50
  exports.schema = {
50
51
  type: 'array',
@@ -122,6 +123,12 @@ class EmailTemplateHandler extends default_1.default {
122
123
  // Do nothing if not set
123
124
  if (!emailTemplates || !emailTemplates.length)
124
125
  return;
126
+ if ((0, utils_1.isDryRun)(this.config)) {
127
+ const { update, create } = await this.calcChanges(assets);
128
+ if (create.length === 0 && update.length === 0) {
129
+ return;
130
+ }
131
+ }
125
132
  await Promise.all(emailTemplates.map(async (emailTemplate) => {
126
133
  await this.updateOrCreate(emailTemplate);
127
134
  }));
@@ -47,6 +47,7 @@ const lodash_1 = require("lodash");
47
47
  const default_1 = __importStar(require("./default"));
48
48
  const constants_1 = __importDefault(require("../../constants"));
49
49
  const logger_1 = __importDefault(require("../../../logger"));
50
+ const utils_1 = require("../../utils");
50
51
  exports.schema = {
51
52
  type: 'array',
52
53
  items: {
@@ -102,7 +103,11 @@ class FlowVaultHandler extends default_1.default {
102
103
  if (!flowVaultConnections)
103
104
  return;
104
105
  const { del, update, create } = await this.calcChanges(assets);
105
- logger_1.default.debug(`Start processChanges for flow vault connections [delete:${del.length}] [update:${update.length}], [create:${create.length}]`);
106
+ if ((0, utils_1.isDryRun)(this.config)) {
107
+ if (create.length === 0 && update.length === 0 && del.length === 0) {
108
+ return;
109
+ }
110
+ }
106
111
  if (del.length > 0) {
107
112
  await this.deleteVaultConnections(del);
108
113
  }
@@ -48,7 +48,8 @@ const dot_prop_1 = __importDefault(require("dot-prop"));
48
48
  const default_1 = __importStar(require("./default"));
49
49
  const client_1 = require("../client");
50
50
  const logger_1 = __importDefault(require("../../../logger"));
51
- const utils_1 = require("../../../utils");
51
+ const utils_1 = require("../../utils");
52
+ const utils_2 = require("../../../utils");
52
53
  const flowVaultConnections_1 = require("./flowVaultConnections");
53
54
  exports.schema = {
54
55
  type: 'array',
@@ -113,6 +114,15 @@ class FlowHandler extends default_1.default {
113
114
  // Do nothing if not set
114
115
  if (!flows)
115
116
  return;
117
+ const { del, update, create, conflicts } = await this.calcChanges(assets);
118
+ if ((0, utils_1.isDryRun)(this.config)) {
119
+ if (create.length === 0 &&
120
+ update.length === 0 &&
121
+ del.length === 0 &&
122
+ conflicts.length === 0) {
123
+ return;
124
+ }
125
+ }
116
126
  const allFlowConnections = await (0, flowVaultConnections_1.getAllFlowConnections)(this.client);
117
127
  // create a map for name to id from allFlowConnections
118
128
  const connectionNameMap = {};
@@ -120,7 +130,6 @@ class FlowHandler extends default_1.default {
120
130
  connectionNameMap[c.name] = c.id;
121
131
  });
122
132
  assets.flows = await this.pargeFlowConnectionName(flows, connectionNameMap);
123
- const { del, update, create, conflicts } = await this.calcChanges(assets);
124
133
  const changes = {
125
134
  del: del,
126
135
  update: update,
@@ -134,7 +143,7 @@ class FlowHandler extends default_1.default {
134
143
  async formateFlowConnectionId(flows, connectionIdMap) {
135
144
  // replace connection_id with flow connection names
136
145
  await Promise.all(flows.map(async (flow) => {
137
- const flowConnetions = (0, utils_1.findKeyPathWithValue)(flow, 'connection_id');
146
+ const flowConnetions = (0, utils_2.findKeyPathWithValue)(flow, 'connection_id');
138
147
  await Promise.all(flowConnetions.map(async (f) => {
139
148
  const connectionId = (dot_prop_1.default.get(flow, f.path) || '');
140
149
  const flowConnectionName = connectionIdMap[connectionId];
@@ -152,7 +161,7 @@ class FlowHandler extends default_1.default {
152
161
  async pargeFlowConnectionName(flows, connectionNameMap) {
153
162
  // replace connection_id with flow connection names
154
163
  const parsedFlows = await Promise.all(flows.map(async (flow) => {
155
- const flowConnetions = (0, utils_1.findKeyPathWithValue)(flow, 'connection_id');
164
+ const flowConnetions = (0, utils_2.findKeyPathWithValue)(flow, 'connection_id');
156
165
  await Promise.all(flowConnetions.map(async (f) => {
157
166
  const connectionName = (dot_prop_1.default.get(flow, f.path) || '');
158
167
  const flowConnectionId = connectionNameMap[connectionName];
@@ -49,6 +49,7 @@ const default_1 = __importStar(require("./default"));
49
49
  const logger_1 = __importDefault(require("../../../logger"));
50
50
  const client_1 = require("../client");
51
51
  const utils_1 = require("../../../utils");
52
+ const utils_2 = require("../../utils");
52
53
  exports.schema = {
53
54
  type: 'array',
54
55
  items: {
@@ -150,6 +151,15 @@ class FormsHandler extends default_1.default {
150
151
  // Do nothing if not set
151
152
  if (!forms)
152
153
  return;
154
+ const { del, update, create, conflicts } = await this.calcChanges(assets);
155
+ if ((0, utils_2.isDryRun)(this.config)) {
156
+ if (create.length === 0 &&
157
+ update.length === 0 &&
158
+ del.length === 0 &&
159
+ conflicts.length === 0) {
160
+ return;
161
+ }
162
+ }
153
163
  const flows = await (0, client_1.paginate)(this.client.flows.list, {
154
164
  paginate: true,
155
165
  include_totals: true,
@@ -160,7 +170,6 @@ class FormsHandler extends default_1.default {
160
170
  flowNamMap[f.name] = f.id;
161
171
  });
162
172
  assets.forms = await this.pargeFormFlowName(forms, flowNamMap);
163
- const { del, update, create, conflicts } = await this.calcChanges(assets);
164
173
  const changes = {
165
174
  del: del,
166
175
  update: update,
@@ -30,7 +30,7 @@ class GuardianFactorsHandler extends default_1.default {
30
30
  return this.existing;
31
31
  try {
32
32
  const factors = await this.client.guardian.factors.list();
33
- this.existing = factors;
33
+ this.existing = (0, utils_1.sortGuardianFactors)(factors);
34
34
  return this.existing;
35
35
  }
36
36
  catch (err) {
@@ -49,6 +49,12 @@ class GuardianFactorsHandler extends default_1.default {
49
49
  // Do nothing if not set
50
50
  if (!guardianFactors || !guardianFactors.length)
51
51
  return;
52
+ if ((0, utils_1.isDryRun)(this.config)) {
53
+ const { del, update, create } = await this.calcChanges(assets);
54
+ if (create.length === 0 && update.length === 0 && del.length === 0) {
55
+ return;
56
+ }
57
+ }
52
58
  // Process each factor
53
59
  await Promise.all(guardianFactors.map(async (factor) => {
54
60
  const data = { ...factor };
@@ -65,8 +65,15 @@ class GuardianPhoneMessageTypesHandler extends default_1.default {
65
65
  // No API to delete or create guardianPhoneFactorMessageTypes, we can only update.
66
66
  const { guardianPhoneFactorMessageTypes } = assets;
67
67
  // Do nothing if not set
68
- if (!guardianPhoneFactorMessageTypes || !guardianPhoneFactorMessageTypes.message_types)
68
+ if (!guardianPhoneFactorMessageTypes || !guardianPhoneFactorMessageTypes.message_types) {
69
69
  return;
70
+ }
71
+ if ((0, utils_1.isDryRun)(this.config)) {
72
+ const { del, update, create } = await this.calcChanges(assets);
73
+ if (create.length === 0 && update.length === 0 && del.length === 0) {
74
+ return;
75
+ }
76
+ }
70
77
  const data = guardianPhoneFactorMessageTypes;
71
78
  await this.client.guardian.factors.phone.setMessageTypes(data);
72
79
  this.updated += 1;
@@ -62,8 +62,15 @@ class GuardianPhoneSelectedProviderHandler extends default_1.default {
62
62
  // No API to delete or create guardianPhoneFactorSelectedProvider, we can only update.
63
63
  const { guardianPhoneFactorSelectedProvider } = assets;
64
64
  // Do nothing if not set
65
- if (!guardianPhoneFactorSelectedProvider || !guardianPhoneFactorSelectedProvider.provider)
65
+ if (!guardianPhoneFactorSelectedProvider || !guardianPhoneFactorSelectedProvider.provider) {
66
66
  return;
67
+ }
68
+ if ((0, utils_1.isDryRun)(this.config)) {
69
+ const { del, update, create } = await this.calcChanges(assets);
70
+ if (create.length === 0 && update.length === 0 && del.length === 0) {
71
+ return;
72
+ }
73
+ }
67
74
  const data = guardianPhoneFactorSelectedProvider;
68
75
  await this.client.guardian.factors.phone.setProvider(data);
69
76
  this.updated += 1;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.schema = void 0;
7
7
  const default_1 = __importDefault(require("./default"));
8
8
  const constants_1 = __importDefault(require("../../constants"));
9
+ const utils_1 = require("../../utils");
9
10
  exports.schema = {
10
11
  type: 'object',
11
12
  properties: {
@@ -40,6 +41,12 @@ class GuardianPoliciesHandler extends default_1.default {
40
41
  // Do nothing if not set
41
42
  if (!guardianPolicies || !guardianPolicies.policies)
42
43
  return;
44
+ if ((0, utils_1.isDryRun)(this.config)) {
45
+ const { del, update, create } = await this.calcChanges(assets);
46
+ if (create.length === 0 && update.length === 0 && del.length === 0) {
47
+ return;
48
+ }
49
+ }
43
50
  const data = guardianPolicies.policies;
44
51
  await this.client.guardian.policies.set(data);
45
52
  this.updated += 1;
@@ -53,6 +53,7 @@ export default class HooksHandler extends DefaultHandler {
53
53
  processSecrets(hooks: any): Promise<void>;
54
54
  getType(reload: boolean): Promise<Asset[] | null>;
55
55
  calcChanges(assets: Assets): Promise<CalculatedChanges>;
56
+ dryRunChanges(assets: Assets): Promise<CalculatedChanges>;
56
57
  validate(assets: any): Promise<void>;
57
58
  processChanges(assets: Assets): Promise<void>;
58
59
  }
@@ -175,6 +175,16 @@ class HooksHandler extends default_1.default {
175
175
  conflicts: stripSecrets(conflicts),
176
176
  };
177
177
  }
178
+ async dryRunChanges(assets) {
179
+ const { del, update, create, conflicts } = await super.dryRunChanges(assets);
180
+ const stripSecrets = (list) => list.map((item) => ({ ...item, secrets: undefined }));
181
+ return {
182
+ del,
183
+ update: stripSecrets(update),
184
+ create: stripSecrets(create),
185
+ conflicts: stripSecrets(conflicts),
186
+ };
187
+ }
178
188
  async validate(assets) {
179
189
  const { hooks } = assets;
180
190
  // Do nothing if not set