auth0-deploy-cli 9.0.0-beta.2 → 9.0.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 (126) hide show
  1. package/AGENTS.md +2 -357
  2. package/CHANGELOG.md +158 -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 +1 -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 +31 -31
  34. package/lib/context/yaml/handlers/attackProtection.js +1 -1
  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 +1 -4
  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.js +19 -6
  59. package/lib/tools/auth0/handlers/clientAuthCredentials.d.ts +27 -0
  60. package/lib/tools/auth0/handlers/clientAuthCredentials.js +234 -0
  61. package/lib/tools/auth0/handlers/clientAuthCredentialsPre.d.ts +38 -0
  62. package/lib/tools/auth0/handlers/clientAuthCredentialsPre.js +123 -0
  63. package/lib/tools/auth0/handlers/clientGrants.d.ts +1 -1
  64. package/lib/tools/auth0/handlers/clients.d.ts +106 -0
  65. package/lib/tools/auth0/handlers/clients.js +206 -2
  66. package/lib/tools/auth0/handlers/connectionProfiles.d.ts +27 -0
  67. package/lib/tools/auth0/handlers/connectionProfiles.js +30 -0
  68. package/lib/tools/auth0/handlers/connections.d.ts +32 -6
  69. package/lib/tools/auth0/handlers/connections.js +49 -7
  70. package/lib/tools/auth0/handlers/databases.d.ts +28 -0
  71. package/lib/tools/auth0/handlers/databases.js +58 -24
  72. package/lib/tools/auth0/handlers/default.d.ts +23 -0
  73. package/lib/tools/auth0/handlers/default.js +47 -29
  74. package/lib/tools/auth0/handlers/eventStreams.js +10 -2
  75. package/lib/tools/auth0/handlers/guardianEmailFactorSettings.d.ts +22 -0
  76. package/lib/tools/auth0/handlers/guardianEmailFactorSettings.js +77 -0
  77. package/lib/tools/auth0/handlers/guardianFactorTemplates.js +13 -4
  78. package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +11 -9
  79. package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +11 -9
  80. package/lib/tools/auth0/handlers/guardianPhoneFactorSettings.d.ts +22 -0
  81. package/lib/tools/auth0/handlers/guardianPhoneFactorSettings.js +77 -0
  82. package/lib/tools/auth0/handlers/guardianPolicies.js +11 -1
  83. package/lib/tools/auth0/handlers/guardianSettings.d.ts +30 -0
  84. package/lib/tools/auth0/handlers/guardianSettings.js +85 -0
  85. package/lib/tools/auth0/handlers/hooks.js +1 -1
  86. package/lib/tools/auth0/handlers/index.js +16 -0
  87. package/lib/tools/auth0/handlers/networkACLKeys.d.ts +45 -0
  88. package/lib/tools/auth0/handlers/networkACLKeys.js +177 -0
  89. package/lib/tools/auth0/handlers/networkACLs.d.ts +132 -2
  90. package/lib/tools/auth0/handlers/networkACLs.js +100 -16
  91. package/lib/tools/auth0/handlers/organizations.d.ts +33 -1
  92. package/lib/tools/auth0/handlers/organizations.js +177 -19
  93. package/lib/tools/auth0/handlers/phoneProvider.js +10 -2
  94. package/lib/tools/auth0/handlers/phoneTemplates.d.ts +4 -1
  95. package/lib/tools/auth0/handlers/phoneTemplates.js +67 -10
  96. package/lib/tools/auth0/handlers/prompts.js +1 -1
  97. package/lib/tools/auth0/handlers/rateLimitPolicies.d.ts +81 -0
  98. package/lib/tools/auth0/handlers/rateLimitPolicies.js +203 -0
  99. package/lib/tools/auth0/handlers/resourceServers.d.ts +44 -0
  100. package/lib/tools/auth0/handlers/resourceServers.js +44 -0
  101. package/lib/tools/auth0/handlers/riskAssessment.d.ts +1 -1
  102. package/lib/tools/auth0/handlers/riskAssessment.js +19 -1
  103. package/lib/tools/auth0/handlers/roles.d.ts +4 -0
  104. package/lib/tools/auth0/handlers/roles.js +10 -0
  105. package/lib/tools/auth0/handlers/rules.js +19 -7
  106. package/lib/tools/auth0/handlers/tenant.d.ts +45 -0
  107. package/lib/tools/auth0/handlers/tenant.js +48 -0
  108. package/lib/tools/auth0/handlers/themes.d.ts +37 -1
  109. package/lib/tools/auth0/handlers/themes.js +64 -0
  110. package/lib/tools/auth0/handlers/tokenExchangeProfiles.js +3 -1
  111. package/lib/tools/auth0/index.js +12 -6
  112. package/lib/tools/calculateDryRunChanges.js +12 -8
  113. package/lib/tools/constants.d.ts +2 -0
  114. package/lib/tools/constants.js +3 -0
  115. package/lib/tools/utils.d.ts +3 -0
  116. package/lib/tools/utils.js +41 -2
  117. package/lib/types.d.ts +9 -1
  118. package/lib/utils.d.ts +6 -0
  119. package/lib/utils.js +17 -2
  120. package/package.json +2 -2
  121. package/references/code-style.md +133 -0
  122. package/references/commands.md +98 -0
  123. package/references/docs-update.md +34 -0
  124. package/references/git-workflow.md +135 -0
  125. package/references/pitfalls.md +62 -0
  126. package/references/testing.md +118 -0
