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.
- package/CHANGELOG.md +53 -1
- package/lib/context/directory/handlers/actionModules.js +11 -12
- package/lib/context/directory/handlers/actions.js +11 -13
- package/lib/context/directory/handlers/databases.js +4 -4
- package/lib/context/directory/handlers/hooks.js +12 -1
- package/lib/context/directory/handlers/rules.js +12 -1
- package/lib/context/directory/index.js +7 -8
- package/lib/context/yaml/index.js +7 -8
- package/lib/tools/auth0/handlers/attackProtection.js +16 -5
- package/lib/tools/auth0/handlers/clientAuthCredentials.d.ts +2 -1
- package/lib/tools/auth0/handlers/clientAuthCredentials.js +7 -0
- package/lib/tools/auth0/handlers/clientAuthCredentialsPre.d.ts +2 -1
- package/lib/tools/auth0/handlers/clientAuthCredentialsPre.js +5 -0
- package/lib/tools/auth0/handlers/clientGrants.d.ts +1 -1
- package/lib/tools/auth0/handlers/clients.d.ts +46 -0
- package/lib/tools/auth0/handlers/clients.js +91 -1
- package/lib/tools/auth0/handlers/connections.d.ts +32 -6
- package/lib/tools/auth0/handlers/connections.js +39 -4
- package/lib/tools/auth0/handlers/databases.js +11 -2
- package/lib/tools/auth0/handlers/default.js +6 -6
- package/lib/tools/auth0/handlers/guardianFactorTemplates.js +13 -4
- package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +11 -9
- package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +11 -9
- package/lib/tools/auth0/handlers/hooks.js +1 -1
- package/lib/tools/auth0/handlers/networkACLs.d.ts +18 -0
- package/lib/tools/auth0/handlers/networkACLs.js +9 -0
- package/lib/tools/auth0/handlers/organizations.d.ts +28 -0
- package/lib/tools/auth0/handlers/organizations.js +127 -2
- package/lib/tools/auth0/handlers/phoneTemplates.d.ts +4 -1
- package/lib/tools/auth0/handlers/phoneTemplates.js +67 -10
- package/lib/tools/auth0/handlers/roles.d.ts +4 -0
- package/lib/tools/auth0/handlers/roles.js +10 -0
- package/lib/tools/auth0/handlers/rules.js +19 -7
- package/lib/tools/auth0/handlers/tenant.d.ts +23 -0
- package/lib/tools/auth0/handlers/tenant.js +24 -0
- package/lib/tools/auth0/handlers/themes.d.ts +37 -1
- package/lib/tools/auth0/handlers/themes.js +64 -0
- package/lib/tools/auth0/index.js +9 -0
- package/lib/tools/utils.d.ts +1 -0
- package/lib/tools/utils.js +25 -2
- package/lib/types.d.ts +1 -0
- package/package.json +2 -2
|
@@ -129,6 +129,22 @@ exports.schema = {
|
|
|
129
129
|
required: ['active'],
|
|
130
130
|
additionalProperties: false,
|
|
131
131
|
},
|
|
132
|
+
cross_app_access_requesting_app: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
active: { type: 'boolean' },
|
|
136
|
+
},
|
|
137
|
+
required: ['active'],
|
|
138
|
+
additionalProperties: false,
|
|
139
|
+
},
|
|
140
|
+
cross_app_access_resource_app: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
status: { type: 'string', enum: ['enabled', 'disabled'] },
|
|
144
|
+
},
|
|
145
|
+
required: ['status'],
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
},
|
|
132
148
|
directory_provisioning_configuration: {
|
|
133
149
|
type: 'object',
|
|
134
150
|
properties: {
|
|
@@ -156,6 +172,9 @@ exports.schema = {
|
|
|
156
172
|
type: 'object',
|
|
157
173
|
properties: {
|
|
158
174
|
id: { type: 'string' },
|
|
175
|
+
name: { type: 'string' },
|
|
176
|
+
email: { type: 'string' },
|
|
177
|
+
direct_members_count: { type: 'number' },
|
|
159
178
|
},
|
|
160
179
|
required: ['id'],
|
|
161
180
|
},
|
|
@@ -467,12 +486,28 @@ class ConnectionsHandler extends default_1.default {
|
|
|
467
486
|
try {
|
|
468
487
|
const groups = [];
|
|
469
488
|
let page = await this.client.connections.directoryProvisioning.listSynchronizedGroups(connectionId, {});
|
|
470
|
-
for (const g of page.data ?? [])
|
|
471
|
-
groups.push({
|
|
489
|
+
for (const g of page.data ?? []) {
|
|
490
|
+
groups.push({
|
|
491
|
+
id: g.id,
|
|
492
|
+
...(g.name !== undefined && { name: g.name }),
|
|
493
|
+
...(g.email !== undefined && { email: g.email }),
|
|
494
|
+
...(g.direct_members_count !== undefined && {
|
|
495
|
+
direct_members_count: g.direct_members_count,
|
|
496
|
+
}),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
472
499
|
while (page.hasNextPage?.()) {
|
|
473
500
|
page = await page.getNextPage();
|
|
474
|
-
for (const g of page.data ?? [])
|
|
475
|
-
groups.push({
|
|
501
|
+
for (const g of page.data ?? []) {
|
|
502
|
+
groups.push({
|
|
503
|
+
id: g.id,
|
|
504
|
+
...(g.name !== undefined && { name: g.name }),
|
|
505
|
+
...(g.email !== undefined && { email: g.email }),
|
|
506
|
+
...(g.direct_members_count !== undefined && {
|
|
507
|
+
direct_members_count: g.direct_members_count,
|
|
508
|
+
}),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
476
511
|
}
|
|
477
512
|
return groups;
|
|
478
513
|
}
|
|
@@ -462,12 +462,21 @@ class DatabaseHandler extends default_1.default {
|
|
|
462
462
|
}
|
|
463
463
|
return connection;
|
|
464
464
|
});
|
|
465
|
+
// A connection can return both legacy password fields and `password_options` on read, but
|
|
466
|
+
// they're mutually exclusive on write. Strip the legacy fields to keep the export deployable.
|
|
467
|
+
const dbConnectionsNormalized = dbConnectionsWithActionNames.map((connection) => {
|
|
468
|
+
if (connection.options && 'password_options' in connection.options) {
|
|
469
|
+
const { passwordPolicy, password_complexity_options, password_history, password_no_personal_info, password_dictionary, ...options } = connection.options;
|
|
470
|
+
return { ...connection, options };
|
|
471
|
+
}
|
|
472
|
+
return connection;
|
|
473
|
+
});
|
|
465
474
|
// If options option is empty for all connection, log the missing options scope.
|
|
466
|
-
const isOptionExists =
|
|
475
|
+
const isOptionExists = dbConnectionsNormalized.every((c) => c.options && Object.keys(c.options).length > 0);
|
|
467
476
|
if (!isOptionExists) {
|
|
468
477
|
logger_1.default.warn(`Insufficient scope the read:connections_options scope is required to get ${this.type} options.`);
|
|
469
478
|
}
|
|
470
|
-
this.existing =
|
|
479
|
+
this.existing = dbConnectionsNormalized;
|
|
471
480
|
return this.existing;
|
|
472
481
|
}
|
|
473
482
|
async calcChanges(assets) {
|
|
@@ -328,8 +328,8 @@ class APIHandler {
|
|
|
328
328
|
const updateFN = this.getClientFN(this.functions.update);
|
|
329
329
|
const updatePayload = (() => {
|
|
330
330
|
const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
|
|
331
|
-
const
|
|
332
|
-
return (0, utils_1.
|
|
331
|
+
const obfuscatedStripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
|
|
332
|
+
return (0, utils_1.stripUnresolvedPlaceholders)(obfuscatedStripped, this.type, this.objString(updateItem));
|
|
333
333
|
})();
|
|
334
334
|
return updateFN(updateItem[this.id], updatePayload);
|
|
335
335
|
}, retryConfig)
|
|
@@ -347,8 +347,8 @@ class APIHandler {
|
|
|
347
347
|
const createFunction = this.getClientFN(this.functions.create);
|
|
348
348
|
const createPayload = (() => {
|
|
349
349
|
const strippedPayload = (0, utils_1.stripFields)(createItem, this.stripCreateFields);
|
|
350
|
-
const
|
|
351
|
-
return (0, utils_1.
|
|
350
|
+
const obfuscatedStripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(strippedPayload, this.sensitiveFieldsToObfuscate);
|
|
351
|
+
return (0, utils_1.stripUnresolvedPlaceholders)(obfuscatedStripped, this.type, this.objString(createItem));
|
|
352
352
|
})();
|
|
353
353
|
return createFunction(createPayload);
|
|
354
354
|
}, retryConfig)
|
|
@@ -369,8 +369,8 @@ class APIHandler {
|
|
|
369
369
|
const updateFN = this.getClientFN(this.functions.update);
|
|
370
370
|
const updatePayload = (() => {
|
|
371
371
|
const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
|
|
372
|
-
const
|
|
373
|
-
return (0, utils_1.
|
|
372
|
+
const obfuscatedStripped = (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
|
|
373
|
+
return (0, utils_1.stripUnresolvedPlaceholders)(obfuscatedStripped, this.type, this.objString(updateItem));
|
|
374
374
|
})();
|
|
375
375
|
return updateFN(updateItem[this.id], updatePayload);
|
|
376
376
|
}, retryConfig)
|
|
@@ -60,11 +60,20 @@ class GuardianFactorTemplatesHandler extends default_1.default {
|
|
|
60
60
|
await Promise.all(guardianFactorTemplates.map(async (fatorTemplates) => {
|
|
61
61
|
const { name, ...data } = fatorTemplates;
|
|
62
62
|
const params = { name: fatorTemplates.name };
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
try {
|
|
64
|
+
if (name === 'sms') {
|
|
65
|
+
await this.client.guardian.factors.sms.setTemplates(data);
|
|
66
|
+
}
|
|
67
|
+
else if (name === 'phone') {
|
|
68
|
+
await this.client.guardian.factors.phone.setTemplates(data);
|
|
69
|
+
}
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
catch (err) {
|
|
72
|
+
if ((0, utils_1.isForbiddenFeatureError)(err, this.type)) {
|
|
73
|
+
// Feature is deprecated/disabled on this tenant; warn and skip instead of failing the import.
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
68
77
|
}
|
|
69
78
|
this.didUpdate(params);
|
|
70
79
|
this.updated += 1;
|
|
@@ -25,14 +25,7 @@ const isFeatureUnavailableError = (err) => {
|
|
|
25
25
|
// Older Management API version where the endpoint is not available.
|
|
26
26
|
return true;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
err.originalError &&
|
|
30
|
-
err.originalError.response &&
|
|
31
|
-
err.originalError.response.body &&
|
|
32
|
-
err.originalError.response.body.errorCode === 'voice_mfa_not_allowed') {
|
|
33
|
-
// Recent Management API version, but with feature explicitly disabled.
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
28
|
+
// 403s (feature explicitly disabled) are handled by isForbiddenFeatureError.
|
|
36
29
|
return false;
|
|
37
30
|
};
|
|
38
31
|
class GuardianPhoneMessageTypesHandler extends default_1.default {
|
|
@@ -75,7 +68,16 @@ class GuardianPhoneMessageTypesHandler extends default_1.default {
|
|
|
75
68
|
}
|
|
76
69
|
}
|
|
77
70
|
const data = guardianPhoneFactorMessageTypes;
|
|
78
|
-
|
|
71
|
+
try {
|
|
72
|
+
await this.client.guardian.factors.phone.setMessageTypes(data);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (isFeatureUnavailableError(err) || (0, utils_1.isForbiddenFeatureError)(err, this.type)) {
|
|
76
|
+
// Feature is deprecated/disabled on this tenant; warn and skip instead of failing the import.
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
79
81
|
this.updated += 1;
|
|
80
82
|
this.didUpdate(guardianPhoneFactorMessageTypes);
|
|
81
83
|
}
|
|
@@ -22,14 +22,7 @@ const isFeatureUnavailableError = (err) => {
|
|
|
22
22
|
// Older Management API version where the endpoint is not available.
|
|
23
23
|
return true;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
err.originalError &&
|
|
27
|
-
err.originalError.response &&
|
|
28
|
-
err.originalError.response.body &&
|
|
29
|
-
err.originalError.response.body.errorCode === 'hooks_not_allowed') {
|
|
30
|
-
// Recent Management API version, but with feature explicitly disabled.
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
25
|
+
// 403s (feature explicitly disabled) are handled by isForbiddenFeatureError.
|
|
33
26
|
return false;
|
|
34
27
|
};
|
|
35
28
|
class GuardianPhoneSelectedProviderHandler extends default_1.default {
|
|
@@ -72,7 +65,16 @@ class GuardianPhoneSelectedProviderHandler extends default_1.default {
|
|
|
72
65
|
}
|
|
73
66
|
}
|
|
74
67
|
const data = guardianPhoneFactorSelectedProvider;
|
|
75
|
-
|
|
68
|
+
try {
|
|
69
|
+
await this.client.guardian.factors.phone.setProvider(data);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (isFeatureUnavailableError(err) || (0, utils_1.isForbiddenFeatureError)(err, this.type)) {
|
|
73
|
+
// Feature is deprecated/disabled on this tenant; warn and skip instead of failing the import.
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
76
78
|
this.updated += 1;
|
|
77
79
|
this.didUpdate(guardianPhoneFactorSelectedProvider);
|
|
78
80
|
}
|
|
@@ -151,7 +151,7 @@ class HooksHandler extends default_1.default {
|
|
|
151
151
|
.get(hook.id)
|
|
152
152
|
.then((hookWithCode) => this.client.hooks.secrets
|
|
153
153
|
.get(hook.id)
|
|
154
|
-
.then((
|
|
154
|
+
.then((secrets) => ({ ...hookWithCode, secrets })))));
|
|
155
155
|
return this.existing;
|
|
156
156
|
}
|
|
157
157
|
catch (err) {
|
|
@@ -85,6 +85,15 @@ export declare const schema: {
|
|
|
85
85
|
uniqueItems: boolean;
|
|
86
86
|
minItems: number;
|
|
87
87
|
};
|
|
88
|
+
auth0_managed: {
|
|
89
|
+
type: string;
|
|
90
|
+
items: {
|
|
91
|
+
type: string;
|
|
92
|
+
pattern: string;
|
|
93
|
+
};
|
|
94
|
+
uniqueItems: boolean;
|
|
95
|
+
minItems: number;
|
|
96
|
+
};
|
|
88
97
|
geo_country_codes: {
|
|
89
98
|
type: string;
|
|
90
99
|
items: {
|
|
@@ -176,6 +185,15 @@ export declare const schema: {
|
|
|
176
185
|
uniqueItems: boolean;
|
|
177
186
|
minItems: number;
|
|
178
187
|
};
|
|
188
|
+
auth0_managed: {
|
|
189
|
+
type: string;
|
|
190
|
+
items: {
|
|
191
|
+
type: string;
|
|
192
|
+
pattern: string;
|
|
193
|
+
};
|
|
194
|
+
uniqueItems: boolean;
|
|
195
|
+
minItems: number;
|
|
196
|
+
};
|
|
179
197
|
geo_country_codes: {
|
|
180
198
|
type: string;
|
|
181
199
|
items: {
|
|
@@ -69,6 +69,15 @@ const MatchSchema = {
|
|
|
69
69
|
uniqueItems: true,
|
|
70
70
|
minItems: 1,
|
|
71
71
|
},
|
|
72
|
+
auth0_managed: {
|
|
73
|
+
type: 'array',
|
|
74
|
+
items: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
pattern: '^auth0\\.[^.\\s]+$',
|
|
77
|
+
},
|
|
78
|
+
uniqueItems: true,
|
|
79
|
+
minItems: 1,
|
|
80
|
+
},
|
|
72
81
|
geo_country_codes: {
|
|
73
82
|
type: 'array',
|
|
74
83
|
items: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Management } from 'auth0';
|
|
2
2
|
import DefaultHandler from './default';
|
|
3
3
|
import { Asset, Assets, CalculatedChanges } from '../../../types';
|
|
4
|
+
import { Client } from './clients';
|
|
4
5
|
export declare const schema: {
|
|
5
6
|
type: string;
|
|
6
7
|
items: {
|
|
@@ -108,6 +109,25 @@ export declare const schema: {
|
|
|
108
109
|
type: string;
|
|
109
110
|
enum: ("allow" | "block")[];
|
|
110
111
|
};
|
|
112
|
+
is_app_entitlement_active: {
|
|
113
|
+
type: string;
|
|
114
|
+
};
|
|
115
|
+
clients: {
|
|
116
|
+
type: string;
|
|
117
|
+
items: {
|
|
118
|
+
type: string;
|
|
119
|
+
properties: {
|
|
120
|
+
client_id: {
|
|
121
|
+
type: string;
|
|
122
|
+
};
|
|
123
|
+
use_for_member_access: {
|
|
124
|
+
type: string;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
required: string[];
|
|
128
|
+
};
|
|
129
|
+
default: never[];
|
|
130
|
+
};
|
|
111
131
|
};
|
|
112
132
|
required: string[];
|
|
113
133
|
};
|
|
@@ -119,6 +139,7 @@ type FormattedClientGrants = {
|
|
|
119
139
|
export default class OrganizationsHandler extends DefaultHandler {
|
|
120
140
|
existing: Asset[];
|
|
121
141
|
formattedClientGrants: FormattedClientGrants[];
|
|
142
|
+
allClients: Client[];
|
|
122
143
|
constructor(config: DefaultHandler);
|
|
123
144
|
deleteOrganization(org: any): Promise<void>;
|
|
124
145
|
deleteOrganizations(data: Asset[]): Promise<void>;
|
|
@@ -128,6 +149,7 @@ export default class OrganizationsHandler extends DefaultHandler {
|
|
|
128
149
|
id: any;
|
|
129
150
|
}>;
|
|
130
151
|
getClientGrantIDByClientName(clientsName: string): string;
|
|
152
|
+
getClientIdByClientName(clientName: string): string;
|
|
131
153
|
getFormattedClientGrants(): Promise<FormattedClientGrants[]>;
|
|
132
154
|
updateOrganizations(updates: CalculatedChanges['update'], orgs: Asset[]): Promise<void>;
|
|
133
155
|
getType(): Promise<Asset[]>;
|
|
@@ -141,5 +163,11 @@ export default class OrganizationsHandler extends DefaultHandler {
|
|
|
141
163
|
createOrganizationDiscoveryDomain(organizationId: string, discoveryDomain: Management.CreateOrganizationDiscoveryDomainRequestContent): Promise<Management.CreateOrganizationDiscoveryDomainResponseContent>;
|
|
142
164
|
updateOrganizationDiscoveryDomain(organizationId: string, discoveryDomainId: string, discoveryDomain: string, discoveryDomainUpdate: Management.UpdateOrganizationDiscoveryDomainRequestContent): Promise<Management.UpdateOrganizationDiscoveryDomainResponseContent>;
|
|
143
165
|
deleteOrganizationDiscoveryDomain(organizationId: string, discoveryDomain: string, discoveryDomainId: string): Promise<void>;
|
|
166
|
+
getAllOrganizationClients(organizationId: string): Promise<Management.OrganizationClient[] | null>;
|
|
167
|
+
createOrganizationClients(organizationId: string, clients: Array<{
|
|
168
|
+
client_id: string;
|
|
169
|
+
use_for_member_access: boolean;
|
|
170
|
+
}>): Promise<void>;
|
|
171
|
+
deleteOrganizationClients(organizationId: string, clientIds: string[]): Promise<void>;
|
|
144
172
|
}
|
|
145
173
|
export {};
|
|
@@ -131,6 +131,19 @@ exports.schema = {
|
|
|
131
131
|
type: 'string',
|
|
132
132
|
enum: Object.values(auth0_1.Management.OrganizationThirdPartyClientAccessEnum),
|
|
133
133
|
},
|
|
134
|
+
is_app_entitlement_active: { type: 'boolean' },
|
|
135
|
+
clients: {
|
|
136
|
+
type: 'array',
|
|
137
|
+
items: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
client_id: { type: 'string' },
|
|
141
|
+
use_for_member_access: { type: 'boolean' },
|
|
142
|
+
},
|
|
143
|
+
required: ['client_id', 'use_for_member_access'],
|
|
144
|
+
},
|
|
145
|
+
default: [],
|
|
146
|
+
},
|
|
134
147
|
},
|
|
135
148
|
required: ['name'],
|
|
136
149
|
},
|
|
@@ -172,6 +185,7 @@ class OrganizationsHandler extends default_1.default {
|
|
|
172
185
|
const organization = { ...org };
|
|
173
186
|
delete organization.connections;
|
|
174
187
|
delete organization.client_grants;
|
|
188
|
+
delete organization.clients;
|
|
175
189
|
if ('discovery_domains' in organization) {
|
|
176
190
|
delete organization.discovery_domains;
|
|
177
191
|
}
|
|
@@ -195,6 +209,19 @@ class OrganizationsHandler extends default_1.default {
|
|
|
195
209
|
throw new Error(`Problem creating discovery domain ${discoveryDomain?.domain} for organization ${createdId}\n${err}`);
|
|
196
210
|
})));
|
|
197
211
|
}
|
|
212
|
+
if (typeof org.clients !== 'undefined' && org.clients.length > 0) {
|
|
213
|
+
const clientsToCreate = org.clients
|
|
214
|
+
.map((oc) => ({
|
|
215
|
+
client_id: this.getClientIdByClientName(oc.client_id),
|
|
216
|
+
use_for_member_access: oc.use_for_member_access,
|
|
217
|
+
}))
|
|
218
|
+
.filter((oc) => !!oc.client_id);
|
|
219
|
+
if (clientsToCreate.length > 0) {
|
|
220
|
+
await this.createOrganizationClients(createdId, clientsToCreate).catch((err) => {
|
|
221
|
+
throw new Error(`Problem creating org clients for organization ${createdId}\n${err}`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
198
225
|
return created;
|
|
199
226
|
}
|
|
200
227
|
async createOrganizations(creates) {
|
|
@@ -213,14 +240,15 @@ class OrganizationsHandler extends default_1.default {
|
|
|
213
240
|
.promise();
|
|
214
241
|
}
|
|
215
242
|
async updateOrganization(org, organizations) {
|
|
216
|
-
const { connections: existingConnections, client_grants: existingClientGrants, discovery_domains: existingDiscoveryDomains, } = await organizations.find((orgToUpdate) => orgToUpdate.name === org.name);
|
|
243
|
+
const { connections: existingConnections, client_grants: existingClientGrants, discovery_domains: existingDiscoveryDomains, clients: existingOrgClients = [], } = await organizations.find((orgToUpdate) => orgToUpdate.name === org.name);
|
|
217
244
|
const params = { id: org.id };
|
|
218
|
-
const { connections, client_grants: organizationClientGrants, discovery_domains: organizationDiscoveryDomains, } = org;
|
|
245
|
+
const { connections, client_grants: organizationClientGrants, discovery_domains: organizationDiscoveryDomains, clients: organizationClients, } = org;
|
|
219
246
|
delete org.connections;
|
|
220
247
|
delete org.name;
|
|
221
248
|
delete org.id;
|
|
222
249
|
delete org.client_grants;
|
|
223
250
|
delete org.discovery_domains;
|
|
251
|
+
delete org.clients;
|
|
224
252
|
await this.client.organizations.update(params.id, org);
|
|
225
253
|
// organization connections
|
|
226
254
|
const connectionsToRemove = existingConnections.filter((c) => !connections.find((x) => x.connection_id === c.connection_id));
|
|
@@ -324,12 +352,61 @@ class OrganizationsHandler extends default_1.default {
|
|
|
324
352
|
\n${orgDiscoveryDomainsToRemove.map((i) => this.objString(i)).join('\n')}`);
|
|
325
353
|
}
|
|
326
354
|
}
|
|
355
|
+
// organization clients
|
|
356
|
+
const orgClientsToAdd = (organizationClients || [])
|
|
357
|
+
.filter((c) => !existingOrgClients.find((x) => x.client_id === c.client_id))
|
|
358
|
+
.map((oc) => ({
|
|
359
|
+
client_id: this.getClientIdByClientName(oc.client_id),
|
|
360
|
+
use_for_member_access: oc.use_for_member_access,
|
|
361
|
+
}))
|
|
362
|
+
.filter((oc) => !!oc.client_id);
|
|
363
|
+
const orgClientsToRemove = (existingOrgClients || [])
|
|
364
|
+
.filter((c) => !(organizationClients || []).find((x) => x.client_id === c.client_id))
|
|
365
|
+
.map((oc) => this.getClientIdByClientName(oc.client_id))
|
|
366
|
+
.filter(Boolean);
|
|
367
|
+
const orgClientsToUpdate = (organizationClients || [])
|
|
368
|
+
.filter((c) => {
|
|
369
|
+
const existing = existingOrgClients.find((x) => x.client_id === c.client_id);
|
|
370
|
+
return existing && existing.use_for_member_access !== c.use_for_member_access;
|
|
371
|
+
})
|
|
372
|
+
.map((oc) => ({
|
|
373
|
+
client_id: this.getClientIdByClientName(oc.client_id),
|
|
374
|
+
use_for_member_access: oc.use_for_member_access,
|
|
375
|
+
}))
|
|
376
|
+
.filter((oc) => !!oc.client_id);
|
|
377
|
+
if (orgClientsToAdd.length > 0) {
|
|
378
|
+
await this.createOrganizationClients(params.id, orgClientsToAdd).catch((err) => {
|
|
379
|
+
throw new Error(`Problem adding org clients for organization ${params.id}\n${err}`);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (orgClientsToRemove.length > 0) {
|
|
383
|
+
if (this.config('AUTH0_ALLOW_DELETE') === 'true' ||
|
|
384
|
+
this.config('AUTH0_ALLOW_DELETE') === true) {
|
|
385
|
+
await this.deleteOrganizationClients(params.id, orgClientsToRemove).catch((err) => {
|
|
386
|
+
throw new Error(`Problem removing org clients for organization ${params.id}\n${err}`);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
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')}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
await Promise.all(orgClientsToUpdate.map((oc) => this.client.organizations.clients
|
|
394
|
+
.update(params.id, oc.client_id, {
|
|
395
|
+
use_for_member_access: oc.use_for_member_access,
|
|
396
|
+
})
|
|
397
|
+
.catch((err) => {
|
|
398
|
+
throw new Error(`Problem updating org client ${oc.client_id} for organization ${params.id}\n${err}`);
|
|
399
|
+
})));
|
|
327
400
|
return params;
|
|
328
401
|
}
|
|
329
402
|
getClientGrantIDByClientName(clientsName) {
|
|
330
403
|
const found = this.formattedClientGrants.find((c) => c.client_id === clientsName);
|
|
331
404
|
return found?.grant_id || '';
|
|
332
405
|
}
|
|
406
|
+
getClientIdByClientName(clientName) {
|
|
407
|
+
const found = this.allClients?.find((c) => c.name === clientName);
|
|
408
|
+
return found?.client_id || '';
|
|
409
|
+
}
|
|
333
410
|
async getFormattedClientGrants() {
|
|
334
411
|
const [clients, clientGrants] = await Promise.all([
|
|
335
412
|
(0, client_1.paginate)(this.client.clients.list, {
|
|
@@ -339,6 +416,8 @@ class OrganizationsHandler extends default_1.default {
|
|
|
339
416
|
paginate: true,
|
|
340
417
|
}),
|
|
341
418
|
]);
|
|
419
|
+
// Store clients for org-client name→ID resolution
|
|
420
|
+
this.allClients = clients;
|
|
342
421
|
// Convert clients by name to the id and store it in the formattedClientGrants
|
|
343
422
|
const formattedClientGrantsMapping = clientGrants?.map((clientGrant) => {
|
|
344
423
|
const { id, client_id: clientName } = clientGrant;
|
|
@@ -394,6 +473,14 @@ class OrganizationsHandler extends default_1.default {
|
|
|
394
473
|
if (organizationDiscoveryDomains) {
|
|
395
474
|
org.discovery_domains = organizationDiscoveryDomains;
|
|
396
475
|
}
|
|
476
|
+
// Get org-client associations
|
|
477
|
+
const orgClients = await this.getAllOrganizationClients(org.id);
|
|
478
|
+
if (orgClients) {
|
|
479
|
+
org.clients = orgClients.map((oc) => ({
|
|
480
|
+
client_id: (0, utils_1.convertClientIdToName)(oc.client_id, clients),
|
|
481
|
+
use_for_member_access: oc.use_for_member_access,
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
397
484
|
}
|
|
398
485
|
this.existing = organizations;
|
|
399
486
|
return this.existing;
|
|
@@ -539,6 +626,44 @@ class OrganizationsHandler extends default_1.default {
|
|
|
539
626
|
logger_1.default.debug(`Deleting discovery domain ${discoveryDomain} for organization ${organizationId}`);
|
|
540
627
|
await this.client.organizations.discoveryDomains.delete(organizationId, discoveryDomainId);
|
|
541
628
|
}
|
|
629
|
+
async getAllOrganizationClients(organizationId) {
|
|
630
|
+
const allOrgClients = [];
|
|
631
|
+
try {
|
|
632
|
+
let orgClients = await this.client.organizations.clients.list(organizationId);
|
|
633
|
+
allOrgClients.push(...orgClients.data);
|
|
634
|
+
while (orgClients.hasNextPage()) {
|
|
635
|
+
orgClients = await orgClients.getNextPage();
|
|
636
|
+
allOrgClients.push(...orgClients.data);
|
|
637
|
+
}
|
|
638
|
+
return allOrgClients;
|
|
639
|
+
}
|
|
640
|
+
catch (err) {
|
|
641
|
+
if (err.statusCode === 404 || err.statusCode === 501) {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
if (err.statusCode === 403 || err.errorCode === 'feature_not_enabled') {
|
|
645
|
+
logger_1.default.debug('Org-to-app entitlement is not enabled for this tenant. Skipping org-client associations.');
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
throw err;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
async createOrganizationClients(organizationId, clients) {
|
|
652
|
+
const BATCH_SIZE = 10;
|
|
653
|
+
for (let i = 0; i < clients.length; i += BATCH_SIZE) {
|
|
654
|
+
const batch = clients.slice(i, i + BATCH_SIZE);
|
|
655
|
+
logger_1.default.debug(`Creating ${batch.length} org client(s) for organization ${organizationId}`);
|
|
656
|
+
await this.client.organizations.clients.create(organizationId, { clients: batch });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
async deleteOrganizationClients(organizationId, clientIds) {
|
|
660
|
+
const BATCH_SIZE = 10;
|
|
661
|
+
for (let i = 0; i < clientIds.length; i += BATCH_SIZE) {
|
|
662
|
+
const batch = clientIds.slice(i, i + BATCH_SIZE);
|
|
663
|
+
logger_1.default.debug(`Deleting ${batch.length} org client(s) for organization ${organizationId}`);
|
|
664
|
+
await this.client.organizations.clients.delete(organizationId, { clients: batch });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
542
667
|
}
|
|
543
668
|
exports.default = OrganizationsHandler;
|
|
544
669
|
__decorate([
|
|
@@ -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<
|
|
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
|
-
|
|
125
|
-
|
|
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 (
|
|
128
|
-
await this.updatePhoneTemplates(
|
|
144
|
+
if (hasId.length > 0) {
|
|
145
|
+
await this.updatePhoneTemplates(hasId);
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
async createPhoneTemplate(template) {
|
|
132
|
-
|
|
133
|
-
|
|
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((
|
|
141
|
-
|
|
142
|
-
|
|
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
|
}
|