auth0-deploy-cli 8.32.0 → 8.34.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.
@@ -320,37 +320,40 @@ class ActionHandler extends default_1.default {
320
320
  if (!action.modules || action.modules.length === 0) {
321
321
  return action;
322
322
  }
323
- const updatedModules = await this.client.pool
324
- .addEachTask({
325
- data: action.modules,
326
- generator: async (module) => {
327
- const foundModule = modules.find((m) => m.name === module.module_name);
328
- if (foundModule && foundModule.id) {
329
- // paginate to get all versions of the module
330
- const allModuleVersions = [];
331
- let moduleVersions = await this.client.actions.modules.versions.list(foundModule.id);
332
- // Process first page
323
+ // Process modules sequentially to avoid a pool deadlock.
324
+ // This function is called as a task inside the shared pool (via addEachTask in calcChanges).
325
+ // If we submitted further addEachTask calls on the same pool here, all concurrency slots
326
+ // could be held by outer action tasks waiting for inner module tasks that can never start
327
+ // (because no slots are free), causing a hang with 3+ actions that have modules.
328
+ const updatedModules = [];
329
+ for (const module of action.modules) {
330
+ const foundModule = modules.find((m) => m.name === module.module_name);
331
+ if (foundModule && foundModule.id) {
332
+ // paginate to get all versions of the module
333
+ const allModuleVersions = [];
334
+ let moduleVersions = await this.client.actions.modules.versions.list(foundModule.id);
335
+ // Process first page
336
+ allModuleVersions.push(...moduleVersions.data);
337
+ // Fetch remaining pages
338
+ while (moduleVersions.hasNextPage()) {
339
+ moduleVersions = await moduleVersions.getNextPage();
333
340
  allModuleVersions.push(...moduleVersions.data);
334
- // Fetch remaining pages
335
- while (moduleVersions.hasNextPage()) {
336
- moduleVersions = await moduleVersions.getNextPage();
337
- allModuleVersions.push(...moduleVersions.data);
338
- }
339
- const moduleVersionId = allModuleVersions?.find((v) => v.version_number === module.module_version_number)?.id;
340
- if (!moduleVersionId) {
341
- throw new Error(`Could not find action module version id for module '${module.module_name}' version '${module.module_version_number}'`);
342
- }
343
- return {
344
- module_name: module.module_name,
345
- module_id: foundModule.id,
346
- module_version_number: module.module_version_number,
347
- module_version_id: moduleVersionId,
348
- };
349
341
  }
350
- return module;
351
- },
352
- })
353
- .promise();
342
+ const moduleVersionId = allModuleVersions?.find((v) => v.version_number === module.module_version_number)?.id;
343
+ if (!moduleVersionId) {
344
+ throw new Error(`Could not find action module version id for module '${module.module_name}' version '${module.module_version_number}'`);
345
+ }
346
+ updatedModules.push({
347
+ module_name: module.module_name,
348
+ module_id: foundModule.id,
349
+ module_version_number: module.module_version_number,
350
+ module_version_id: moduleVersionId,
351
+ });
352
+ }
353
+ else {
354
+ updatedModules.push(module);
355
+ }
356
+ }
354
357
  return {
355
358
  ...action,
356
359
  modules: updatedModules,
@@ -37,6 +37,11 @@ export declare const schema: {
37
37
  type: string;
38
38
  description: string;
39
39
  };
40
+ default_for: {
41
+ type: string;
42
+ enum: string[];
43
+ description: string;
44
+ };
40
45
  };
41
46
  required: string[];
42
47
  };
@@ -73,8 +73,13 @@ exports.schema = {
73
73
  type: 'boolean',
74
74
  description: 'When enabled, all scopes configured on the resource server are allowed for by this client grant.',
75
75
  },
76
+ default_for: {
77
+ type: 'string',
78
+ enum: ['third_party_clients'],
79
+ description: 'Indicates that a client grant is the default client grant for third party clients.',
80
+ },
76
81
  },
77
- required: ['client_id', 'audience'],
82
+ required: ['audience'],
78
83
  },
79
84
  };