@@ -51,6 +51,12 @@ const logger_1 = __importDefault(require("../../../logger"));
51
51
  const client_1 = require("../client");
52
52
  const utils_1 = require("../../../utils");
53
53
  const utils_2 = require("../../utils");
54
+ // Org sub-resources are skipped when the tenant can't read them: the EA feature is off
55
+ // (feature_not_enabled, returned as 400 or 403) or the token lacks scope (403). The SDK
56
+ // puts the API code on `err.body.errorCode`, not `err.errorCode`.
57
+ function isOrgSubresourceUnavailable(err) {
58
+ return err?.statusCode === 403 || err?.body?.errorCode === 'feature_not_enabled';
59
+ }
54
60
  exports.schema = {
55
61
  type: 'array',
56
62
  items: {
@@ -127,6 +133,23 @@ exports.schema = {
127
133
  required: ['domain', 'status'],
128
134
  },
129
135
  },
136
+ third_party_client_access: {
137
+ type: 'string',
138
+ enum: Object.values(auth0_1.Management.OrganizationThirdPartyClientAccessEnum),
139
+ },
140
+ is_app_entitlement_active: { type: 'boolean' },
141
+ clients: {
142
+ type: 'array',
143
+ items: {
144
+ type: 'object',
145
+ properties: {
146
+ client_id: { type: 'string' },
147
+ use_for_member_access: { type: 'boolean' },
148
+ },
149
+ required: ['client_id', 'use_for_member_access'],
150
+ },
151
+ default: [],
152
+ },
130
153
  },
131
154
  required: ['name'],
132
155
  },
@@ -168,6 +191,7 @@ class OrganizationsHandler extends default_1.default {
168
191
  const organization = { ...org };
169
192
  delete organization.connections;
170
193
  delete organization.client_grants;
194
+ delete organization.clients;
171
195
  if ('discovery_domains' in organization) {
172
196
  delete organization.discovery_domains;
173
197
  }
@@ -176,8 +200,9 @@ class OrganizationsHandler extends default_1.default {
176
200
  throw new Error(`Organization "${organization.name}" was created but the response did not include an ID. Skipping connection/grant association.`);
177
201
  }
178
202
  const createdId = created.id;
203
+ const retryConfig = this.getRetryConfig();
179
204
  if (typeof org.connections !== 'undefined' && org.connections.length > 0) {
180
- await Promise.all(org.connections.map((conn) => this.client.organizations.connections.create(createdId, conn)));
205
+ await Promise.all(org.connections.map((conn) => (0, default_1.retryWithExponentialBackoff)(() => this.client.organizations.connections.create(createdId, conn), retryConfig)));
181
206
  }
182
207
  if (typeof org.client_grants !== 'undefined' && org.client_grants.length > 0) {
183
208
  await Promise.all(org.client_grants.map((organizationClientGrants) => this.createOrganizationClientGrants(createdId, this.getClientGrantIDByClientName(organizationClientGrants.client_id))));
@@ -191,6 +216,19 @@ class OrganizationsHandler extends default_1.default {
191
216
  throw new Error(`Problem creating discovery domain ${discoveryDomain?.domain} for organization ${createdId}\n${err}`);
192
217
  })));
193
218
  }
