auth0-deploy-cli 8.25.0 → 8.27.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/.github/workflows/claude-code-review.yml +1 -4
- package/CHANGELOG.md +49 -1
- package/lib/tools/auth0/handlers/actions.js +1 -1
- package/lib/tools/auth0/handlers/clientGrants.d.ts +5 -0
- package/lib/tools/auth0/handlers/clientGrants.js +41 -8
- package/lib/tools/auth0/handlers/clients.d.ts +43 -8
- package/lib/tools/auth0/handlers/clients.js +109 -41
- package/lib/tools/auth0/handlers/connectionProfiles.js +0 -3
- package/lib/tools/auth0/handlers/connections.d.ts +7 -8
- package/lib/tools/auth0/handlers/connections.js +67 -59
- package/lib/tools/auth0/handlers/customDomains.d.ts +4 -0
- package/lib/tools/auth0/handlers/customDomains.js +6 -3
- package/lib/tools/auth0/handlers/databases.d.ts +71 -2
- package/lib/tools/auth0/handlers/databases.js +139 -25
- package/lib/tools/auth0/handlers/default.js +2 -4
- package/lib/tools/auth0/handlers/flowVaultConnections.js +6 -3
- package/lib/tools/auth0/handlers/flows.js +0 -3
- package/lib/tools/auth0/handlers/forms.js +0 -3
- package/lib/tools/auth0/handlers/logStreams.js +0 -3
- package/lib/tools/auth0/handlers/organizations.d.ts +4 -1
- package/lib/tools/auth0/handlers/organizations.js +61 -32
- package/lib/tools/auth0/handlers/prompts.d.ts +2 -2
- package/lib/tools/auth0/handlers/prompts.js +1 -0
- package/lib/tools/auth0/handlers/resourceServers.d.ts +1 -3
- package/lib/tools/auth0/handlers/resourceServers.js +4 -4
- package/lib/tools/auth0/handlers/roles.js +6 -3
- package/lib/tools/auth0/handlers/scimHandler.d.ts +5 -8
- package/lib/tools/auth0/handlers/scimHandler.js +19 -13
- package/lib/tools/auth0/handlers/selfServiceProfiles.d.ts +8 -0
- package/lib/tools/auth0/handlers/selfServiceProfiles.js +9 -0
- package/lib/tools/auth0/handlers/userAttributeProfiles.js +0 -3
- package/lib/tools/utils.d.ts +11 -0
- package/lib/tools/utils.js +24 -1
- package/package.json +8 -8
|
@@ -168,16 +168,21 @@ const getConnectionEnabledClients = async (auth0Client, connectionId) => {
|
|
|
168
168
|
try {
|
|
169
169
|
const enabledClientsFormatted = [];
|
|
170
170
|
let enabledClients = await auth0Client.connections.clients.get(connectionId);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
enabledClientsFormatted.push(client.client_id);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
171
|
+
// Process first page
|
|
172
|
+
enabledClients.data?.forEach((client) => {
|
|
173
|
+
if (client?.client_id) {
|
|
174
|
+
enabledClientsFormatted.push(client.client_id);
|
|
178
175
|
}
|
|
176
|
+
});
|
|
177
|
+
// Fetch remaining pages
|
|
178
|
+
while (enabledClients.hasNextPage()) {
|
|
179
179
|
enabledClients = await enabledClients.getNextPage();
|
|
180
|
-
|
|
180
|
+
enabledClients.data?.forEach((client) => {
|
|
181
|
+
if (client?.client_id) {
|
|
182
|
+
enabledClientsFormatted.push(client.client_id);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
181
186
|
return enabledClientsFormatted;
|
|
182
187
|
}
|
|
183
188
|
catch (error) {
|
|
@@ -195,13 +200,36 @@ exports.getConnectionEnabledClients = getConnectionEnabledClients;
|
|
|
195
200
|
* @returns Promise that resolves to true if the update was successful, false otherwise
|
|
196
201
|
*
|
|
197
202
|
*/
|
|
198
|
-
const updateConnectionEnabledClients = async (auth0Client, typeName, connectionId, enabledClientIds) => {
|
|
203
|
+
const updateConnectionEnabledClients = async (auth0Client, typeName, connectionId, enabledClientIds, existingConnections) => {
|
|
199
204
|
if (!connectionId || !Array.isArray(enabledClientIds) || !enabledClientIds.length)
|
|
200
205
|
return false;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
let existingEnabledClients = [];
|
|
207
|
+
if (Array.isArray(existingConnections)) {
|
|
208
|
+
const existingConnection = existingConnections.find((con) => con.id === connectionId);
|
|
209
|
+
existingEnabledClients = existingConnection?.enabled_clients ?? [];
|
|
210
|
+
}
|
|
211
|
+
// Determine which clients to enable vs. disable by comparing the incoming `enabledClientIds` with the `existingEnabledClients`.
|
|
212
|
+
const enabledClientIdSet = new Set(enabledClientIds);
|
|
213
|
+
const existingClientIdSet = new Set(existingEnabledClients);
|
|
214
|
+
// If both sets are identical, skip the update entirely.
|
|
215
|
+
if (enabledClientIdSet.size === existingClientIdSet.size &&
|
|
216
|
+
[...enabledClientIdSet].every((id) => existingClientIdSet.has(id))) {
|
|
217
|
+
logger_1.default.debug(`Enabled clients for ${typeName}: ${connectionId} are unchanged, skipping update`);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
const clientsToEnable = enabledClientIds;
|
|
221
|
+
// Any client that exists on the tenant but not in the provided `enabledClientIds` should be disabled.
|
|
222
|
+
const clientsToDisable = existingEnabledClients.filter((clientId) => !enabledClientIdSet.has(clientId));
|
|
223
|
+
const enabledClientUpdatePayloads = [
|
|
224
|
+
...clientsToEnable.map((clientId) => ({
|
|
225
|
+
client_id: clientId,
|
|
226
|
+
status: true,
|
|
227
|
+
})),
|
|
228
|
+
...clientsToDisable.map((clientId) => ({
|
|
229
|
+
client_id: clientId,
|
|
230
|
+
status: false,
|
|
231
|
+
})),
|
|
232
|
+
];
|
|
205
233
|
const payloadChunks = (0, lodash_1.chunk)(enabledClientUpdatePayloads, 50);
|
|
206
234
|
try {
|
|
207
235
|
await Promise.all(payloadChunks.map((payload) => auth0Client.connections.clients.update(connectionId, payload)));
|
|
@@ -225,7 +253,7 @@ exports.updateConnectionEnabledClients = updateConnectionEnabledClients;
|
|
|
225
253
|
*
|
|
226
254
|
* @returns A Promise that resolves when all enabled client updates are complete
|
|
227
255
|
*/
|
|
228
|
-
const processConnectionEnabledClients = async (auth0Client, typeName, changes, delayMs = 2500 // Default delay is 2.5 seconds
|
|
256
|
+
const processConnectionEnabledClients = async (auth0Client, typeName, existingConnections, changes, delayMs = 2500 // Default delay is 2.5 seconds
|
|
229
257
|
) => {
|
|
230
258
|
const { create, update, conflicts } = changes;
|
|
231
259
|
let createWithId = [];
|
|
@@ -262,9 +290,9 @@ const processConnectionEnabledClients = async (auth0Client, typeName, changes, d
|
|
|
262
290
|
// Process enabled clients for each change type
|
|
263
291
|
// Delete is handled by the `processChanges` method, removed connection completely
|
|
264
292
|
await Promise.all([
|
|
265
|
-
...createWithId.map((conn) => (0, exports.updateConnectionEnabledClients)(auth0Client, typeName, conn.id, conn.enabled_clients)),
|
|
266
|
-
...update.map((conn) => (0, exports.updateConnectionEnabledClients)(auth0Client, typeName, conn.id, conn.enabled_clients)),
|
|
267
|
-
...conflicts.map((conn) => (0, exports.updateConnectionEnabledClients)(auth0Client, typeName, conn.id, conn.enabled_clients)),
|
|
293
|
+
...createWithId.map((conn) => (0, exports.updateConnectionEnabledClients)(auth0Client, typeName, conn.id, conn.enabled_clients, existingConnections)),
|
|
294
|
+
...update.map((conn) => (0, exports.updateConnectionEnabledClients)(auth0Client, typeName, conn.id, conn.enabled_clients, existingConnections)),
|
|
295
|
+
...conflicts.map((conn) => (0, exports.updateConnectionEnabledClients)(auth0Client, typeName, conn.id, conn.enabled_clients, existingConnections)),
|
|
268
296
|
]);
|
|
269
297
|
};
|
|
270
298
|
exports.processConnectionEnabledClients = processConnectionEnabledClients;
|
|
@@ -277,7 +305,7 @@ class ConnectionsHandler extends default_1.default {
|
|
|
277
305
|
functions: {
|
|
278
306
|
// When `connections` is updated, it can result in `update`,`create` or `delete` action on SCIM.
|
|
279
307
|
// Because, `scim_configuration` is inside `connections`.
|
|
280
|
-
update: async (
|
|
308
|
+
update: async (connectionId, bodyParams) => this.scimHandler.updateOverride(connectionId, bodyParams),
|
|
281
309
|
// When a new `connection` is created. We can perform only `create` option on SCIM.
|
|
282
310
|
// When a connection is `deleted`. `scim_configuration` is also deleted along with it; no action on SCIM is required.
|
|
283
311
|
create: async (bodyParams) => this.scimHandler.createOverride(bodyParams),
|
|
@@ -306,45 +334,19 @@ class ConnectionsHandler extends default_1.default {
|
|
|
306
334
|
}
|
|
307
335
|
}
|
|
308
336
|
/**
|
|
309
|
-
* Retrieves directory provisioning
|
|
310
|
-
* @
|
|
311
|
-
* @returns A promise that resolves to the configuration object, or null if not configured/supported
|
|
337
|
+
* Retrieves all directory provisioning configurations for all connections.
|
|
338
|
+
* @returns A promise that resolves to the configurations object, or null if not configured/supported
|
|
312
339
|
*/
|
|
313
|
-
async
|
|
314
|
-
|
|
315
|
-
return null;
|
|
316
|
-
const creates = [connectionId];
|
|
317
|
-
let config = null;
|
|
340
|
+
async getConnectionDirectoryProvisionings() {
|
|
341
|
+
let directoryProvisioningConfigs;
|
|
318
342
|
try {
|
|
319
|
-
await this.client.
|
|
320
|
-
|
|
321
|
-
data: creates || [],
|
|
322
|
-
generator: async (id) => this.client.connections.directoryProvisioning
|
|
323
|
-
.get(id)
|
|
324
|
-
.then((resp) => {
|
|
325
|
-
config = resp;
|
|
326
|
-
})
|
|
327
|
-
.catch((err) => {
|
|
328
|
-
throw new auth0_1.ManagementError(err);
|
|
329
|
-
}),
|
|
330
|
-
})
|
|
331
|
-
.promise();
|
|
332
|
-
const stripKeysFromOutput = [
|
|
333
|
-
'connection_id',
|
|
334
|
-
'connection_name',
|
|
335
|
-
'strategy',
|
|
336
|
-
'created_at',
|
|
337
|
-
'updated_at',
|
|
338
|
-
];
|
|
339
|
-
stripKeysFromOutput.forEach((key) => {
|
|
340
|
-
if (config && key in config) {
|
|
341
|
-
delete config[key];
|
|
342
|
-
}
|
|
343
|
+
directoryProvisioningConfigs = await (0, client_1.paginate)(this.client.connections.directoryProvisioning.list, {
|
|
344
|
+
checkpoint: true,
|
|
343
345
|
});
|
|
344
|
-
return
|
|
346
|
+
return directoryProvisioningConfigs;
|
|
345
347
|
}
|
|
346
348
|
catch (error) {
|
|
347
|
-
const errLog = `Unable to fetch directory provisioning for
|
|
349
|
+
const errLog = `Unable to fetch directory provisioning for connections. `;
|
|
348
350
|
if (error instanceof auth0_1.ManagementError) {
|
|
349
351
|
const bodyMessage = error.body?.message;
|
|
350
352
|
logger_1.default.warn(errLog + bodyMessage);
|
|
@@ -471,9 +473,12 @@ class ConnectionsHandler extends default_1.default {
|
|
|
471
473
|
async getType() {
|
|
472
474
|
if (this.existing)
|
|
473
475
|
return this.existing;
|
|
474
|
-
const connections = await
|
|
475
|
-
|
|
476
|
-
|
|
476
|
+
const [connections, directoryProvisioningConfigs] = await Promise.all([
|
|
477
|
+
(0, client_1.paginate)(this.client.connections.list, {
|
|
478
|
+
checkpoint: true,
|
|
479
|
+
}),
|
|
480
|
+
this.getConnectionDirectoryProvisionings(),
|
|
481
|
+
]);
|
|
477
482
|
// Filter out database connections as we have separate handler for it
|
|
478
483
|
const filteredConnections = connections.filter((c) => c.strategy !== 'auth0');
|
|
479
484
|
// If options option is empty for all connection, log the missing options scope.
|
|
@@ -493,10 +498,13 @@ class ConnectionsHandler extends default_1.default {
|
|
|
493
498
|
if (enabledClients && enabledClients?.length) {
|
|
494
499
|
connection.enabled_clients = enabledClients;
|
|
495
500
|
}
|
|
496
|
-
if (connection.strategy === 'google-apps') {
|
|
497
|
-
const dirProvConfig =
|
|
501
|
+
if (connection.strategy === 'google-apps' && directoryProvisioningConfigs) {
|
|
502
|
+
const dirProvConfig = directoryProvisioningConfigs.find((congigCon) => congigCon.connection_id === con.id);
|
|
498
503
|
if (dirProvConfig) {
|
|
499
|
-
connection.directory_provisioning_configuration =
|
|
504
|
+
connection.directory_provisioning_configuration = {
|
|
505
|
+
mapping: dirProvConfig.mapping,
|
|
506
|
+
synchronize_automatically: dirProvConfig.synchronize_automatically,
|
|
507
|
+
};
|
|
500
508
|
}
|
|
501
509
|
}
|
|
502
510
|
return connection;
|
|
@@ -558,7 +566,7 @@ class ConnectionsHandler extends default_1.default {
|
|
|
558
566
|
changes = (0, utils_1.filterIncluded)(changes, includedConnections);
|
|
559
567
|
await super.processChanges(assets, changes);
|
|
560
568
|
// process enabled clients
|
|
561
|
-
await (0, exports.processConnectionEnabledClients)(this.client, this.type, changes);
|
|
569
|
+
await (0, exports.processConnectionEnabledClients)(this.client, this.type, await this.existing, changes);
|
|
562
570
|
// process directory provisioning
|
|
563
571
|
await this.processConnectionDirectoryProvisioning(changes);
|
|
564
572
|
}
|
|
@@ -77,6 +77,10 @@ exports.schema = {
|
|
|
77
77
|
description: 'Custom domain verification method. Must be `txt`.',
|
|
78
78
|
defaultValue: 'txt',
|
|
79
79
|
},
|
|
80
|
+
relying_party_identifier: {
|
|
81
|
+
type: ['string'],
|
|
82
|
+
description: 'Relying Party ID (rpId) to be used for Passkeys on this custom domain. If not provided or set to null, the full domain will be used.',
|
|
83
|
+
},
|
|
80
84
|
},
|
|
81
85
|
required: ['domain', 'type'],
|
|
82
86
|
},
|
|
@@ -95,6 +99,7 @@ class CustomDomainsHadnler extends default_1.default {
|
|
|
95
99
|
'certificate',
|
|
96
100
|
'created_at',
|
|
97
101
|
'updated_at',
|
|
102
|
+
'is_default',
|
|
98
103
|
],
|
|
99
104
|
stripUpdateFields: [
|
|
100
105
|
'status',
|
|
@@ -106,10 +111,8 @@ class CustomDomainsHadnler extends default_1.default {
|
|
|
106
111
|
'certificate',
|
|
107
112
|
'created_at',
|
|
108
113
|
'updated_at',
|
|
114
|
+
'is_default',
|
|
109
115
|
],
|
|
110
|
-
functions: {
|
|
111
|
-
update: (args, data) => this.client.customDomains.update(args.custom_domain_id, data),
|
|
112
|
-
},
|
|
113
116
|
});
|
|
114
117
|
}
|
|
115
118
|
objString(item) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import DefaultAPIHandler from './default';
|
|
2
|
-
import { CalculatedChanges, Assets
|
|
2
|
+
import { CalculatedChanges, Assets } from '../../../types';
|
|
3
|
+
import { Connection } from './connections';
|
|
4
|
+
import { Action } from './actions';
|
|
3
5
|
export declare const schema: {
|
|
4
6
|
type: string;
|
|
5
7
|
items: {
|
|
@@ -16,6 +18,50 @@ export declare const schema: {
|
|
|
16
18
|
options: {
|
|
17
19
|
type: string;
|
|
18
20
|
properties: {
|
|
21
|
+
authentication_methods: {
|
|
22
|
+
type: string;
|
|
23
|
+
properties: {
|
|
24
|
+
passkey: {
|
|
25
|
+
type: string;
|
|
26
|
+
properties: {
|
|
27
|
+
enabled: {
|
|
28
|
+
type: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
password: {
|
|
33
|
+
type: string;
|
|
34
|
+
properties: {
|
|
35
|
+
enabled: {
|
|
36
|
+
type: string;
|
|
37
|
+
};
|
|
38
|
+
api_behavior: {
|
|
39
|
+
type: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
email_otp: {
|
|
44
|
+
type: string;
|
|
45
|
+
properties: {
|
|
46
|
+
enabled: {
|
|
47
|
+
type: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
phone_otp: {
|
|
52
|
+
type: string;
|
|
53
|
+
properties: {
|
|
54
|
+
enabled: {
|
|
55
|
+
type: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
disable_self_service_change_password: {
|
|
62
|
+
type: string;
|
|
63
|
+
default: boolean;
|
|
64
|
+
};
|
|
19
65
|
customScripts: {
|
|
20
66
|
type: string;
|
|
21
67
|
properties: {};
|
|
@@ -36,6 +82,10 @@ export declare const schema: {
|
|
|
36
82
|
active: {
|
|
37
83
|
type: string;
|
|
38
84
|
};
|
|
85
|
+
default_method: {
|
|
86
|
+
type: string;
|
|
87
|
+
enum: string[];
|
|
88
|
+
};
|
|
39
89
|
};
|
|
40
90
|
};
|
|
41
91
|
profile_required: {
|
|
@@ -73,6 +123,10 @@ export declare const schema: {
|
|
|
73
123
|
active: {
|
|
74
124
|
type: string;
|
|
75
125
|
};
|
|
126
|
+
default_method: {
|
|
127
|
+
type: string;
|
|
128
|
+
enum: string[];
|
|
129
|
+
};
|
|
76
130
|
};
|
|
77
131
|
};
|
|
78
132
|
profile_required: {
|
|
@@ -106,6 +160,10 @@ export declare const schema: {
|
|
|
106
160
|
active: {
|
|
107
161
|
type: string;
|
|
108
162
|
};
|
|
163
|
+
default_method: {
|
|
164
|
+
type: string;
|
|
165
|
+
enum: string[];
|
|
166
|
+
};
|
|
109
167
|
};
|
|
110
168
|
};
|
|
111
169
|
profile_required: {
|
|
@@ -124,6 +182,14 @@ export declare const schema: {
|
|
|
124
182
|
};
|
|
125
183
|
};
|
|
126
184
|
};
|
|
185
|
+
custom_password_hash: {
|
|
186
|
+
type: string;
|
|
187
|
+
properties: {
|
|
188
|
+
action_id: {
|
|
189
|
+
type: string;
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
};
|
|
127
193
|
};
|
|
128
194
|
};
|
|
129
195
|
};
|
|
@@ -131,12 +197,15 @@ export declare const schema: {
|
|
|
131
197
|
};
|
|
132
198
|
};
|
|
133
199
|
export default class DatabaseHandler extends DefaultAPIHandler {
|
|
200
|
+
existing: Connection[] | null;
|
|
134
201
|
constructor(config: DefaultAPIHandler);
|
|
135
202
|
objString(db: any): string;
|
|
203
|
+
getFormattedOptions(options: any, actions?: Action[]): any;
|
|
136
204
|
validate(assets: Assets): Promise<void>;
|
|
205
|
+
private validatePasswordlessSettings;
|
|
137
206
|
private validateEmailUniqueConstraints;
|
|
138
207
|
getClientFN(fn: 'create' | 'delete' | 'getAll' | 'update'): Function;
|
|
139
|
-
getType(): Promise<
|
|
208
|
+
getType(): Promise<Connection[]>;
|
|
140
209
|
calcChanges(assets: Assets): Promise<CalculatedChanges>;
|
|
141
210
|
processChanges(assets: Assets): Promise<void>;
|
|
142
211
|
}
|
|
@@ -60,6 +60,37 @@ exports.schema = {
|
|
|
60
60
|
options: {
|
|
61
61
|
type: 'object',
|
|
62
62
|
properties: {
|
|
63
|
+
authentication_methods: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
passkey: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
enabled: { type: 'boolean' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
password: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
enabled: { type: 'boolean' },
|
|
76
|
+
api_behavior: { type: 'string' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
email_otp: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
enabled: { type: 'boolean' },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
phone_otp: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
enabled: { type: 'boolean' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
disable_self_service_change_password: { type: 'boolean', default: false },
|
|
63
94
|
customScripts: {
|
|
64
95
|
type: 'object',
|
|
65
96
|
properties: {
|
|
@@ -77,6 +108,7 @@ exports.schema = {
|
|
|
77
108
|
type: 'object',
|
|
78
109
|
properties: {
|
|
79
110
|
active: { type: 'boolean' },
|
|
111
|
+
default_method: { type: 'string', enum: ['password', 'email_otp'] },
|
|
80
112
|
},
|
|
81
113
|
},
|
|
82
114
|
profile_required: { type: 'boolean' },
|
|
@@ -102,6 +134,7 @@ exports.schema = {
|
|
|
102
134
|
type: 'object',
|
|
103
135
|
properties: {
|
|
104
136
|
active: { type: 'boolean' },
|
|
137
|
+
default_method: { type: 'string', enum: ['password', 'phone_otp'] },
|
|
105
138
|
},
|
|
106
139
|
},
|
|
107
140
|
profile_required: { type: 'boolean' },
|
|
@@ -126,6 +159,7 @@ exports.schema = {
|
|
|
126
159
|
type: 'object',
|
|
127
160
|
properties: {
|
|
128
161
|
active: { type: 'boolean' },
|
|
162
|
+
default_method: { type: 'string', enum: ['password'] },
|
|
129
163
|
},
|
|
130
164
|
},
|
|
131
165
|
profile_required: { type: 'boolean' },
|
|
@@ -139,6 +173,12 @@ exports.schema = {
|
|
|
139
173
|
},
|
|
140
174
|
},
|
|
141
175
|
},
|
|
176
|
+
custom_password_hash: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
action_id: { type: 'string' },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
142
182
|
},
|
|
143
183
|
},
|
|
144
184
|
},
|
|
@@ -156,6 +196,22 @@ class DatabaseHandler extends default_1.default {
|
|
|
156
196
|
objString(db) {
|
|
157
197
|
return super.objString({ name: db.name, id: db.id });
|
|
158
198
|
}
|
|
199
|
+
getFormattedOptions(options, actions = []) {
|
|
200
|
+
try {
|
|
201
|
+
const formattedOptions = { ...options };
|
|
202
|
+
// Handle custom_password_hash.action_id conversion
|
|
203
|
+
if (options?.custom_password_hash?.action_id) {
|
|
204
|
+
formattedOptions.custom_password_hash = {
|
|
205
|
+
...options.custom_password_hash,
|
|
206
|
+
action_id: (0, utils_1.convertActionNameToId)(options.custom_password_hash.action_id, actions),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return formattedOptions;
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
159
215
|
async validate(assets) {
|
|
160
216
|
const { databases } = assets;
|
|
161
217
|
// Do nothing if not set
|
|
@@ -164,9 +220,25 @@ class DatabaseHandler extends default_1.default {
|
|
|
164
220
|
// Validate each database
|
|
165
221
|
databases.forEach((database) => {
|
|
166
222
|
this.validateEmailUniqueConstraints(database);
|
|
223
|
+
// this.validatePasswordlessSettings(database); // Enable only the feature is GA PR:#1282
|
|
167
224
|
});
|
|
168
225
|
await super.validate(assets);
|
|
169
226
|
}
|
|
227
|
+
validatePasswordlessSettings(payload) {
|
|
228
|
+
const options = payload?.options;
|
|
229
|
+
if (!options)
|
|
230
|
+
return;
|
|
231
|
+
const passwordEnabled = options?.authentication_methods?.password?.enabled;
|
|
232
|
+
const disableSelfServiceChangePassword = options?.disable_self_service_change_password;
|
|
233
|
+
if (passwordEnabled === undefined || disableSelfServiceChangePassword === undefined)
|
|
234
|
+
return;
|
|
235
|
+
if (passwordEnabled === false && disableSelfServiceChangePassword !== true) {
|
|
236
|
+
throw new Error(`Database "${payload.name}": When password authentication is disabled, disable_self_service_change_password must be true.`);
|
|
237
|
+
}
|
|
238
|
+
if (passwordEnabled === true && disableSelfServiceChangePassword === true) {
|
|
239
|
+
throw new Error(`Database "${payload.name}": disable_self_service_change_password must be false when password authentication is enabled.`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
170
242
|
validateEmailUniqueConstraints(payload) {
|
|
171
243
|
const attributes = payload?.options?.attributes;
|
|
172
244
|
// Only validate if attributes are present
|
|
@@ -192,11 +264,16 @@ class DatabaseHandler extends default_1.default {
|
|
|
192
264
|
getClientFN(fn) {
|
|
193
265
|
// Override this as a database is actually a connection but we are treating them as a different object
|
|
194
266
|
if (fn === 'create') {
|
|
195
|
-
return (payload) =>
|
|
267
|
+
return (payload) => {
|
|
268
|
+
// Remove deprecated enabled_clients field
|
|
269
|
+
if ('enabled_clients' in payload)
|
|
270
|
+
delete payload.enabled_clients;
|
|
271
|
+
return this.client.connections.create(payload);
|
|
272
|
+
};
|
|
196
273
|
}
|
|
197
274
|
// If we going to update database, we need to get current options first
|
|
198
275
|
if (fn === 'update') {
|
|
199
|
-
return (
|
|
276
|
+
return (id, payload) => this.client.connections.get(id).then((response) => {
|
|
200
277
|
const connection = response;
|
|
201
278
|
const attributes = payload?.options?.attributes;
|
|
202
279
|
const requiresUsername = payload?.options?.requires_username;
|
|
@@ -215,7 +292,10 @@ class DatabaseHandler extends default_1.default {
|
|
|
215
292
|
if (payload.options && Object.keys(payload.options).length === 0) {
|
|
216
293
|
delete payload.options;
|
|
217
294
|
}
|
|
218
|
-
|
|
295
|
+
// Remove deprecated enabled_clients field
|
|
296
|
+
if ('enabled_clients' in payload)
|
|
297
|
+
delete payload.enabled_clients;
|
|
298
|
+
return this.client.connections.update(id, payload);
|
|
219
299
|
});
|
|
220
300
|
}
|
|
221
301
|
return this.client.connections[fn].bind(this.client.connections);
|
|
@@ -223,25 +303,52 @@ class DatabaseHandler extends default_1.default {
|
|
|
223
303
|
async getType() {
|
|
224
304
|
if (this.existing)
|
|
225
305
|
return this.existing;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
306
|
+
// Fetch connections and actions concurrently
|
|
307
|
+
const [connections, actions] = await Promise.all([
|
|
308
|
+
(0, client_1.paginate)(this.client.connections.list, {
|
|
309
|
+
strategy: [auth0_1.Management.ConnectionStrategyEnum.Auth0],
|
|
310
|
+
checkpoint: true,
|
|
311
|
+
}),
|
|
312
|
+
(0, client_1.paginate)(this.client.actions.list, {
|
|
313
|
+
paginate: true,
|
|
314
|
+
include_totals: true,
|
|
315
|
+
}),
|
|
316
|
+
]);
|
|
230
317
|
const dbConnectionsWithEnabledClients = await Promise.all(connections.map(async (con) => {
|
|
231
318
|
if (!con?.id)
|
|
232
319
|
return con;
|
|
233
320
|
const enabledClients = await (0, connections_1.getConnectionEnabledClients)(this.client, con.id);
|
|
321
|
+
const connection = { ...con };
|
|
234
322
|
if (enabledClients && enabledClients?.length) {
|
|
235
|
-
|
|
323
|
+
connection.enabled_clients = enabledClients;
|
|
236
324
|
}
|
|
237
|
-
return
|
|
325
|
+
return connection;
|
|
238
326
|
}));
|
|
327
|
+
// Convert action ID back to action name for export
|
|
328
|
+
const dbConnectionsWithActionNames = dbConnectionsWithEnabledClients.map((connection) => {
|
|
329
|
+
if (connection.options && 'custom_password_hash' in connection.options) {
|
|
330
|
+
const customPasswordHash = connection.options?.custom_password_hash;
|
|
331
|
+
if (customPasswordHash?.action_id) {
|
|
332
|
+
return {
|
|
333
|
+
...connection,
|
|
334
|
+
options: {
|
|
335
|
+
...connection.options,
|
|
336
|
+
custom_password_hash: {
|
|
337
|
+
...customPasswordHash,
|
|
338
|
+
action_id: (0, utils_1.convertActionIdToName)(customPasswordHash.action_id, actions),
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return connection;
|
|
345
|
+
});
|
|
239
346
|
// If options option is empty for all connection, log the missing options scope.
|
|
240
|
-
const isOptionExists =
|
|
347
|
+
const isOptionExists = dbConnectionsWithActionNames.every((c) => c.options && Object.keys(c.options).length > 0);
|
|
241
348
|
if (!isOptionExists) {
|
|
242
349
|
logger_1.default.warn(`Insufficient scope the read:connections_options scope is required to get ${this.type} options.`);
|
|
243
350
|
}
|
|
244
|
-
this.existing =
|
|
351
|
+
this.existing = dbConnectionsWithActionNames;
|
|
245
352
|
return this.existing;
|
|
246
353
|
}
|
|
247
354
|
async calcChanges(assets) {
|
|
@@ -255,22 +362,29 @@ class DatabaseHandler extends default_1.default {
|
|
|
255
362
|
conflicts: [],
|
|
256
363
|
};
|
|
257
364
|
// Convert enabled_clients by name to the id
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
365
|
+
// Fetch clients, connections, and actions concurrently
|
|
366
|
+
const [clients, existingDatabasesConnections, actions] = await Promise.all([
|
|
367
|
+
(0, client_1.paginate)(this.client.clients.list, {
|
|
368
|
+
paginate: true,
|
|
369
|
+
}),
|
|
370
|
+
(0, client_1.paginate)(this.client.connections.list, {
|
|
371
|
+
strategy: [auth0_1.Management.ConnectionStrategyEnum.Auth0],
|
|
372
|
+
checkpoint: true,
|
|
373
|
+
include_totals: true,
|
|
374
|
+
}),
|
|
375
|
+
(0, client_1.paginate)(this.client.actions.list, {
|
|
376
|
+
paginate: true,
|
|
377
|
+
include_totals: true,
|
|
378
|
+
}),
|
|
379
|
+
]);
|
|
266
380
|
const formatted = databases.map((db) => {
|
|
381
|
+
const { options, ...rest } = db;
|
|
382
|
+
const formattedOptions = this.getFormattedOptions(options, actions);
|
|
383
|
+
const formattedDb = { ...rest, options: formattedOptions };
|
|
267
384
|
if (db.enabled_clients) {
|
|
268
|
-
|
|
269
|
-
...db,
|
|
270
|
-
enabled_clients: (0, utils_1.getEnabledClients)(assets, db, existingDatabasesConnections, clients),
|
|
271
|
-
};
|
|
385
|
+
formattedDb.enabled_clients = (0, utils_1.getEnabledClients)(assets, db, existingDatabasesConnections, clients);
|
|
272
386
|
}
|
|
273
|
-
return
|
|
387
|
+
return formattedDb;
|
|
274
388
|
});
|
|
275
389
|
return super.calcChanges({ ...assets, databases: formatted });
|
|
276
390
|
}
|
|
@@ -289,7 +403,7 @@ class DatabaseHandler extends default_1.default {
|
|
|
289
403
|
const changes = await this.calcChanges(assets);
|
|
290
404
|
await super.processChanges(assets, (0, utils_1.filterExcluded)(changes, excludedConnections));
|
|
291
405
|
// process enabled clients
|
|
292
|
-
await (0, connections_1.processConnectionEnabledClients)(this.client, this.type, (0, utils_1.filterExcluded)(changes, excludedConnections));
|
|
406
|
+
await (0, connections_1.processConnectionEnabledClients)(this.client, this.type, await this.existing, (0, utils_1.filterExcluded)(changes, excludedConnections));
|
|
293
407
|
}
|
|
294
408
|
}
|
|
295
409
|
exports.default = DatabaseHandler;
|
|
@@ -232,12 +232,11 @@ class APIHandler {
|
|
|
232
232
|
data: conflicts || [],
|
|
233
233
|
generator: (updateItem) => retryWithExponentialBackoff(() => {
|
|
234
234
|
const updateFN = this.getClientFN(this.functions.update);
|
|
235
|
-
const params = { [this.id]: updateItem[this.id] };
|
|
236
235
|
const updatePayload = (() => {
|
|
237
236
|
const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
|
|
238
237
|
return (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
|
|
239
238
|
})();
|
|
240
|
-
return updateFN(
|
|
239
|
+
return updateFN(updateItem[this.id], updatePayload);
|
|
241
240
|
}, retryConfig)
|
|
242
241
|
.then((data) => this.didUpdate(data))
|
|
243
242
|
.catch((err) => {
|
|
@@ -272,12 +271,11 @@ class APIHandler {
|
|
|
272
271
|
data: update || [],
|
|
273
272
|
generator: (updateItem) => retryWithExponentialBackoff(() => {
|
|
274
273
|
const updateFN = this.getClientFN(this.functions.update);
|
|
275
|
-
const params = { [this.id]: updateItem[this.id] };
|
|
276
274
|
const updatePayload = (() => {
|
|
277
275
|
const data = (0, utils_1.stripFields)({ ...updateItem }, this.stripUpdateFields);
|
|
278
276
|
return (0, utils_1.stripObfuscatedFieldsFromPayload)(data, this.sensitiveFieldsToObfuscate);
|
|
279
277
|
})();
|
|
280
|
-
return updateFN(
|
|
278
|
+
return updateFN(updateItem[this.id], updatePayload);
|
|
281
279
|
}, retryConfig)
|
|
282
280
|
.then((data) => {
|
|
283
281
|
this.didUpdate(data);
|