80
85
  class ClientGrantsHandler extends default_1.default {
@@ -83,11 +88,17 @@ class ClientGrantsHandler extends default_1.default {
83
88
  ...config,
84
89
  type: 'clientGrants',
85
90
  id: 'id',
86
- // @ts-ignore because not sure why two-dimensional array passed in
87
- // Try ['client_id', 'audience', 'subject_type'] first; falls through to
88
- // ['client_id', 'audience'] when subject_type is null (falsy).
89
- identifiers: ['id', ['client_id', 'audience', 'subject_type'], ['client_id', 'audience']],
90
- stripUpdateFields: ['audience', 'client_id', 'subject_type', 'is_system'],
91
+ // Nested arrays are not reflected in the type but are supported at runtime.
92
+ // Try ['client_id', 'audience', 'subject_type'] first, then ['client_id', 'audience']
93
+ // for regular grants, then ['default_for', 'audience'] for default third-party grants
94
+ // which have no client_id.
95
+ identifiers: [
96
+ 'id',
97
+ ['client_id', 'audience', 'subject_type'],
98
+ ['client_id', 'audience'],
99
+ ['default_for', 'audience'],
100
+ ],
101
+ stripUpdateFields: ['audience', 'client_id', 'subject_type', 'is_system', 'default_for'],
91
102
  ignoreDryRunFields: ['_clientName'],
92
103
  });
93
104
  }
@@ -101,6 +112,15 @@ class ClientGrantsHandler extends default_1.default {
101
112
  return;
102
113
  // Validate each client grant
103
114
  clientGrants.forEach((grant) => {
115
+ // client_id and default_for are mutually exclusive; exactly one must be present
116
+ const hasClientId = !!grant.client_id;
117
+ const hasDefaultFor = !!grant.default_for;
118
+ if (hasClientId && hasDefaultFor) {
119
+ throw new Error(`Client grant for audience "${grant.audience}": Cannot specify both "client_id" and "default_for". They are mutually exclusive.`);
120
+ }
121
+ if (!hasClientId && !hasDefaultFor) {
122
+ throw new Error(`Client grant for audience "${grant.audience}": One of "client_id" or "default_for" is required.`);
123
+ }
104
124
  // When allow_all_scopes is true, scope should not be present
105
125
  if (grant.allow_all_scopes === true && grant.scope && grant.scope.length > 0) {
106
126
  throw new Error(`Client grant for client_id "${grant.client_id}" and audience "${grant.audience}": Cannot specify "scope" when "allow_all_scopes" is set to true. Remove the "scope" property or set "allow_all_scopes" to false.`);
@@ -127,7 +147,8 @@ class ClientGrantsHandler extends default_1.default {
127
147
  is_first_party: true,
128
148
  });
129
149
  const firstPartyClientIds = new Set(clients.map((c) => c.client_id));
130
- this.existing = this.existing.filter((grant) => firstPartyClientIds.has(grant.client_id));
150
+ // default_for grants have no client_id and are not tied to a specific client; always keep them
151
+ this.existing = this.existing.filter((grant) => !grant.client_id || firstPartyClientIds.has(grant.client_id));
131
152
  }
132
153
  return this.existing;
133
154
  }
@@ -187,9 +208,9 @@ class ClientGrantsHandler extends default_1.default {
187
208
  let filtered = list;
188
209
  // Filter out the current client (Auth0 Management API client)
189
210
  filtered = filtered.filter((item) => item.client_id !== currentClient);
190
- // Filter out excluded clients
211
+ // Filter out excluded clients; default_for grants have no client_id and are never excluded
191
212
  if (excludedClients.length) {
192
- filtered = filtered.filter((item) => item.client_id &&
213
+ filtered = filtered.filter((item) => !item.client_id ||
193
214
  ![...excludedClientsByNames, ...excludedClients].includes(item.client_id));
194
215
  }
195
216
  // Filter out system grants
@@ -308,6 +308,16 @@ export declare const schema: {
308
308
  };
309
309
  };
310
310
  };
311
+ third_party_security_mode: {
312
+ type: string;
313
+ enum: string[];
314
+ description: string;
315
+ };
316
+ redirection_policy: {
317
+ type: string;
318
+ enum: string[];
319
+ description: string;
320
+ };
311
321
  oidc_logout: {
312
322
  type: string[];
313
323
  description: string;
@@ -312,6 +312,16 @@ exports.schema = {
312
312
  },
313
313
  },
314
314
  },