219
+ if (typeof org.clients !== 'undefined' && org.clients.length > 0) {
220
+ const clientsToCreate = org.clients
221
+ .map((oc) => ({
222
+ client_id: this.getClientIdByClientName(oc.client_id),
223
+ use_for_member_access: oc.use_for_member_access,
224
+ }))
225
+ .filter((oc) => !!oc.client_id);
226
+ if (clientsToCreate.length > 0) {
227
+ await this.createOrganizationClients(createdId, clientsToCreate).catch((err) => {
228
+ throw new Error(`Problem creating org clients for organization ${createdId}\n${err}`);
229
+ });
230
+ }
231
+ }
194
232
  return created;
195
233
  }
196
234
  async createOrganizations(creates) {
@@ -209,15 +247,24 @@ class OrganizationsHandler extends default_1.default {
209
247
  .promise();
210
248
  }
211
249
  async updateOrganization(org, organizations) {
212
- const { connections: existingConnections, client_grants: existingClientGrants, discovery_domains: existingDiscoveryDomains, } = await organizations.find((orgToUpdate) => orgToUpdate.name === org.name);
250
+ const existingOrg = organizations.find((orgToUpdate) => orgToUpdate.name === org.name);
251
+ const { connections: existingConnections, client_grants: existingClientGrants, discovery_domains: existingDiscoveryDomains, clients: existingOrgClients = [], } = existingOrg;
213
252
  const params = { id: org.id };
214
- const { connections, client_grants: organizationClientGrants, discovery_domains: organizationDiscoveryDomains, } = org;
253
+ const { connections, client_grants: organizationClientGrants, discovery_domains: organizationDiscoveryDomains, clients: organizationClients, } = org;
215
254
  delete org.connections;
216
255
  delete org.name;
217
256
  delete org.id;
218
257
  delete org.client_grants;
219
258
  delete org.discovery_domains;
220
- await this.client.organizations.update(params.id, org);
259
+ delete org.clients;
260
+ // Only PATCH if top-level properties actually differ from the existing state.
261
+ // Compare only the keys present in the desired config against those same keys
262
+ // on the remote org, so that fields the user didn't specify are not considered.
263
+ let changed = false;
264
+ if (!(0, lodash_1.isEqual)(org, (0, lodash_1.pick)(existingOrg, Object.keys(org)))) {
265
+ await this.client.organizations.update(params.id, org);
266
+ changed = true;
267
+ }
221
268
  // organization connections
222
269
  const connectionsToRemove = existingConnections.filter((c) => !connections.find((x) => x.connection_id === c.connection_id));
223
270
  const connectionsToAdd = connections.filter((c) => !existingConnections.find((x) => x.connection_id === c.connection_id));
@@ -228,27 +275,27 @@ class OrganizationsHandler extends default_1.default {
228
275
  x.organization_access_level !== c.organization_access_level ||
229
276
  x.organization_connection_name !== c.organization_connection_name ||
230
277
  x.is_enabled !== (c.is_enabled ?? true))));
278
+ if (connectionsToUpdate.length > 0 ||
279
+ connectionsToAdd.length > 0 ||
280
+ connectionsToRemove.length > 0) {
281
+ changed = true;
282
+ }
283
+ const retryConfig = this.getRetryConfig();
231
284
  // Handle updates first