315
+ third_party_security_mode: {
316
+ type: 'string',
317
+ enum: ['strict', 'permissive'],
318
+ description: 'Indicates the security mode for a Third Party Client, to apply new security semantics and improvements.',
319
+ },
320
+ redirection_policy: {
321
+ type: 'string',
322
+ enum: ['allow_always', 'open_redirect_protection'],
323
+ description: 'Determines how OAuth2/OIDC errors are handled when a non-recoverable error happens.',
324
+ },
315
325
  oidc_logout: {
316
326
  type: ['object', 'null'],
317
327
  description: 'Configuration for OIDC backchannel logout',
@@ -436,6 +446,8 @@ class ClientHandler extends default_1.default {
436
446
  'external_metadata_type',
437
447
  'external_metadata_created_by',
438
448
  'jwks_uri',
449
+ // third_party_security_mode is immutable after creation; PATCH returns 400
450
+ 'third_party_security_mode',
439
451
  ],
440
452
  functions: {
441
453
  create: (client) => this.createClient(client),
@@ -16,10 +16,29 @@ export declare const schema: {
16
16
  };
17
17
  options: {
18
18
  type: string;
19
+ additionalProperties: boolean;
19
20
  properties: {
21
+ api_enable_groups: {
22
+ type: string;
23
+ };
20
24
  dpop_signing_alg: {
21
25
  type: string;
22
26
  };
27
+ token_endpoint_auth_signing_alg: {
28
+ type: string;
29
+ enum: ("RS256" | "RS512" | "PS256" | "ES256" | "ES384" | "PS384" | "RS384")[];
30
+ };
31
+ id_token_signed_response_algs: {
32
+ type: string;
33
+ items: {
34
+ type: string;
35
+ enum: ("RS256" | "RS512" | "PS256" | "ES256" | "ES384" | "PS384" | "RS384")[];
36
+ };
37
+ };
38
+ token_endpoint_jwtca_aud_format: {
39
+ type: string;
40
+ enum: ("issuer" | "token_endpoint")[];
41
+ };
23
42
  };
24
43
  };
25
44
  enabled_clients: {
@@ -106,6 +125,22 @@ export declare const schema: {
106
125
  type: string;
107
126
  description: string;
108
127
  };
128
+ synchronize_groups: {
129
+ type: string;
130
+ enum: ("all" | "off" | "selected")[];
131
+ };
132
+ synchronized_groups: {
133
+ type: string;
134
+ items: {
135
+ type: string;
136
+ properties: {
137
+ id: {
138
+ type: string;
139
+ };
140
+ };
141
+ required: string[];
142
+ };
143
+ };
109
144
  };
110
145
  };
111
146
  };