232
- await Promise.all(connectionsToUpdate.map((conn) => this.client.organizations.connections
233
- .update(params.id, conn.connection_id, {
285
+ await Promise.all(connectionsToUpdate.map((conn) => (0, default_1.retryWithExponentialBackoff)(() => this.client.organizations.connections.update(params.id, conn.connection_id, {
234
286
  organization_connection_name: conn.organization_connection_name,
235
287
  assign_membership_on_login: conn.assign_membership_on_login,
236
288
  show_as_button: conn.show_as_button,
237
289
  is_signup_enabled: conn.is_signup_enabled,
238
290
  is_enabled: conn.is_enabled,
239
291
  organization_access_level: conn.organization_access_level,
240
- })
241
- .catch(() => {
292
+ }), retryConfig).catch(() => {
242
293
  throw new Error(`Problem updating Enabled Connection ${conn.connection_id} for organizations ${params.id}`);
243
294
  })));
244
- await Promise.all(connectionsToAdd.map((conn) => this.client.organizations.connections
245
- .create(params.id, (0, lodash_1.omit)(conn, 'connection'))
246
- .catch(() => {
295
+ await Promise.all(connectionsToAdd.map((conn) => (0, default_1.retryWithExponentialBackoff)(() => this.client.organizations.connections.create(params.id, (0, lodash_1.omit)(conn, 'connection')), retryConfig).catch(() => {
247
296
  throw new Error(`Problem adding Enabled Connection ${conn.connection_id} for organizations ${params.id}`);
248
297
  })));
249
- await Promise.all(connectionsToRemove.map((conn) => this.client.organizations.connections
250
- .delete(params.id, conn.connection_id)
251
- .catch(() => {
298
+ await Promise.all(connectionsToRemove.map((conn) => (0, default_1.retryWithExponentialBackoff)(() => this.client.organizations.connections.delete(params.id, conn.connection_id), retryConfig).catch(() => {
252
299
  throw new Error(`Problem removing Enabled Connection ${conn.connection_id} for organizations ${params.id}`);
253
300
  })));
254
301
  // organization client_grants
@@ -262,6 +309,9 @@ class OrganizationsHandler extends default_1.default {
262
309
  ?.map((clientGrant) => ({
263
310
  grant_id: this.getClientGrantIDByClientName(clientGrant.client_id),
264
311
  })) || [];
312
+ if (orgClientGrantsToAdd.length > 0 || orgClientGrantsToRemove.length > 0) {
313
+ changed = true;
314
+ }
265
315
  // Handle updates first
266
316
  await Promise.all(orgClientGrantsToAdd.map((orgClientGrant) => this.createOrganizationClientGrants(params.id, orgClientGrant.grant_id).catch(() => {
267
317
  throw new Error(`Problem adding organization clientGrant ${orgClientGrant.grant_id} for organizations ${params.id}`);
@@ -283,6 +333,9 @@ class OrganizationsHandler extends default_1.default {
283
333
  };
284
334
  })
285
335
  .filter(Boolean) || [];
336
+ if (orgDiscoveryDomainsToUpdate.length > 0 || orgDiscoveryDomainsToAdd.length > 0) {
337
+ changed = true;
338
+ }
286
339
  for (const { id, domain, ...updateParams } of orgDiscoveryDomainsToUpdate) {
287
340
  try {
288
341
  await this.updateOrganizationDiscoveryDomain(params.id, id, domain, updateParams);
@@ -306,6 +359,7 @@ class OrganizationsHandler extends default_1.default {
306
359
  if (orgDiscoveryDomainsToRemove.length > 0) {
307
360
  if (this.config('AUTH0_ALLOW_DELETE') === 'true' ||
308
361
  this.config('AUTH0_ALLOW_DELETE') === true) {
362
+ changed = true;
309
363
  for (const domain of orgDiscoveryDomainsToRemove) {
310
364
  try {
311
365
  await this.deleteOrganizationDiscoveryDomain(params.id, domain.domain, domain.id);
@@ -320,12 +374,66 @@ class OrganizationsHandler extends default_1.default {
320
374
  \n${orgDiscoveryDomainsToRemove.map((i) => this.objString(i)).join('\n')}`);
321
375
  }
322
376
  }
323
- return params;
377
+ // organization clients
378
+ const orgClientsToAdd = (organizationClients || [])
379
+ .filter((c) => !existingOrgClients.find((x) => x.client_id === c.client_id))
380
+ .map((oc) => ({
381
+ client_id: this.getClientIdByClientName(oc.client_id),
382
+ use_for_member_access: oc.use_for_member_access,
383
+ }))
384
+ .filter((oc) => !!oc.client_id);
385
+ const orgClientsToRemove = (existingOrgClients || [])
386
+ .filter((c) => !(organizationClients || []).find((x) => x.client_id === c.client_id))
387
+ .map((oc) => this.getClientIdByClientName(oc.client_id))
388
+ .filter(Boolean);
389
+ const orgClientsToUpdate = (organizationClients || [])
390
+ .filter((c) => {
391
+ const existing = existingOrgClients.find((x) => x.client_id === c.client_id);
392
+ return existing && existing.use_for_member_access !== c.use_for_member_access;
393
+ })
394
+ .map((oc) => ({
395
+ client_id: this.getClientIdByClientName(oc.client_id),
396
+ use_for_member_access: oc.use_for_member_access,
397
+ }))
398
+ .filter((oc) => !!oc.client_id);
399
+ if (orgClientsToAdd.length > 0) {
400
+ changed = true;
401
+ await this.createOrganizationClients(params.id, orgClientsToAdd).catch((err) => {
402
+ throw new Error(`Problem adding org clients for organization ${params.id}\n${err}`);
403
+ });
404
+ }
405
+ if (orgClientsToRemove.length > 0) {
406
+ if (this.config('AUTH0_ALLOW_DELETE') === 'true' ||
407
+ this.config('AUTH0_ALLOW_DELETE') === true) {
408
+ changed = true;
409
+ await this.deleteOrganizationClients(params.id, orgClientsToRemove).catch((err) => {
410
+ throw new Error(`Problem removing org clients for organization ${params.id}\n${err}`);
411
+ });
412
+ }
413
+ else {
414
+ logger_1.default.warn(`Detected the following organization client associations should be removed. Doing so may be destructive.\nYou can enable deletes by setting 'AUTH0_ALLOW_DELETE' to true in the config\n${orgClientsToRemove.join('\n')}`);
415
+ }
416
+ }
417
+ if (orgClientsToUpdate.length > 0) {
418
+ changed = true;
419
+ }
420
+ await Promise.all(orgClientsToUpdate.map((oc) => this.client.organizations.clients
421
+ .update(params.id, oc.client_id, {
422
+ use_for_member_access: oc.use_for_member_access,
423
+ })
424
+ .catch((err) => {
425
+ throw new Error(`Problem updating org client ${oc.client_id} for organization ${params.id}\n${err}`);
426
+ })));
427
+ return changed ? params : null;
324
428
  }
325
429
  getClientGrantIDByClientName(clientsName) {
326
430
  const found = this.formattedClientGrants.find((c) => c.client_id === clientsName);
327
431
  return found?.grant_id || '';
328
432
  }
433
+ getClientIdByClientName(clientName) {
434
+ const found = this.allClients?.find((c) => c.name === clientName);
435
+ return found?.client_id || '';
436
+ }
329
437
  async getFormattedClientGrants() {
330
438
  const [clients, clientGrants] = await Promise.all([
331
439
  (0, client_1.paginate)(this.client.clients.list, {
@@ -335,6 +443,8 @@ class OrganizationsHandler extends default_1.default {
335
443
  paginate: true,
336
444
  }),
337
445
  ]);
446
+ // Store clients for org-client name→ID resolution
447
+ this.allClients = clients;
338
448
  // Convert clients by name to the id and store it in the formattedClientGrants
339
449
  const formattedClientGrantsMapping = clientGrants?.map((clientGrant) => {
340
450
  const { id, client_id: clientName } = clientGrant;
@@ -352,8 +462,10 @@ class OrganizationsHandler extends default_1.default {
352
462
  data: updates || [],
353
463
  generator: (item) => this.updateOrganization(item, orgs)
354
464
  .then((data) => {
355
- this.didUpdate(data);
356
- this.updated += 1;
465
+ if (data) {
466
+ this.didUpdate(data);
467
+ this.updated += 1;
468
+ }
357
469
  })
358
470
  .catch((err) => {
359
471
  throw new Error(`Problem updating ${this.type} ${this.objString(item)}\n${err}`);
@@ -390,6 +502,14 @@ class OrganizationsHandler extends default_1.default {
390
502
  if (organizationDiscoveryDomains) {
391
503
  org.discovery_domains = organizationDiscoveryDomains;
392
504
  }
505
+ // Get org-client associations
506
+ const orgClients = await this.getAllOrganizationClients(org.id);
507
+ if (orgClients) {
508
+ org.clients = orgClients.map((oc) => ({
509
+ client_id: (0, utils_1.convertClientIdToName)(oc.client_id, clients),
510
+ use_for_member_access: oc.use_for_member_access,
511
+ }));
512
+ }
393
513
  }
394
514
  this.existing = organizations;
395
515
  return this.existing;
@@ -504,8 +624,8 @@ class OrganizationsHandler extends default_1.default {
504
624
  if (err.statusCode === 404 || err.statusCode === 501) {
505
625
  return null;
506
626
  }
507
- if (err.statusCode === 403 || err.errorCode === 'feature_not_enabled') {
508
- logger_1.default.debug('Organization Discovery domains are not enabled for this tenant. Please verify `scope` or contact Auth0 support to enable this feature.');
627
+ if (isOrgSubresourceUnavailable(err)) {
628
+ logger_1.default.debug(`Skipping organization discovery domains (${err?.body?.errorCode ?? err.statusCode}).`);
509
629
  return null;
510
630
  }
511
631
  throw err;
@@ -535,6 +655,44 @@ class OrganizationsHandler extends default_1.default {
535
655
  logger_1.default.debug(`Deleting discovery domain ${discoveryDomain} for organization ${organizationId}`);
536
656
  await this.client.organizations.discoveryDomains.delete(organizationId, discoveryDomainId);
537
657
  }
658
+ async getAllOrganizationClients(organizationId) {
659
+ const allOrgClients = [];
660
+ try {
661
+ let orgClients = await this.client.organizations.clients.list(organizationId);
662
+ allOrgClients.push(...orgClients.data);
663
+ while (orgClients.hasNextPage()) {
664
+ orgClients = await orgClients.getNextPage();
665
+ allOrgClients.push(...orgClients.data);
666
+ }
667
+ return allOrgClients;
668
+ }
669
+ catch (err) {
670
+ if (err.statusCode === 404 || err.statusCode === 501) {
671
+ return null;
672
+ }
673
+ if (isOrgSubresourceUnavailable(err)) {
674
+ logger_1.default.debug(`Skipping org-client associations (${err?.body?.errorCode ?? err.statusCode}).`);
675
+ return null;
676
+ }
677
+ throw err;
678
+ }
679
+ }
680
+ async createOrganizationClients(organizationId, clients) {
681
+ const BATCH_SIZE = 10;
682
+ for (let i = 0; i < clients.length; i += BATCH_SIZE) {
683
+ const batch = clients.slice(i, i + BATCH_SIZE);
684
+ logger_1.default.debug(`Creating ${batch.length} org client(s) for organization ${organizationId}`);
685
+ await this.client.organizations.clients.create(organizationId, { clients: batch });
686
+ }
687
+ }
688
+ async deleteOrganizationClients(organizationId, clientIds) {
689
+ const BATCH_SIZE = 10;
690
+ for (let i = 0; i < clientIds.length; i += BATCH_SIZE) {
691
+ const batch = clientIds.slice(i, i + BATCH_SIZE);
692
+ logger_1.default.debug(`Deleting ${batch.length} org client(s) for organization ${organizationId}`);
693
+ await this.client.organizations.clients.delete(organizationId, { clients: batch });
694
+ }
695
+ }
538
696
  }
539
697
  exports.default = OrganizationsHandler;
540
698
  __decorate([
@@ -202,12 +202,20 @@ class PhoneProviderHandler extends default_1.default {
202
202
  }
203
203
  const currentProviders = await this.getPhoneProviders();
204
204
  const providerReqPayload = (() => {
205
+ // Work on a shallow copy so we don't mutate the caller's asset (phoneProviders[0]).
206
+ // The SDK response type doesn't declare `credentials`, so widen it here.
207
+ const payload = { ...phoneProviders[0] };
205
208
  // Removing id from update and create payloads, otherwise API will error
206
209
  // id may be required to handle if `--export_ids=true`
207
- const payload = phoneProviders[0];
208
- if (payload && 'id' in payload) {
210
+ if ('id' in payload) {
209
211
  delete payload.id;
210
212
  }
213
+ // The `custom` provider requires a `credentials` object in the payload even
214
+ // when it is empty. Exported configs omit credentials, so inject an empty
215
+ // object here to avoid a "Missing required property: credentials" 400 error.
216
+ if (payload.name === 'custom' && payload.credentials == null) {
217
+ payload.credentials = {};
218
+ }
211
219
  return payload;
212
220
  })();
213
221
  if (currentProviders === null || currentProviders.length === 0) {
@@ -54,7 +54,10 @@ export default class PhoneTemplatesHandler extends DefaultHandler {
54
54
  objString(template: any): string;
55
55
  getType(): Promise<PhoneTemplate[]>;
56
56
  processChanges(assets: Assets): Promise<void>;
57
- createPhoneTemplate(template: any): Promise<Asset>;
57
+ createPhoneTemplate(template: any): Promise<{
58
+ asset: Asset;
59
+ action: 'create' | 'update';
60
+ }>;
58
61
  createPhoneTemplates(creates: CalculatedChanges['create']): Promise<void>;
59
62
  updatePhoneTemplate(template: any): Promise<Asset>;
60
63
  updatePhoneTemplates(updates: CalculatedChanges['update']): Promise<void>;
@@ -44,6 +44,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.schema = void 0;
46
46
  const default_1 = __importStar(require("./default"));
47
+ const utils_1 = require("../../utils");
47
48
  const logger_1 = __importDefault(require("../../../logger"));
48
49
  exports.schema = {
49
50
  type: 'array',
@@ -121,25 +122,77 @@ class PhoneTemplatesHandler extends default_1.default {
121
122
  if (del.length > 0) {
122
123
  await this.deletePhoneTemplates(del);
123
124
  }
124
- if (create.length > 0) {
125
- await this.createPhoneTemplates(create);
125
+ // On newly created tenants the phone templates returned by the API have no
126
+ // ID until they are explicitly created. calcChanges matches assets by `type`
127
+ // and routes them all to `update`, but PATCH requires an ID. Split out the
128
+ // templates whose existing counterpart has no ID yet and create them instead.
129
+ const missingId = [];
130
+ const hasId = [];
131
+ update.forEach((template) => {
132
+ const existing = this.existing?.find((t) => t.type === template.type);
133
+ if (existing?.id) {
134
+ hasId.push(template);
135
+ }
136
+ else {
137
+ missingId.push(template);
138
+ }
139
+ });
140
+ const toCreate = [...create, ...missingId];
141
+ if (toCreate.length > 0) {
142
+ await this.createPhoneTemplates(toCreate);
126
143
  }
127
- if (update.length > 0) {
128
- await this.updatePhoneTemplates(update);
144
+ if (hasId.length > 0) {
145
+ await this.updatePhoneTemplates(hasId);
129
146
  }
130
147
  }
131
148
  async createPhoneTemplate(template) {
132
- const created = await this.client.branding.phone.templates.create(template);
133
- return created;
149
+ // The create endpoint only accepts type/disabled/content; strip read-only
150
+ // fields (channel, customizable, tenant) that the API would reject.
151
+ const createPayload = (0, utils_1.stripFields)(template, this.stripCreateFields);
152
+ // The API rejects an empty `content.from`, which is what fresh tenants
153
+ // export. Drop it when blank (cloning `content`, which stripFields only
154
+ // shallow-copied, so the caller's original asset isn't mutated).
155
+ if (createPayload.content && !createPayload.content.from) {
156
+ createPayload.content = { ...createPayload.content };
157
+ delete createPayload.content.from;
158
+ }
159
+ try {
160
+ const created = await this.client.branding.phone.templates.create(createPayload);
161
+ return { asset: created, action: 'create' };
162
+ }
163
+ catch (err) {
164
+ // A 409 means the template already exists on the tenant (it was created
165
+ // between our list call and this create, or the list endpoint didn't
166
+ // surface its ID). Re-fetch to pick up the ID and fall back to an update.
167
+ if (err.statusCode === 409) {
168
+ logger_1.default.debug(`Phone template type '${template.type}' already exists, falling back to update`);
169
+ // Reassigns the shared `this.existing`; safe under concurrency since
170
+ // every re-fetch returns the same consistent list. If it still lacks an
171
+ // ID, updatePhoneTemplate warns and skips.
172
+ const response = await this.client.branding.phone.templates.list();
173
+ this.existing = response.templates ?? [];
174
+ const updated = await this.updatePhoneTemplate(template);
175
+ return { asset: updated, action: 'update' };
176
+ }
177
+ throw err;
178
+ }
134
179
  }
135
180
  async createPhoneTemplates(creates) {
136
181
  await this.client.pool
137
182
  .addEachTask({
138
183
  data: creates || [],
139
184
  generator: (item) => this.createPhoneTemplate(item)
140
- .then((data) => {
141
- this.didCreate(data);
142
- this.created += 1;
185
+ .then(({ asset, action }) => {
186
+ // A 409 fallback actually performed an update; count it as such so
187
+ // the import summary doesn't report a phantom "create".
188
+ if (action === 'update') {
189
+ this.didUpdate(asset);
190
+ this.updated += 1;
191
+ }
192
+ else {
193
+ this.didCreate(asset);
194
+ this.created += 1;
195
+ }
143
196
  })
144
197
  .catch((err) => {
145
198
  throw new Error(`Problem creating ${this.type} ${this.objString(item)}\n${err}`);
@@ -160,7 +213,6 @@ class PhoneTemplatesHandler extends default_1.default {
160
213
  logger_1.default.debug(`Stripping ${this.type} create-only fields ${JSON.stringify(stripUpdateFields)}`);
161
214
  const updatePayload = {
162
215
  content: {
163
- from: template.content?.from,
164
216
  body: {
165
217
  text: template.content?.body?.text,
166
218
  voice: template.content?.body?.voice,
@@ -168,6 +220,11 @@ class PhoneTemplatesHandler extends default_1.default {
168
220
  },
169
221
  disabled: template.disabled,
170
222
  };
223
+ // `content.from` is optional but the API rejects an empty string, which is
224
+ // what fresh tenants export. Only include it when it has a value.
225
+ if (template.content?.from) {
226
+ updatePayload.content.from = template.content.from;
227
+ }
171
228
  const updated = await this.client.branding.phone.templates.update(existing.id, updatePayload);
172
229
  return updated;
173
230
  }
@@ -496,7 +496,7 @@ class PromptsHandler extends default_1.default {
496
496
  updatePayload = {
497
497
  ...updatePrams,
498
498
  rendering_mode: auth0_1.Management.AculRenderingModeEnum.Advanced,
499
- default_head_tags_disabled: screenRenderer.default_head_tags_disabled || undefined,
499
+ default_head_tags_disabled: screenRenderer.default_head_tags_disabled ?? undefined,
500
500
  head_tags: screenRenderer.head_tags,
501
501
  };
502
502
  }
@@ -0,0 +1,81 @@
1
+ import { Management } from 'auth0';
2
+ import DefaultAPIHandler from './default';
3
+ import { Asset, Assets, CalculatedChanges } from '../../../types';
4
+ export type RateLimitPolicyConfiguration = Management.RateLimitPolicyConfiguration;
5
+ export type RateLimitPolicy = Management.RateLimitPolicy;
6
+ export declare const schema: {
7
+ type: string;
8
+ items: {
9
+ type: string;
10
+ properties: {
11
+ resource: {
12
+ type: string;
13
+ enum: "oauth_authentication_api"[];
14
+ };
15
+ consumer: {
16
+ type: string;
17
+ enum: "client"[];
18
+ };
19
+ consumer_selector: {
20
+ type: string;
21
+ };
22
+ configuration: {
23
+ type: string;
24
+ oneOf: ({
25
+ required: string[];
26
+ properties: {
27
+ action: {
28
+ type: string;
29
+ enum: string[];
30
+ };
31
+ limit?: undefined;
32
+ redirect_uri?: undefined;
33
+ };
34
+ additionalProperties: boolean;
35
+ } | {
36
+ required: string[];
37
+ properties: {
38
+ action: {
39
+ type: string;
40
+ enum: string[];
41
+ };
42
+ limit: {
43
+ type: string;
44
+ };
45
+ redirect_uri?: undefined;
46
+ };
47
+ additionalProperties: boolean;
48
+ } | {
49
+ required: string[];
50
+ properties: {
51
+ action: {
52
+ type: string;
53
+ enum: string[];
54
+ };
55
+ limit: {
56
+ type: string;
57
+ };
58
+ redirect_uri: {
59
+ type: string;
60
+ };
61
+ };
62
+ additionalProperties: boolean;
63
+ })[];
64
+ };
65
+ };
66
+ required: string[];
67
+ };
68
+ };
69
+ export default class RateLimitPoliciesHandler extends DefaultAPIHandler {
70
+ existing: RateLimitPolicy[] | null;
71
+ constructor(config: DefaultAPIHandler);
72
+ objString(policy: RateLimitPolicy): string;
73
+ getType(): Promise<Asset | null>;
74
+ processChanges(assets: Assets): Promise<void>;
75
+ createRateLimitPolicy(policy: RateLimitPolicy): Promise<RateLimitPolicy>;
76
+ createRateLimitPolicies(creates: CalculatedChanges['create']): Promise<void>;
77
+ updateRateLimitPolicy(policy: RateLimitPolicy): Promise<void>;
78
+ updateRateLimitPolicies(updates: CalculatedChanges['update']): Promise<void>;
79
+ deleteRateLimitPolicy(policy: RateLimitPolicy): Promise<void>;
80
+ deleteRateLimitPolicies(data: Asset[]): Promise<void>;
81
+ }