@@ -115,7 +150,11 @@ export declare const schema: {
115
150
  type DirectoryProvisioningConfig = Management.DirectoryProvisioning;
116
151
  export type Connection = Management.ConnectionForList & {
117
152
  enabled_clients?: string[];
118
- directory_provisioning_configuration?: Pick<DirectoryProvisioningConfig, 'mapping' | 'synchronize_automatically'>;
153
+ directory_provisioning_configuration?: Pick<DirectoryProvisioningConfig, 'mapping' | 'synchronize_automatically' | 'synchronize_groups'> & {
154
+ synchronized_groups?: Array<{
155
+ id: string;
156
+ }>;
157
+ };
119
158
  };
120
159
  export declare const addExcludedConnectionPropertiesToChanges: ({ proposedChanges, existingConnections, config, }: {
121
160
  proposedChanges: CalculatedChanges;
@@ -169,6 +208,7 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
169
208
  } | {
170
209
  options?: undefined;
171
210
  };
211
+ validate(assets: Assets): Promise<void>;
172
212
  /**
173
213
  * Retrieves all directory provisioning configurations for all connections.
174
214
  * @returns A promise that resolves to the configurations object, or null if not configured/supported
@@ -186,6 +226,16 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
186
226
  * Deletes directory provisioning configuration for a connection.
187
227
  */
188
228
  private deleteConnectionDirectoryProvisioning;
229
+ /**
230
+ * Retrieves all synchronized groups for a connection using checkpoint pagination.
231
+ */
232
+ getConnectionSynchronizedGroups(connectionId: string): Promise<Array<{
233
+ id: string;
234
+ }> | null>;
235
+ /**
236
+ * Replaces the synchronized groups for a connection (PUT replace-all semantics).
237
+ */
238
+ private updateConnectionSynchronizedGroups;
189
239
  /**
190
240
  * This function processes directory provisioning for create, update, and conflict operations.
191
241
  * Directory provisioning is only supported for google-apps strategy connections.
@@ -51,14 +51,40 @@ const utils_1 = require("../../utils");
51
51
  const client_1 = require("../client");
52
52
  const scimHandler_1 = __importDefault(require("./scimHandler"));
53
53
  const logger_1 = __importDefault(require("../../../logger"));
54
+ const validationError_1 = __importDefault(require("../../validationError"));
54
55
  const connectionOptionsSchema = {
55
56
  type: 'object',
57
+ additionalProperties: true,
56
58
  properties: {
59
+ api_enable_groups: {
60
+ type: 'boolean',
61
+ },
57
62
  dpop_signing_alg: {
58
63
  type: 'string',
59
64
  },
65
+ token_endpoint_auth_signing_alg: {
66
+ type: 'string',
67
+ enum: Object.values(auth0_1.Management.ConnectionTokenEndpointAuthSigningAlgEnum),
68
+ },
69
+ id_token_signed_response_algs: {
70
+ type: 'array',
71
+ items: {
72
+ type: 'string',
73
+ enum: Object.values(auth0_1.Management.ConnectionIdTokenSignedResponseAlgEnum),
74
+ },
75
+ },
76
+ token_endpoint_jwtca_aud_format: {
77
+ type: 'string',
78
+ enum: Object.values(auth0_1.Management.ConnectionTokenEndpointJwtcaAudFormatEnumOidc),
79
+ },
60
80
  },
61
81
  };
82
+ const oidcOktaStrategies = new Set(['oidc', 'okta']);
83
+ const oidcOktaOnlyConnectionOptionFields = [
84
+ 'token_endpoint_auth_signing_alg',
85
+ 'id_token_signed_response_algs',
86
+ 'token_endpoint_jwtca_aud_format',
87
+ ];
62
88
  exports.schema = {
63
89
  type: 'array',
64
90
  items: {
@@ -118,6 +144,20 @@ exports.schema = {
118
144
  type: 'boolean',
119
145
  description: 'The field whether periodic automatic synchronization is enabled',
120
146
  },
147
+ synchronize_groups: {
148
+ type: 'string',
149
+ enum: Object.values(auth0_1.Management.SynchronizeGroupsEnum),
150
+ },
151
+ synchronized_groups: {
152
+ type: 'array',
153
+ items: {
154
+ type: 'object',
155
+ properties: {
156
+ id: { type: 'string' },
157
+ },
158
+ required: ['id'],
159
+ },
160
+ },
121
161
  },
122
162
  },
123
163
  },
@@ -333,6 +373,27 @@ class ConnectionsHandler extends default_1.default {
333
373
  return {};
334
374
  }
335
375
  }
376
+ async validate(assets) {
377
+ const { connections } = assets;
378
+ if (!connections) {
379
+ await super.validate(assets);
380
+ return;
381
+ }
382
+ connections.forEach((connection) => {
383
+ if (!connection?.options)
384
+ return;
385
+ const strategy = connection?.strategy ?? 'unknown';
386
+ if (oidcOktaStrategies.has(strategy))
387
+ return;
388
+ const unsupportedFields = oidcOktaOnlyConnectionOptionFields.filter((field) => field in connection.options);
389
+ if (unsupportedFields.length > 0) {
390
+ throw new validationError_1.default(`Connection "${connection.name}": option(s) ${unsupportedFields
391
+ .map((field) => `"${field}"`)
392
+ .join(', ')} are only supported for strategies "oidc" and "okta". Found strategy "${strategy}".`);
393
+ }
394
+ });
395
+ await super.validate(assets);
396
+ }
336
397
  /**
337
398
  * Retrieves all directory provisioning configurations for all connections.
338
399
  * @returns A promise that resolves to the configurations object, or null if not configured/supported
@@ -367,6 +428,7 @@ class ConnectionsHandler extends default_1.default {
367
428
  const createPayload = {
368
429
  mapping: payload.mapping,
369
430
  synchronize_automatically: payload.synchronize_automatically,
431
+ ...(payload.synchronize_groups && { synchronize_groups: payload.synchronize_groups }),
370
432
  };
371
433
  await this.client.connections.directoryProvisioning.create(connectionId, createPayload);
372
434
  logger_1.default.debug(`Created directory provisioning for connection '${connectionId}'`);
@@ -381,6 +443,7 @@ class ConnectionsHandler extends default_1.default {
381
443
  const updatePayload = {
382
444
  mapping: payload.mapping,
383
445
  synchronize_automatically: payload.synchronize_automatically,
446
+ ...(payload.synchronize_groups && { synchronize_groups: payload.synchronize_groups }),
384
447
  };
385
448
  await this.client.connections.directoryProvisioning.update(connectionId, updatePayload);
386
449
  logger_1.default.debug(`Updated directory provisioning for connection '${connectionId}'`);
@@ -395,6 +458,40 @@ class ConnectionsHandler extends default_1.default {
395
458
  await this.client.connections.directoryProvisioning.delete(connectionId);
396
459
  logger_1.default.debug(`Deleted directory provisioning for connection '${connectionId}'`);
397
460
  }
461
+ /**
462
+ * Retrieves all synchronized groups for a connection using checkpoint pagination.
463
+ */
464
+ async getConnectionSynchronizedGroups(connectionId) {
465
+ try {
466
+ const groups = [];
467
+ let page = await this.client.connections.directoryProvisioning.listSynchronizedGroups(connectionId, {});
468
+ for (const g of page.data ?? [])
469
+ groups.push({ id: g.id });
470
+ while (page.hasNextPage?.()) {
471
+ page = await page.getNextPage();
472
+ for (const g of page.data ?? [])
473
+ groups.push({ id: g.id });
474
+ }
475
+ return groups;
476
+ }
477
+ catch (error) {
478
+ const errLog = `Unable to fetch synchronized groups for connection '${connectionId}'. `;
479
+ const statusCode = error?.statusCode;
480
+ if (statusCode != null && [403, 404, 501].includes(statusCode)) {
481
+ const bodyMessage = error instanceof auth0_1.ManagementError ? error.body?.message : error.message;
482
+ logger_1.default.warn(errLog + bodyMessage);
483
+ return null;
484
+ }
485
+ throw error;
486
+ }
487
+ }
488
+ /**
489
+ * Replaces the synchronized groups for a connection (PUT replace-all semantics).
490
+ */
491
+ async updateConnectionSynchronizedGroups(connectionId, groups) {
492
+ await this.client.connections.directoryProvisioning.set(connectionId, { groups });
493
+ logger_1.default.debug(`Updated synchronized groups for connection '${connectionId}'`);
494
+ }
398
495
  /**
399
496
  * This function processes directory provisioning for create, update, and conflict operations.
400
497
  * Directory provisioning is only supported for google-apps strategy connections.
@@ -469,6 +566,20 @@ class ConnectionsHandler extends default_1.default {
469
566
  .map((i) => this.objString(i))
470
567
  .join('\n')}`);
471
568
  }
569
+ // Process synchronized groups for connections where synchronize_groups === 'selected'
570
+ const connectionsToSyncGroups = [
571
+ ...directoryConnectionsToCreate,
572
+ ...directoryConnectionsToUpdate,
573
+ ].filter((conn) => conn.directory_provisioning_configuration?.synchronize_groups === 'selected' &&
574
+ conn.directory_provisioning_configuration?.synchronized_groups !== undefined);
575
+ await this.client.pool
576
+ .addEachTask({
577
+ data: connectionsToSyncGroups,
578
+ generator: (conn) => this.updateConnectionSynchronizedGroups(conn.id, conn.directory_provisioning_configuration.synchronized_groups).catch((err) => {
579
+ throw new Error(`Failed to update synchronized groups for connection '${conn.id}':\n${err}`);
580
+ }),
581
+ })
582
+ .promise();
472
583
  }
473
584
  async getType() {
474
585
  if (this.existing)
@@ -508,7 +619,17 @@ class ConnectionsHandler extends default_1.default {
508
619
  connection.directory_provisioning_configuration = {
509
620
  mapping: dirProvConfig.mapping,
510
621
  synchronize_automatically: dirProvConfig.synchronize_automatically,
622
+ ...(dirProvConfig.synchronize_groups && {
623
+ synchronize_groups: dirProvConfig.synchronize_groups,
624
+ }),
511
625
  };
626
+ if (dirProvConfig.synchronize_groups === 'selected') {
627
+ const syncedGroups = await this.getConnectionSynchronizedGroups(con.id);
628
+ if (syncedGroups?.length) {
629
+ connection.directory_provisioning_configuration.synchronized_groups =
630
+ syncedGroups;
631
+ }
632
+ }
512
633
  }
513
634
  }
514
635
  return { index, connection };
@@ -117,6 +117,7 @@ class ResourceServersHandler extends default_1.default {
117
117
  'identifier',
118
118
  'id',
119
119
  'is_system',
120
+ 'authorization_policy',
120
121
  ];
121
122
  const sanitized = {};
122
123
  allowedKeys.forEach((key) => {
@@ -168,6 +169,7 @@ class ResourceServersHandler extends default_1.default {
168
169
  proof_of_possession: update.proof_of_possession,
169
170
  skip_consent_for_verifiable_first_party_clients: update.skip_consent_for_verifiable_first_party_clients,
170
171
  subject_type_authorization: update.subject_type_authorization,
172
+ authorization_policy: update.authorization_policy,
171
173
  };
172
174
  return this.client.resourceServers.update(id, updateFields);
173
175
  }
@@ -74,6 +74,11 @@ export declare const schema: {
74
74
  enum: string[];
75
75
  description: string;
76
76
  };
77
+ dynamic_client_registration_security_mode: {
78
+ type: string;
79
+ enum: string[];
80
+ description: string;
81
+ };
77
82
  };
78
83
  };
79
84
  export interface Tenant extends Management.GetTenantSettingsResponseContent {
@@ -101,6 +101,11 @@ exports.schema = {
101
101
  enum: ['audience', 'compatibility'],
102
102
  description: 'OAuth resource parameter compatibility mode for specifying the protected resource.',
103
103
  },
104
+ dynamic_client_registration_security_mode: {
105
+ type: 'string',
106
+ enum: ['strict', 'permissive'],
107
+ description: 'Indicates the security mode for new clients created through the Dynamic Client Registration endpoint.',
108
+ },
104
109
  },
105
110
  };
106
111
  const blockPageKeys = [
@@ -464,6 +464,7 @@ class ThemesHandler extends default_1.default {
464
464
  ...options,
465
465
  type: 'themes',
466
466
  id: 'themeId',
467
+ identifiers: ['themeId'],
467
468
  });
468
469
  }
469
470
  objString(theme) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth0-deploy-cli",
3
- "version": "8.32.0",
3
+ "version": "8.34.0",
4
4
  "description": "A command line tool for deploying updates to your Auth0 tenant",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -33,10 +33,10 @@
33
33
  "readme": "README.md",
34
34
  "homepage": "https://github.com/auth0/auth0-deploy-cli#readme",
35
35
  "dependencies": {
36
- "@clack/prompts": "1.2.0",
36
+ "@clack/prompts": "1.3.0",
37
37
  "ajv": "^6.12.6",
38
38
  "chalk": "5.6.2",
39
- "auth0": "^5.8.0",
39
+ "auth0": "^5.9.0",
40
40
  "dot-prop": "^5.3.0",
41
41
  "fs-extra": "^10.1.0",
42
42
  "js-yaml": "^4.1.1",