@happyvertical/directory 0.74.8
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/AGENT.md +34 -0
- package/LICENSE +7 -0
- package/dist/adapters/aws.d.ts +39 -0
- package/dist/adapters/aws.d.ts.map +1 -0
- package/dist/adapters/kanidm.d.ts +41 -0
- package/dist/adapters/kanidm.d.ts.map +1 -0
- package/dist/adapters/postgres.d.ts +73 -0
- package/dist/adapters/postgres.d.ts.map +1 -0
- package/dist/adapters/stalwart.d.ts +39 -0
- package/dist/adapters/stalwart.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1942 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/errors.d.ts +33 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/factory.d.ts +36 -0
- package/dist/shared/factory.d.ts.map +1 -0
- package/dist/shared/types.d.ts +321 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/metadata.json +32 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1942 @@
|
|
|
1
|
+
import { IAMClient, ListUsersCommand, CreateUserCommand, GetUserCommand, UpdateUserCommand, DeleteUserCommand, CreateGroupCommand, GetGroupCommand, DeleteGroupCommand, ListGroupsCommand, AddUserToGroupCommand, RemoveUserFromGroupCommand, ListGroupsForUserCommand, AttachUserPolicyCommand, DetachUserPolicyCommand, CreateAccessKeyCommand, DeleteAccessKeyCommand } from "@aws-sdk/client-iam";
|
|
2
|
+
import { OrganizationsClient, CreateOrganizationalUnitCommand, DescribeOrganizationalUnitCommand, ListOrganizationalUnitsForParentCommand, CreateAccountCommand, DescribeCreateAccountStatusCommand, ListAccountsCommand, MoveAccountCommand } from "@aws-sdk/client-organizations";
|
|
3
|
+
import pg from "pg";
|
|
4
|
+
class DirectoryError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
provider;
|
|
7
|
+
cause;
|
|
8
|
+
constructor(message, code, provider, cause) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "DirectoryError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.provider = provider;
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class ConnectionError extends DirectoryError {
|
|
17
|
+
constructor(message, provider, cause) {
|
|
18
|
+
super(message, "CONNECTION_ERROR", provider, cause);
|
|
19
|
+
this.name = "ConnectionError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
class AuthenticationError extends DirectoryError {
|
|
23
|
+
constructor(message, provider, cause) {
|
|
24
|
+
super(message, "AUTHENTICATION_ERROR", provider, cause);
|
|
25
|
+
this.name = "AuthenticationError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class NotFoundError extends DirectoryError {
|
|
29
|
+
resourceType;
|
|
30
|
+
resourceId;
|
|
31
|
+
constructor(resourceType, resourceId, provider, cause) {
|
|
32
|
+
super(
|
|
33
|
+
`${resourceType} not found: ${resourceId}`,
|
|
34
|
+
"NOT_FOUND",
|
|
35
|
+
provider,
|
|
36
|
+
cause
|
|
37
|
+
);
|
|
38
|
+
this.name = "NotFoundError";
|
|
39
|
+
this.resourceType = resourceType;
|
|
40
|
+
this.resourceId = resourceId;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
class ConflictError extends DirectoryError {
|
|
44
|
+
resourceType;
|
|
45
|
+
resourceId;
|
|
46
|
+
constructor(resourceType, resourceId, provider, cause) {
|
|
47
|
+
super(
|
|
48
|
+
`${resourceType} already exists: ${resourceId}`,
|
|
49
|
+
"CONFLICT",
|
|
50
|
+
provider,
|
|
51
|
+
cause
|
|
52
|
+
);
|
|
53
|
+
this.name = "ConflictError";
|
|
54
|
+
this.resourceType = resourceType;
|
|
55
|
+
this.resourceId = resourceId;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
class ValidationError extends DirectoryError {
|
|
59
|
+
constructor(message, provider, cause) {
|
|
60
|
+
super(message, "VALIDATION_ERROR", provider, cause);
|
|
61
|
+
this.name = "ValidationError";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class RateLimitError extends DirectoryError {
|
|
65
|
+
retryAfter;
|
|
66
|
+
constructor(provider, retryAfter) {
|
|
67
|
+
super(
|
|
68
|
+
`Rate limit exceeded${retryAfter ? ` (retry after ${retryAfter}s)` : ""}`,
|
|
69
|
+
"RATE_LIMIT",
|
|
70
|
+
provider
|
|
71
|
+
);
|
|
72
|
+
this.name = "RateLimitError";
|
|
73
|
+
this.retryAfter = retryAfter;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function handleAwsError(error, context) {
|
|
77
|
+
if (error instanceof AuthenticationError || error instanceof ConnectionError || error instanceof NotFoundError || error instanceof ConflictError || error instanceof DirectoryError) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
const awsError = error;
|
|
81
|
+
const name = awsError.name ?? "";
|
|
82
|
+
const message = awsError.message ?? String(error);
|
|
83
|
+
switch (name) {
|
|
84
|
+
case "EntityAlreadyExistsException":
|
|
85
|
+
case "DuplicateOrganizationalUnitException":
|
|
86
|
+
throw new ConflictError("resource", context, "aws", error);
|
|
87
|
+
case "NoSuchEntityException":
|
|
88
|
+
case "OrganizationalUnitNotFoundException":
|
|
89
|
+
case "AccountNotFoundException":
|
|
90
|
+
throw new NotFoundError("resource", context, "aws", error);
|
|
91
|
+
case "AccessDeniedException":
|
|
92
|
+
case "InvalidClientTokenId":
|
|
93
|
+
case "UnrecognizedClientException":
|
|
94
|
+
case "InvalidAccessKeyId":
|
|
95
|
+
throw new AuthenticationError(`${context}: ${message}`, "aws", error);
|
|
96
|
+
default:
|
|
97
|
+
throw new DirectoryError(
|
|
98
|
+
`${context}: ${message}`,
|
|
99
|
+
"AWS_ERROR",
|
|
100
|
+
"aws",
|
|
101
|
+
error
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function mapIamUserToDirectoryUser(user) {
|
|
106
|
+
const displayNameTag = user.Tags?.find((t) => t.Key === "DisplayName");
|
|
107
|
+
const emailTag = user.Tags?.find((t) => t.Key === "Email");
|
|
108
|
+
return {
|
|
109
|
+
id: user.UserName ?? "",
|
|
110
|
+
username: user.UserName ?? "",
|
|
111
|
+
displayName: displayNameTag?.Value,
|
|
112
|
+
email: emailTag?.Value,
|
|
113
|
+
active: true,
|
|
114
|
+
metadata: {
|
|
115
|
+
arn: user.Arn,
|
|
116
|
+
userId: user.UserId,
|
|
117
|
+
createDate: user.CreateDate?.toISOString()
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function mapIamGroupToDirectoryGroup(group) {
|
|
122
|
+
return {
|
|
123
|
+
id: group.GroupName ?? "",
|
|
124
|
+
name: group.GroupName ?? "",
|
|
125
|
+
metadata: {
|
|
126
|
+
arn: group.Arn,
|
|
127
|
+
groupId: group.GroupId,
|
|
128
|
+
createDate: group.CreateDate?.toISOString()
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function mapIamUserToAwsIamUser(user) {
|
|
133
|
+
return {
|
|
134
|
+
username: user.UserName ?? "",
|
|
135
|
+
arn: user.Arn,
|
|
136
|
+
userId: user.UserId,
|
|
137
|
+
createDate: user.CreateDate
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
class AwsAdapter {
|
|
141
|
+
constructor(options) {
|
|
142
|
+
this.options = options;
|
|
143
|
+
const clientConfig = {
|
|
144
|
+
region: options.region,
|
|
145
|
+
...options.credentials ? { credentials: options.credentials } : {}
|
|
146
|
+
};
|
|
147
|
+
this.orgs = new OrganizationsClient(clientConfig);
|
|
148
|
+
this.iam = new IAMClient(clientConfig);
|
|
149
|
+
}
|
|
150
|
+
orgs;
|
|
151
|
+
iam;
|
|
152
|
+
// ==========================================================================
|
|
153
|
+
// Connection
|
|
154
|
+
// ==========================================================================
|
|
155
|
+
async testConnection() {
|
|
156
|
+
try {
|
|
157
|
+
await this.iam.send(new ListUsersCommand({ MaxItems: 1 }));
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async disconnect() {
|
|
164
|
+
}
|
|
165
|
+
// ==========================================================================
|
|
166
|
+
// User CRUD (DirectoryAdapter -> IAM Users)
|
|
167
|
+
// ==========================================================================
|
|
168
|
+
async createUser(input) {
|
|
169
|
+
try {
|
|
170
|
+
const tags = [];
|
|
171
|
+
if (input.displayName) {
|
|
172
|
+
tags.push({ Key: "DisplayName", Value: input.displayName });
|
|
173
|
+
}
|
|
174
|
+
if (input.email) {
|
|
175
|
+
tags.push({ Key: "Email", Value: input.email });
|
|
176
|
+
}
|
|
177
|
+
const result = await this.iam.send(
|
|
178
|
+
new CreateUserCommand({
|
|
179
|
+
UserName: input.username,
|
|
180
|
+
...tags.length > 0 ? { Tags: tags } : {}
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
return mapIamUserToDirectoryUser({
|
|
184
|
+
...result.User,
|
|
185
|
+
Tags: tags
|
|
186
|
+
});
|
|
187
|
+
} catch (error) {
|
|
188
|
+
handleAwsError(error, `createUser(${input.username})`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async getUser(id) {
|
|
192
|
+
try {
|
|
193
|
+
const result = await this.iam.send(new GetUserCommand({ UserName: id }));
|
|
194
|
+
return mapIamUserToDirectoryUser(result.User ?? {});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
handleAwsError(error, `getUser(${id})`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async updateUser(id, _input) {
|
|
200
|
+
try {
|
|
201
|
+
await this.iam.send(new UpdateUserCommand({ UserName: id }));
|
|
202
|
+
return this.getUser(id);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
handleAwsError(error, `updateUser(${id})`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async deleteUser(id) {
|
|
208
|
+
try {
|
|
209
|
+
await this.iam.send(new DeleteUserCommand({ UserName: id }));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
handleAwsError(error, `deleteUser(${id})`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async listUsers() {
|
|
215
|
+
try {
|
|
216
|
+
const result = await this.iam.send(new ListUsersCommand({}));
|
|
217
|
+
return (result.Users ?? []).map((u) => mapIamUserToDirectoryUser(u));
|
|
218
|
+
} catch (error) {
|
|
219
|
+
handleAwsError(error, "listUsers");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// ==========================================================================
|
|
223
|
+
// Group CRUD (DirectoryAdapter -> IAM Groups)
|
|
224
|
+
// ==========================================================================
|
|
225
|
+
async createGroup(input) {
|
|
226
|
+
try {
|
|
227
|
+
const result = await this.iam.send(
|
|
228
|
+
new CreateGroupCommand({ GroupName: input.name })
|
|
229
|
+
);
|
|
230
|
+
const group = mapIamGroupToDirectoryGroup(result.Group ?? {});
|
|
231
|
+
if (input.members) {
|
|
232
|
+
for (const memberId of input.members) {
|
|
233
|
+
await this.addUserToGroup(memberId, input.name);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return group;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
handleAwsError(error, `createGroup(${input.name})`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async getGroup(id) {
|
|
242
|
+
try {
|
|
243
|
+
const result = await this.iam.send(
|
|
244
|
+
new GetGroupCommand({ GroupName: id })
|
|
245
|
+
);
|
|
246
|
+
const group = mapIamGroupToDirectoryGroup(result.Group ?? {});
|
|
247
|
+
group.members = (result.Users ?? []).map((u) => u.UserName ?? "");
|
|
248
|
+
return group;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
handleAwsError(error, `getGroup(${id})`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async updateGroup(id, _input) {
|
|
254
|
+
return this.getGroup(id);
|
|
255
|
+
}
|
|
256
|
+
async deleteGroup(id) {
|
|
257
|
+
try {
|
|
258
|
+
await this.iam.send(new DeleteGroupCommand({ GroupName: id }));
|
|
259
|
+
} catch (error) {
|
|
260
|
+
handleAwsError(error, `deleteGroup(${id})`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async listGroups() {
|
|
264
|
+
try {
|
|
265
|
+
const result = await this.iam.send(new ListGroupsCommand({}));
|
|
266
|
+
return (result.Groups ?? []).map((g) => mapIamGroupToDirectoryGroup(g));
|
|
267
|
+
} catch (error) {
|
|
268
|
+
handleAwsError(error, "listGroups");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// ==========================================================================
|
|
272
|
+
// Membership
|
|
273
|
+
// ==========================================================================
|
|
274
|
+
async addUserToGroup(userId, groupId) {
|
|
275
|
+
try {
|
|
276
|
+
await this.iam.send(
|
|
277
|
+
new AddUserToGroupCommand({
|
|
278
|
+
UserName: userId,
|
|
279
|
+
GroupName: groupId
|
|
280
|
+
})
|
|
281
|
+
);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
handleAwsError(error, `addUserToGroup(${userId}, ${groupId})`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async removeUserFromGroup(userId, groupId) {
|
|
287
|
+
try {
|
|
288
|
+
await this.iam.send(
|
|
289
|
+
new RemoveUserFromGroupCommand({
|
|
290
|
+
UserName: userId,
|
|
291
|
+
GroupName: groupId
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
handleAwsError(error, `removeUserFromGroup(${userId}, ${groupId})`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async getGroupMembers(groupId) {
|
|
299
|
+
try {
|
|
300
|
+
const result = await this.iam.send(
|
|
301
|
+
new GetGroupCommand({ GroupName: groupId })
|
|
302
|
+
);
|
|
303
|
+
return (result.Users ?? []).map((u) => mapIamUserToDirectoryUser(u));
|
|
304
|
+
} catch (error) {
|
|
305
|
+
handleAwsError(error, `getGroupMembers(${groupId})`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async getUserGroups(userId) {
|
|
309
|
+
try {
|
|
310
|
+
const result = await this.iam.send(
|
|
311
|
+
new ListGroupsForUserCommand({ UserName: userId })
|
|
312
|
+
);
|
|
313
|
+
return (result.Groups ?? []).map((g) => mapIamGroupToDirectoryGroup(g));
|
|
314
|
+
} catch (error) {
|
|
315
|
+
handleAwsError(error, `getUserGroups(${userId})`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// ==========================================================================
|
|
319
|
+
// Organizational Units (AwsDirectoryAdapter)
|
|
320
|
+
// ==========================================================================
|
|
321
|
+
async createOrganizationalUnit(input) {
|
|
322
|
+
try {
|
|
323
|
+
const result = await this.orgs.send(
|
|
324
|
+
new CreateOrganizationalUnitCommand({
|
|
325
|
+
ParentId: input.parentId,
|
|
326
|
+
Name: input.name
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
const ou = result.OrganizationalUnit;
|
|
330
|
+
return {
|
|
331
|
+
id: ou?.Id ?? "",
|
|
332
|
+
name: ou?.Name ?? "",
|
|
333
|
+
arn: ou?.Arn,
|
|
334
|
+
parentId: input.parentId
|
|
335
|
+
};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
handleAwsError(error, `createOrganizationalUnit(${input.name})`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async getOrganizationalUnit(id) {
|
|
341
|
+
try {
|
|
342
|
+
const result = await this.orgs.send(
|
|
343
|
+
new DescribeOrganizationalUnitCommand({
|
|
344
|
+
OrganizationalUnitId: id
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
const ou = result.OrganizationalUnit;
|
|
348
|
+
return {
|
|
349
|
+
id: ou?.Id ?? "",
|
|
350
|
+
name: ou?.Name ?? "",
|
|
351
|
+
arn: ou?.Arn
|
|
352
|
+
};
|
|
353
|
+
} catch (error) {
|
|
354
|
+
handleAwsError(error, `getOrganizationalUnit(${id})`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async listOrganizationalUnits(parentId) {
|
|
358
|
+
try {
|
|
359
|
+
const result = await this.orgs.send(
|
|
360
|
+
new ListOrganizationalUnitsForParentCommand({
|
|
361
|
+
ParentId: parentId
|
|
362
|
+
})
|
|
363
|
+
);
|
|
364
|
+
return (result.OrganizationalUnits ?? []).map((ou) => ({
|
|
365
|
+
id: ou.Id ?? "",
|
|
366
|
+
name: ou.Name ?? "",
|
|
367
|
+
arn: ou.Arn,
|
|
368
|
+
parentId
|
|
369
|
+
}));
|
|
370
|
+
} catch (error) {
|
|
371
|
+
handleAwsError(error, `listOrganizationalUnits(${parentId})`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// ==========================================================================
|
|
375
|
+
// Accounts (AwsDirectoryAdapter)
|
|
376
|
+
// ==========================================================================
|
|
377
|
+
async createAccount(input) {
|
|
378
|
+
try {
|
|
379
|
+
const result = await this.orgs.send(
|
|
380
|
+
new CreateAccountCommand({
|
|
381
|
+
AccountName: input.name,
|
|
382
|
+
Email: input.email,
|
|
383
|
+
...input.roleName ? { RoleName: input.roleName } : {}
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
const status = result.CreateAccountStatus;
|
|
387
|
+
return {
|
|
388
|
+
id: status?.Id ?? "",
|
|
389
|
+
accountId: status?.AccountId,
|
|
390
|
+
state: status?.State ?? "IN_PROGRESS",
|
|
391
|
+
failureReason: status?.FailureReason ? String(status.FailureReason) : void 0
|
|
392
|
+
};
|
|
393
|
+
} catch (error) {
|
|
394
|
+
handleAwsError(error, `createAccount(${input.name})`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async getAccountCreationStatus(id) {
|
|
398
|
+
try {
|
|
399
|
+
const result = await this.orgs.send(
|
|
400
|
+
new DescribeCreateAccountStatusCommand({
|
|
401
|
+
CreateAccountRequestId: id
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
const status = result.CreateAccountStatus;
|
|
405
|
+
return {
|
|
406
|
+
id: status?.Id ?? "",
|
|
407
|
+
accountId: status?.AccountId,
|
|
408
|
+
state: status?.State ?? "IN_PROGRESS",
|
|
409
|
+
failureReason: status?.FailureReason ? String(status.FailureReason) : void 0
|
|
410
|
+
};
|
|
411
|
+
} catch (error) {
|
|
412
|
+
handleAwsError(error, `getAccountCreationStatus(${id})`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async listAccounts() {
|
|
416
|
+
try {
|
|
417
|
+
const result = await this.orgs.send(new ListAccountsCommand({}));
|
|
418
|
+
return (result.Accounts ?? []).map((a) => ({
|
|
419
|
+
id: a.Id ?? "",
|
|
420
|
+
name: a.Name ?? "",
|
|
421
|
+
email: a.Email ?? "",
|
|
422
|
+
arn: a.Arn,
|
|
423
|
+
status: a.Status ? String(a.Status) : void 0
|
|
424
|
+
}));
|
|
425
|
+
} catch (error) {
|
|
426
|
+
handleAwsError(error, "listAccounts");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async moveAccount(accountId, sourceParentId, destParentId) {
|
|
430
|
+
try {
|
|
431
|
+
await this.orgs.send(
|
|
432
|
+
new MoveAccountCommand({
|
|
433
|
+
AccountId: accountId,
|
|
434
|
+
SourceParentId: sourceParentId,
|
|
435
|
+
DestinationParentId: destParentId
|
|
436
|
+
})
|
|
437
|
+
);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
handleAwsError(
|
|
440
|
+
error,
|
|
441
|
+
`moveAccount(${accountId}, ${sourceParentId} -> ${destParentId})`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// ==========================================================================
|
|
446
|
+
// IAM Users (AwsDirectoryAdapter)
|
|
447
|
+
// ==========================================================================
|
|
448
|
+
async createIamUser(input) {
|
|
449
|
+
try {
|
|
450
|
+
const result = await this.iam.send(
|
|
451
|
+
new CreateUserCommand({
|
|
452
|
+
UserName: input.username,
|
|
453
|
+
...input.path ? { Path: input.path } : {}
|
|
454
|
+
})
|
|
455
|
+
);
|
|
456
|
+
return mapIamUserToAwsIamUser(result.User ?? {});
|
|
457
|
+
} catch (error) {
|
|
458
|
+
handleAwsError(error, `createIamUser(${input.username})`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async getIamUser(username) {
|
|
462
|
+
try {
|
|
463
|
+
const result = await this.iam.send(
|
|
464
|
+
new GetUserCommand({ UserName: username })
|
|
465
|
+
);
|
|
466
|
+
return mapIamUserToAwsIamUser(result.User ?? {});
|
|
467
|
+
} catch (error) {
|
|
468
|
+
handleAwsError(error, `getIamUser(${username})`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async deleteIamUser(username) {
|
|
472
|
+
try {
|
|
473
|
+
await this.iam.send(new DeleteUserCommand({ UserName: username }));
|
|
474
|
+
} catch (error) {
|
|
475
|
+
handleAwsError(error, `deleteIamUser(${username})`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async listIamUsers() {
|
|
479
|
+
try {
|
|
480
|
+
const result = await this.iam.send(new ListUsersCommand({}));
|
|
481
|
+
return (result.Users ?? []).map((u) => mapIamUserToAwsIamUser(u));
|
|
482
|
+
} catch (error) {
|
|
483
|
+
handleAwsError(error, "listIamUsers");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// ==========================================================================
|
|
487
|
+
// IAM Policies (AwsDirectoryAdapter)
|
|
488
|
+
// ==========================================================================
|
|
489
|
+
async attachUserPolicy(username, policyArn) {
|
|
490
|
+
try {
|
|
491
|
+
await this.iam.send(
|
|
492
|
+
new AttachUserPolicyCommand({
|
|
493
|
+
UserName: username,
|
|
494
|
+
PolicyArn: policyArn
|
|
495
|
+
})
|
|
496
|
+
);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
handleAwsError(error, `attachUserPolicy(${username}, ${policyArn})`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async detachUserPolicy(username, policyArn) {
|
|
502
|
+
try {
|
|
503
|
+
await this.iam.send(
|
|
504
|
+
new DetachUserPolicyCommand({
|
|
505
|
+
UserName: username,
|
|
506
|
+
PolicyArn: policyArn
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
handleAwsError(error, `detachUserPolicy(${username}, ${policyArn})`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// ==========================================================================
|
|
514
|
+
// Access Keys (AwsDirectoryAdapter)
|
|
515
|
+
// ==========================================================================
|
|
516
|
+
async createAccessKey(username) {
|
|
517
|
+
try {
|
|
518
|
+
const result = await this.iam.send(
|
|
519
|
+
new CreateAccessKeyCommand({ UserName: username })
|
|
520
|
+
);
|
|
521
|
+
const key = result.AccessKey;
|
|
522
|
+
return {
|
|
523
|
+
accessKeyId: key?.AccessKeyId ?? "",
|
|
524
|
+
secretAccessKey: key?.SecretAccessKey ?? "",
|
|
525
|
+
username: key?.UserName ?? username,
|
|
526
|
+
createDate: key?.CreateDate
|
|
527
|
+
};
|
|
528
|
+
} catch (error) {
|
|
529
|
+
handleAwsError(error, `createAccessKey(${username})`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async deleteAccessKey(username, accessKeyId) {
|
|
533
|
+
try {
|
|
534
|
+
await this.iam.send(
|
|
535
|
+
new DeleteAccessKeyCommand({
|
|
536
|
+
UserName: username,
|
|
537
|
+
AccessKeyId: accessKeyId
|
|
538
|
+
})
|
|
539
|
+
);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
handleAwsError(error, `deleteAccessKey(${username}, ${accessKeyId})`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const aws = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
546
|
+
__proto__: null,
|
|
547
|
+
AwsAdapter
|
|
548
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
549
|
+
class KanidmAdapter {
|
|
550
|
+
constructor(options) {
|
|
551
|
+
this.options = options;
|
|
552
|
+
if (!options.apiToken && (!options.adminUsername || !options.adminPassword)) {
|
|
553
|
+
throw new ValidationError(
|
|
554
|
+
"KanidmAdapter requires either apiToken or both adminUsername and adminPassword",
|
|
555
|
+
"kanidm"
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
559
|
+
this.timeout = options.timeout ?? 3e4;
|
|
560
|
+
}
|
|
561
|
+
baseUrl;
|
|
562
|
+
timeout;
|
|
563
|
+
adminToken = null;
|
|
564
|
+
adminTokenExpiry = 0;
|
|
565
|
+
// ==========================================================================
|
|
566
|
+
// Authentication
|
|
567
|
+
// ==========================================================================
|
|
568
|
+
async getAdminToken() {
|
|
569
|
+
if (this.options.apiToken) {
|
|
570
|
+
return this.options.apiToken;
|
|
571
|
+
}
|
|
572
|
+
if (this.adminToken && Date.now() < this.adminTokenExpiry) {
|
|
573
|
+
return this.adminToken;
|
|
574
|
+
}
|
|
575
|
+
const authUrl = `${this.baseUrl}/v1/auth`;
|
|
576
|
+
const initResponse = await fetch(authUrl, {
|
|
577
|
+
method: "POST",
|
|
578
|
+
headers: { "Content-Type": "application/json" },
|
|
579
|
+
body: JSON.stringify({
|
|
580
|
+
step: {
|
|
581
|
+
init2: {
|
|
582
|
+
username: this.options.adminUsername,
|
|
583
|
+
issue: "token",
|
|
584
|
+
privileged: true
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}),
|
|
588
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
589
|
+
});
|
|
590
|
+
if (!initResponse.ok) {
|
|
591
|
+
throw new AuthenticationError(
|
|
592
|
+
"Failed to initialize admin authentication",
|
|
593
|
+
"kanidm"
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
const cookies = initResponse.headers.get("set-cookie");
|
|
597
|
+
const beginResponse = await fetch(authUrl, {
|
|
598
|
+
method: "POST",
|
|
599
|
+
headers: {
|
|
600
|
+
"Content-Type": "application/json",
|
|
601
|
+
...cookies ? { Cookie: cookies } : {}
|
|
602
|
+
},
|
|
603
|
+
body: JSON.stringify({ step: { begin: "password" } }),
|
|
604
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
605
|
+
});
|
|
606
|
+
if (!beginResponse.ok) {
|
|
607
|
+
throw new AuthenticationError(
|
|
608
|
+
"Failed to begin password authentication",
|
|
609
|
+
"kanidm"
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const credResponse = await fetch(authUrl, {
|
|
613
|
+
method: "POST",
|
|
614
|
+
headers: {
|
|
615
|
+
"Content-Type": "application/json",
|
|
616
|
+
...cookies ? { Cookie: cookies } : {}
|
|
617
|
+
},
|
|
618
|
+
body: JSON.stringify({
|
|
619
|
+
step: { cred: { password: this.options.adminPassword } }
|
|
620
|
+
}),
|
|
621
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
622
|
+
});
|
|
623
|
+
if (!credResponse.ok) {
|
|
624
|
+
throw new AuthenticationError("Invalid admin credentials", "kanidm");
|
|
625
|
+
}
|
|
626
|
+
const result = await credResponse.json();
|
|
627
|
+
const token = result.state?.success || result.token;
|
|
628
|
+
if (!token) {
|
|
629
|
+
throw new AuthenticationError("Failed to obtain admin token", "kanidm");
|
|
630
|
+
}
|
|
631
|
+
this.adminToken = token;
|
|
632
|
+
this.adminTokenExpiry = Date.now() + 36e5;
|
|
633
|
+
return this.adminToken;
|
|
634
|
+
}
|
|
635
|
+
// ==========================================================================
|
|
636
|
+
// HTTP Helper
|
|
637
|
+
// ==========================================================================
|
|
638
|
+
async request(method, path, body) {
|
|
639
|
+
const token = await this.getAdminToken();
|
|
640
|
+
const url = `${this.baseUrl}${path}`;
|
|
641
|
+
let response;
|
|
642
|
+
try {
|
|
643
|
+
response = await fetch(url, {
|
|
644
|
+
method,
|
|
645
|
+
headers: {
|
|
646
|
+
"Content-Type": "application/json",
|
|
647
|
+
Authorization: `Bearer ${token}`
|
|
648
|
+
},
|
|
649
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
650
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
651
|
+
});
|
|
652
|
+
} catch (error) {
|
|
653
|
+
if (error instanceof TypeError) {
|
|
654
|
+
throw new ConnectionError(
|
|
655
|
+
`Failed to connect to Kanidm at ${this.baseUrl}`,
|
|
656
|
+
"kanidm",
|
|
657
|
+
error
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
662
|
+
if (response.ok) {
|
|
663
|
+
const text = await response.text();
|
|
664
|
+
if (!text) return void 0;
|
|
665
|
+
return JSON.parse(text);
|
|
666
|
+
}
|
|
667
|
+
const errorBody = await response.text().catch(() => "");
|
|
668
|
+
switch (response.status) {
|
|
669
|
+
case 401:
|
|
670
|
+
case 403:
|
|
671
|
+
this.adminToken = null;
|
|
672
|
+
this.adminTokenExpiry = 0;
|
|
673
|
+
throw new AuthenticationError(
|
|
674
|
+
errorBody || `Authentication failed: ${response.status}`,
|
|
675
|
+
"kanidm"
|
|
676
|
+
);
|
|
677
|
+
case 404:
|
|
678
|
+
throw new NotFoundError("resource", path, "kanidm");
|
|
679
|
+
case 409:
|
|
680
|
+
throw new ConflictError("resource", path, "kanidm");
|
|
681
|
+
default:
|
|
682
|
+
throw new DirectoryError(
|
|
683
|
+
errorBody || `Request failed: ${response.status} ${response.statusText}`,
|
|
684
|
+
"REQUEST_ERROR",
|
|
685
|
+
"kanidm"
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// ==========================================================================
|
|
690
|
+
// Mapping Helpers
|
|
691
|
+
// ==========================================================================
|
|
692
|
+
mapPersonToUser(data) {
|
|
693
|
+
const attrs = data.attrs;
|
|
694
|
+
return {
|
|
695
|
+
id: this.attrFirst(attrs, "uuid") ?? this.attrFirst(attrs, "name") ?? "",
|
|
696
|
+
username: this.attrFirst(attrs, "name") ?? "",
|
|
697
|
+
displayName: this.attrFirst(attrs, "displayname"),
|
|
698
|
+
email: this.attrFirst(attrs, "mail"),
|
|
699
|
+
active: this.attrFirst(attrs, "class") ? !attrs.class?.includes("recycled") : true,
|
|
700
|
+
groups: attrs.memberof,
|
|
701
|
+
metadata: { attrs }
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
mapGroupToDirectoryGroup(data) {
|
|
705
|
+
const attrs = data.attrs;
|
|
706
|
+
return {
|
|
707
|
+
id: this.attrFirst(attrs, "uuid") ?? this.attrFirst(attrs, "name") ?? "",
|
|
708
|
+
name: this.attrFirst(attrs, "name") ?? "",
|
|
709
|
+
displayName: this.attrFirst(attrs, "displayname"),
|
|
710
|
+
description: this.attrFirst(attrs, "description"),
|
|
711
|
+
members: attrs.member,
|
|
712
|
+
metadata: { attrs }
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
mapOAuth2Client(data) {
|
|
716
|
+
const attrs = data.attrs;
|
|
717
|
+
return {
|
|
718
|
+
id: this.attrFirst(attrs, "uuid") ?? this.attrFirst(attrs, "oauth2_rs_name") ?? "",
|
|
719
|
+
name: this.attrFirst(attrs, "oauth2_rs_name") ?? "",
|
|
720
|
+
displayName: this.attrFirst(attrs, "displayname"),
|
|
721
|
+
redirectUris: attrs.oauth2_rs_origin ?? [],
|
|
722
|
+
scopes: attrs.oauth2_rs_scope_map ? attrs.oauth2_rs_scope_map : void 0,
|
|
723
|
+
metadata: { attrs }
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
attrFirst(attrs, key) {
|
|
727
|
+
const values = attrs[key];
|
|
728
|
+
return values?.[0];
|
|
729
|
+
}
|
|
730
|
+
// ==========================================================================
|
|
731
|
+
// Connection
|
|
732
|
+
// ==========================================================================
|
|
733
|
+
async testConnection() {
|
|
734
|
+
try {
|
|
735
|
+
await this.getAdminToken();
|
|
736
|
+
return true;
|
|
737
|
+
} catch {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async disconnect() {
|
|
742
|
+
this.adminToken = null;
|
|
743
|
+
this.adminTokenExpiry = 0;
|
|
744
|
+
}
|
|
745
|
+
// ==========================================================================
|
|
746
|
+
// User CRUD
|
|
747
|
+
// ==========================================================================
|
|
748
|
+
async createUser(input) {
|
|
749
|
+
const body = {
|
|
750
|
+
attrs: {
|
|
751
|
+
name: [input.username],
|
|
752
|
+
...input.displayName ? { displayname: [input.displayName] } : {},
|
|
753
|
+
...input.email ? { mail: [input.email] } : {},
|
|
754
|
+
...input.metadata ?? {}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
await this.request("POST", "/v1/person", body);
|
|
758
|
+
return this.getUser(input.username);
|
|
759
|
+
}
|
|
760
|
+
async getUser(id) {
|
|
761
|
+
const data = await this.request(
|
|
762
|
+
"GET",
|
|
763
|
+
`/v1/person/${encodeURIComponent(id)}`
|
|
764
|
+
);
|
|
765
|
+
return this.mapPersonToUser(data);
|
|
766
|
+
}
|
|
767
|
+
async updateUser(id, input) {
|
|
768
|
+
const attrs = {};
|
|
769
|
+
if (input.displayName !== void 0) {
|
|
770
|
+
attrs.displayname = [input.displayName];
|
|
771
|
+
}
|
|
772
|
+
if (input.email !== void 0) {
|
|
773
|
+
attrs.mail = [input.email];
|
|
774
|
+
}
|
|
775
|
+
await this.request("PATCH", `/v1/person/${encodeURIComponent(id)}`, {
|
|
776
|
+
attrs
|
|
777
|
+
});
|
|
778
|
+
return this.getUser(id);
|
|
779
|
+
}
|
|
780
|
+
async deleteUser(id) {
|
|
781
|
+
await this.request("DELETE", `/v1/person/${encodeURIComponent(id)}`);
|
|
782
|
+
}
|
|
783
|
+
async listUsers() {
|
|
784
|
+
const data = await this.request("GET", "/v1/person");
|
|
785
|
+
return (data ?? []).map((entry) => this.mapPersonToUser(entry));
|
|
786
|
+
}
|
|
787
|
+
// ==========================================================================
|
|
788
|
+
// Group CRUD
|
|
789
|
+
// ==========================================================================
|
|
790
|
+
async createGroup(input) {
|
|
791
|
+
const body = {
|
|
792
|
+
attrs: {
|
|
793
|
+
name: [input.name],
|
|
794
|
+
...input.displayName ? { displayname: [input.displayName] } : {},
|
|
795
|
+
...input.description ? { description: [input.description] } : {},
|
|
796
|
+
...input.members ? { member: input.members } : {},
|
|
797
|
+
...input.metadata ?? {}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
await this.request("POST", "/v1/group", body);
|
|
801
|
+
return this.getGroup(input.name);
|
|
802
|
+
}
|
|
803
|
+
async getGroup(id) {
|
|
804
|
+
const data = await this.request(
|
|
805
|
+
"GET",
|
|
806
|
+
`/v1/group/${encodeURIComponent(id)}`
|
|
807
|
+
);
|
|
808
|
+
return this.mapGroupToDirectoryGroup(data);
|
|
809
|
+
}
|
|
810
|
+
async updateGroup(id, input) {
|
|
811
|
+
const attrs = {};
|
|
812
|
+
if (input.displayName !== void 0) {
|
|
813
|
+
attrs.displayname = [input.displayName];
|
|
814
|
+
}
|
|
815
|
+
if (input.description !== void 0) {
|
|
816
|
+
attrs.description = [input.description];
|
|
817
|
+
}
|
|
818
|
+
await this.request("PATCH", `/v1/group/${encodeURIComponent(id)}`, {
|
|
819
|
+
attrs
|
|
820
|
+
});
|
|
821
|
+
return this.getGroup(id);
|
|
822
|
+
}
|
|
823
|
+
async deleteGroup(id) {
|
|
824
|
+
await this.request("DELETE", `/v1/group/${encodeURIComponent(id)}`);
|
|
825
|
+
}
|
|
826
|
+
async listGroups() {
|
|
827
|
+
const data = await this.request("GET", "/v1/group");
|
|
828
|
+
return (data ?? []).map((entry) => this.mapGroupToDirectoryGroup(entry));
|
|
829
|
+
}
|
|
830
|
+
// ==========================================================================
|
|
831
|
+
// Membership
|
|
832
|
+
// ==========================================================================
|
|
833
|
+
async addUserToGroup(userId, groupId) {
|
|
834
|
+
await this.request(
|
|
835
|
+
"POST",
|
|
836
|
+
`/v1/group/${encodeURIComponent(groupId)}/_attr/member`,
|
|
837
|
+
[userId]
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
async removeUserFromGroup(userId, groupId) {
|
|
841
|
+
await this.request(
|
|
842
|
+
"DELETE",
|
|
843
|
+
`/v1/group/${encodeURIComponent(groupId)}/_attr/member`,
|
|
844
|
+
[userId]
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
async getGroupMembers(groupId) {
|
|
848
|
+
const group = await this.getGroup(groupId);
|
|
849
|
+
if (!group.members || group.members.length === 0) {
|
|
850
|
+
return [];
|
|
851
|
+
}
|
|
852
|
+
const users = [];
|
|
853
|
+
for (const memberId of group.members) {
|
|
854
|
+
try {
|
|
855
|
+
const user = await this.getUser(memberId);
|
|
856
|
+
users.push(user);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
if (!(error instanceof NotFoundError)) {
|
|
859
|
+
throw error;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return users;
|
|
864
|
+
}
|
|
865
|
+
async getUserGroups(userId) {
|
|
866
|
+
const user = await this.getUser(userId);
|
|
867
|
+
if (!user.groups || user.groups.length === 0) {
|
|
868
|
+
return [];
|
|
869
|
+
}
|
|
870
|
+
const groups = [];
|
|
871
|
+
for (const groupId of user.groups) {
|
|
872
|
+
try {
|
|
873
|
+
const group = await this.getGroup(groupId);
|
|
874
|
+
groups.push(group);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
if (!(error instanceof NotFoundError)) {
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return groups;
|
|
882
|
+
}
|
|
883
|
+
// ==========================================================================
|
|
884
|
+
// OAuth2 Client CRUD
|
|
885
|
+
// ==========================================================================
|
|
886
|
+
async createOAuth2Client(input) {
|
|
887
|
+
const body = {
|
|
888
|
+
attrs: {
|
|
889
|
+
oauth2_rs_name: [input.name],
|
|
890
|
+
...input.displayName ? { displayname: [input.displayName] } : {},
|
|
891
|
+
oauth2_rs_origin: input.redirectUris,
|
|
892
|
+
...input.scopes ? { oauth2_rs_scope_map: input.scopes } : {}
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
await this.request("POST", "/v1/oauth2", body);
|
|
896
|
+
return this.getOAuth2Client(input.name);
|
|
897
|
+
}
|
|
898
|
+
async getOAuth2Client(id) {
|
|
899
|
+
const data = await this.request(
|
|
900
|
+
"GET",
|
|
901
|
+
`/v1/oauth2/${encodeURIComponent(id)}`
|
|
902
|
+
);
|
|
903
|
+
return this.mapOAuth2Client(data);
|
|
904
|
+
}
|
|
905
|
+
async updateOAuth2Client(id, input) {
|
|
906
|
+
const attrs = {};
|
|
907
|
+
if (input.displayName !== void 0) {
|
|
908
|
+
attrs.displayname = [input.displayName];
|
|
909
|
+
}
|
|
910
|
+
if (input.redirectUris !== void 0) {
|
|
911
|
+
attrs.oauth2_rs_origin = input.redirectUris;
|
|
912
|
+
}
|
|
913
|
+
if (input.scopes !== void 0) {
|
|
914
|
+
attrs.oauth2_rs_scope_map = input.scopes;
|
|
915
|
+
}
|
|
916
|
+
await this.request("PATCH", `/v1/oauth2/${encodeURIComponent(id)}`, {
|
|
917
|
+
attrs
|
|
918
|
+
});
|
|
919
|
+
return this.getOAuth2Client(id);
|
|
920
|
+
}
|
|
921
|
+
async deleteOAuth2Client(id) {
|
|
922
|
+
await this.request("DELETE", `/v1/oauth2/${encodeURIComponent(id)}`);
|
|
923
|
+
}
|
|
924
|
+
async listOAuth2Clients() {
|
|
925
|
+
const data = await this.request("GET", "/v1/oauth2");
|
|
926
|
+
return (data ?? []).map((entry) => this.mapOAuth2Client(entry));
|
|
927
|
+
}
|
|
928
|
+
// ==========================================================================
|
|
929
|
+
// OAuth2 Secret Management
|
|
930
|
+
// ==========================================================================
|
|
931
|
+
async getOAuth2ClientSecret(id) {
|
|
932
|
+
const data = await this.request(
|
|
933
|
+
"GET",
|
|
934
|
+
`/v1/oauth2/${encodeURIComponent(id)}/_basic_secret`
|
|
935
|
+
);
|
|
936
|
+
return data;
|
|
937
|
+
}
|
|
938
|
+
// ==========================================================================
|
|
939
|
+
// Credential Management
|
|
940
|
+
// ==========================================================================
|
|
941
|
+
async createCredentialResetIntent(userId, options) {
|
|
942
|
+
const ttl = options?.ttl ?? 3600;
|
|
943
|
+
const token = await this.request(
|
|
944
|
+
"GET",
|
|
945
|
+
`/v1/person/${encodeURIComponent(userId)}/_credential/_update_intent/${ttl}`
|
|
946
|
+
);
|
|
947
|
+
return {
|
|
948
|
+
token,
|
|
949
|
+
url: `${this.baseUrl}/ui/reset?token=${encodeURIComponent(token)}`,
|
|
950
|
+
ttl
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const kanidm = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
955
|
+
__proto__: null,
|
|
956
|
+
KanidmAdapter
|
|
957
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
958
|
+
const { Client } = pg;
|
|
959
|
+
const PROVIDER$1 = "postgres";
|
|
960
|
+
const SYSTEM_ROLE_PREFIX = "pg_";
|
|
961
|
+
const SYSTEM_ROLES = /* @__PURE__ */ new Set(["postgres"]);
|
|
962
|
+
const SYSTEM_DATABASES = /* @__PURE__ */ new Set(["template0", "template1", "postgres"]);
|
|
963
|
+
class PostgresAdapter {
|
|
964
|
+
options;
|
|
965
|
+
client = null;
|
|
966
|
+
constructor(options) {
|
|
967
|
+
this.options = options;
|
|
968
|
+
}
|
|
969
|
+
// ==========================================================================
|
|
970
|
+
// Private Helpers
|
|
971
|
+
// ==========================================================================
|
|
972
|
+
/**
|
|
973
|
+
* Escape a SQL identifier by wrapping in double quotes and escaping
|
|
974
|
+
* any embedded double quotes. Used for DDL statements where
|
|
975
|
+
* parameterized queries are not supported.
|
|
976
|
+
*/
|
|
977
|
+
escapeIdentifier(name) {
|
|
978
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Escape a SQL string literal by wrapping in single quotes and escaping
|
|
982
|
+
* any embedded single quotes. Used for DDL statements (e.g., passwords)
|
|
983
|
+
* where parameterized queries are not supported.
|
|
984
|
+
*/
|
|
985
|
+
escapeLiteral(value) {
|
|
986
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Get or create the pg.Client instance. Connects lazily on first use.
|
|
990
|
+
*/
|
|
991
|
+
async getClient() {
|
|
992
|
+
if (this.client) {
|
|
993
|
+
return this.client;
|
|
994
|
+
}
|
|
995
|
+
try {
|
|
996
|
+
this.client = new Client({
|
|
997
|
+
host: this.options.host,
|
|
998
|
+
port: this.options.port ?? 5432,
|
|
999
|
+
user: this.options.adminUser,
|
|
1000
|
+
password: this.options.adminPassword,
|
|
1001
|
+
database: this.options.database ?? "postgres",
|
|
1002
|
+
ssl: this.options.ssl ? typeof this.options.ssl === "object" ? this.options.ssl : { rejectUnauthorized: false } : void 0
|
|
1003
|
+
});
|
|
1004
|
+
await this.client.connect();
|
|
1005
|
+
return this.client;
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
this.client = null;
|
|
1008
|
+
throw this.mapError(error);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Check if a role name is a system role that should be excluded from lists.
|
|
1013
|
+
*/
|
|
1014
|
+
isSystemRole(rolname) {
|
|
1015
|
+
return rolname.startsWith(SYSTEM_ROLE_PREFIX) || SYSTEM_ROLES.has(rolname);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Map a pg_roles row to a DirectoryUser.
|
|
1019
|
+
*/
|
|
1020
|
+
rowToUser(row) {
|
|
1021
|
+
return {
|
|
1022
|
+
id: row.rolname,
|
|
1023
|
+
username: row.rolname,
|
|
1024
|
+
active: true,
|
|
1025
|
+
metadata: {
|
|
1026
|
+
superuser: row.rolsuper,
|
|
1027
|
+
createDb: row.rolcreatedb,
|
|
1028
|
+
createRole: row.rolcreaterole
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Map a pg_roles row to a DirectoryGroup.
|
|
1034
|
+
*/
|
|
1035
|
+
rowToGroup(row) {
|
|
1036
|
+
return {
|
|
1037
|
+
id: row.rolname,
|
|
1038
|
+
name: row.rolname,
|
|
1039
|
+
metadata: {
|
|
1040
|
+
superuser: row.rolsuper,
|
|
1041
|
+
createDb: row.rolcreatedb,
|
|
1042
|
+
createRole: row.rolcreaterole
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Map a pg_roles row to a PgRole.
|
|
1048
|
+
*/
|
|
1049
|
+
rowToPgRole(row) {
|
|
1050
|
+
return {
|
|
1051
|
+
name: row.rolname,
|
|
1052
|
+
login: row.rolcanlogin,
|
|
1053
|
+
superuser: row.rolsuper,
|
|
1054
|
+
createDb: row.rolcreatedb,
|
|
1055
|
+
createRole: row.rolcreaterole
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Map a pg_database row to a PgDatabase.
|
|
1060
|
+
*/
|
|
1061
|
+
rowToDatabase(row) {
|
|
1062
|
+
return {
|
|
1063
|
+
name: row.datname,
|
|
1064
|
+
owner: row.owner,
|
|
1065
|
+
encoding: row.encoding,
|
|
1066
|
+
size: row.size
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Map PostgreSQL errors to directory error classes.
|
|
1071
|
+
*/
|
|
1072
|
+
mapError(error) {
|
|
1073
|
+
if (error instanceof DirectoryError) {
|
|
1074
|
+
return error;
|
|
1075
|
+
}
|
|
1076
|
+
const pgError = error;
|
|
1077
|
+
switch (pgError.code) {
|
|
1078
|
+
case "42710":
|
|
1079
|
+
return new ConflictError(
|
|
1080
|
+
"role",
|
|
1081
|
+
pgError.message ?? "unknown",
|
|
1082
|
+
PROVIDER$1,
|
|
1083
|
+
error
|
|
1084
|
+
);
|
|
1085
|
+
case "42704":
|
|
1086
|
+
return new NotFoundError(
|
|
1087
|
+
"role",
|
|
1088
|
+
pgError.message ?? "unknown",
|
|
1089
|
+
PROVIDER$1,
|
|
1090
|
+
error
|
|
1091
|
+
);
|
|
1092
|
+
case "28P01":
|
|
1093
|
+
return new AuthenticationError(
|
|
1094
|
+
pgError.message ?? "Invalid password",
|
|
1095
|
+
PROVIDER$1,
|
|
1096
|
+
error
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const message = pgError.message ?? (error instanceof Error ? error.message : String(error));
|
|
1100
|
+
if (message.includes("ECONNREFUSED") || message.includes("connect ETIMEDOUT") || message.includes("getaddrinfo")) {
|
|
1101
|
+
return new ConnectionError(
|
|
1102
|
+
`Failed to connect to PostgreSQL: ${message}`,
|
|
1103
|
+
PROVIDER$1,
|
|
1104
|
+
error
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
return new DirectoryError(message, "UNKNOWN_ERROR", PROVIDER$1, error);
|
|
1108
|
+
}
|
|
1109
|
+
// ==========================================================================
|
|
1110
|
+
// Connection
|
|
1111
|
+
// ==========================================================================
|
|
1112
|
+
async testConnection() {
|
|
1113
|
+
try {
|
|
1114
|
+
const client = await this.getClient();
|
|
1115
|
+
await client.query("SELECT 1");
|
|
1116
|
+
return true;
|
|
1117
|
+
} catch {
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
async disconnect() {
|
|
1122
|
+
if (this.client) {
|
|
1123
|
+
await this.client.end();
|
|
1124
|
+
this.client = null;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
// ==========================================================================
|
|
1128
|
+
// User CRUD (PostgreSQL roles WITH LOGIN)
|
|
1129
|
+
// ==========================================================================
|
|
1130
|
+
async createUser(input) {
|
|
1131
|
+
const client = await this.getClient();
|
|
1132
|
+
try {
|
|
1133
|
+
let sql = `CREATE ROLE ${this.escapeIdentifier(input.username)} WITH LOGIN`;
|
|
1134
|
+
if (input.password) {
|
|
1135
|
+
sql += ` PASSWORD ${this.escapeLiteral(input.password)}`;
|
|
1136
|
+
}
|
|
1137
|
+
await client.query(sql);
|
|
1138
|
+
return this.getUser(input.username);
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
throw this.mapError(error);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async getUser(id) {
|
|
1144
|
+
const client = await this.getClient();
|
|
1145
|
+
try {
|
|
1146
|
+
const result = await client.query(
|
|
1147
|
+
`SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin
|
|
1148
|
+
FROM pg_roles
|
|
1149
|
+
WHERE rolname = $1 AND rolcanlogin = true`,
|
|
1150
|
+
[id]
|
|
1151
|
+
);
|
|
1152
|
+
if (result.rows.length === 0) {
|
|
1153
|
+
throw new NotFoundError("user", id, PROVIDER$1);
|
|
1154
|
+
}
|
|
1155
|
+
return this.rowToUser(result.rows[0]);
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
throw this.mapError(error);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
async updateUser(id, input) {
|
|
1161
|
+
const client = await this.getClient();
|
|
1162
|
+
try {
|
|
1163
|
+
await this.getUser(id);
|
|
1164
|
+
const alterClauses = [];
|
|
1165
|
+
if (input.password !== void 0) {
|
|
1166
|
+
alterClauses.push(`PASSWORD ${this.escapeLiteral(input.password)}`);
|
|
1167
|
+
}
|
|
1168
|
+
if (alterClauses.length > 0) {
|
|
1169
|
+
const sql = `ALTER ROLE ${this.escapeIdentifier(id)} WITH ${alterClauses.join(" ")}`;
|
|
1170
|
+
await client.query(sql);
|
|
1171
|
+
}
|
|
1172
|
+
return this.getUser(id);
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
throw this.mapError(error);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async deleteUser(id) {
|
|
1178
|
+
const client = await this.getClient();
|
|
1179
|
+
try {
|
|
1180
|
+
await this.getUser(id);
|
|
1181
|
+
await client.query(`DROP ROLE ${this.escapeIdentifier(id)}`);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
throw this.mapError(error);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async listUsers() {
|
|
1187
|
+
const client = await this.getClient();
|
|
1188
|
+
try {
|
|
1189
|
+
const result = await client.query(
|
|
1190
|
+
`SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin
|
|
1191
|
+
FROM pg_roles
|
|
1192
|
+
WHERE rolcanlogin = true
|
|
1193
|
+
ORDER BY rolname`
|
|
1194
|
+
);
|
|
1195
|
+
return result.rows.filter(
|
|
1196
|
+
(row) => !this.isSystemRole(row.rolname)
|
|
1197
|
+
).map((row) => this.rowToUser(row));
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
throw this.mapError(error);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
// ==========================================================================
|
|
1203
|
+
// Group CRUD (PostgreSQL roles WITHOUT LOGIN)
|
|
1204
|
+
// ==========================================================================
|
|
1205
|
+
async createGroup(input) {
|
|
1206
|
+
const client = await this.getClient();
|
|
1207
|
+
try {
|
|
1208
|
+
await client.query(
|
|
1209
|
+
`CREATE ROLE ${this.escapeIdentifier(input.name)} NOLOGIN`
|
|
1210
|
+
);
|
|
1211
|
+
if (input.members) {
|
|
1212
|
+
for (const member of input.members) {
|
|
1213
|
+
await this.addUserToGroup(member, input.name);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return this.getGroup(input.name);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
throw this.mapError(error);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
async getGroup(id) {
|
|
1222
|
+
const client = await this.getClient();
|
|
1223
|
+
try {
|
|
1224
|
+
const result = await client.query(
|
|
1225
|
+
`SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin
|
|
1226
|
+
FROM pg_roles
|
|
1227
|
+
WHERE rolname = $1 AND rolcanlogin = false`,
|
|
1228
|
+
[id]
|
|
1229
|
+
);
|
|
1230
|
+
if (result.rows.length === 0) {
|
|
1231
|
+
throw new NotFoundError("group", id, PROVIDER$1);
|
|
1232
|
+
}
|
|
1233
|
+
return this.rowToGroup(result.rows[0]);
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
throw this.mapError(error);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
async updateGroup(id, _input) {
|
|
1239
|
+
return this.getGroup(id);
|
|
1240
|
+
}
|
|
1241
|
+
async deleteGroup(id) {
|
|
1242
|
+
const client = await this.getClient();
|
|
1243
|
+
try {
|
|
1244
|
+
await this.getGroup(id);
|
|
1245
|
+
await client.query(`DROP ROLE ${this.escapeIdentifier(id)}`);
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
throw this.mapError(error);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async listGroups() {
|
|
1251
|
+
const client = await this.getClient();
|
|
1252
|
+
try {
|
|
1253
|
+
const result = await client.query(
|
|
1254
|
+
`SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin
|
|
1255
|
+
FROM pg_roles
|
|
1256
|
+
WHERE rolcanlogin = false
|
|
1257
|
+
ORDER BY rolname`
|
|
1258
|
+
);
|
|
1259
|
+
return result.rows.filter(
|
|
1260
|
+
(row) => !this.isSystemRole(row.rolname)
|
|
1261
|
+
).map((row) => this.rowToGroup(row));
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
throw this.mapError(error);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
// ==========================================================================
|
|
1267
|
+
// Membership (GRANT/REVOKE role TO/FROM role)
|
|
1268
|
+
// ==========================================================================
|
|
1269
|
+
async addUserToGroup(userId, groupId) {
|
|
1270
|
+
const client = await this.getClient();
|
|
1271
|
+
try {
|
|
1272
|
+
await client.query(
|
|
1273
|
+
`GRANT ${this.escapeIdentifier(groupId)} TO ${this.escapeIdentifier(userId)}`
|
|
1274
|
+
);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
throw this.mapError(error);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async removeUserFromGroup(userId, groupId) {
|
|
1280
|
+
const client = await this.getClient();
|
|
1281
|
+
try {
|
|
1282
|
+
await client.query(
|
|
1283
|
+
`REVOKE ${this.escapeIdentifier(groupId)} FROM ${this.escapeIdentifier(userId)}`
|
|
1284
|
+
);
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
throw this.mapError(error);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
async getGroupMembers(groupId) {
|
|
1290
|
+
const client = await this.getClient();
|
|
1291
|
+
try {
|
|
1292
|
+
await this.getGroup(groupId);
|
|
1293
|
+
const result = await client.query(
|
|
1294
|
+
`SELECT r.rolname, r.rolsuper, r.rolcreatedb, r.rolcreaterole, r.rolcanlogin
|
|
1295
|
+
FROM pg_auth_members m
|
|
1296
|
+
JOIN pg_roles g ON g.oid = m.roleid
|
|
1297
|
+
JOIN pg_roles r ON r.oid = m.member
|
|
1298
|
+
WHERE g.rolname = $1 AND r.rolcanlogin = true
|
|
1299
|
+
ORDER BY r.rolname`,
|
|
1300
|
+
[groupId]
|
|
1301
|
+
);
|
|
1302
|
+
return result.rows.map(
|
|
1303
|
+
(row) => this.rowToUser(row)
|
|
1304
|
+
);
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
throw this.mapError(error);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async getUserGroups(userId) {
|
|
1310
|
+
const client = await this.getClient();
|
|
1311
|
+
try {
|
|
1312
|
+
await this.getUser(userId);
|
|
1313
|
+
const result = await client.query(
|
|
1314
|
+
`SELECT g.rolname, g.rolsuper, g.rolcreatedb, g.rolcreaterole, g.rolcanlogin
|
|
1315
|
+
FROM pg_auth_members m
|
|
1316
|
+
JOIN pg_roles g ON g.oid = m.roleid
|
|
1317
|
+
JOIN pg_roles r ON r.oid = m.member
|
|
1318
|
+
WHERE r.rolname = $1 AND g.rolcanlogin = false
|
|
1319
|
+
ORDER BY g.rolname`,
|
|
1320
|
+
[userId]
|
|
1321
|
+
);
|
|
1322
|
+
return result.rows.map(
|
|
1323
|
+
(row) => this.rowToGroup(row)
|
|
1324
|
+
);
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
throw this.mapError(error);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
// ==========================================================================
|
|
1330
|
+
// Database Provisioning
|
|
1331
|
+
// ==========================================================================
|
|
1332
|
+
async createDatabase(input) {
|
|
1333
|
+
const client = await this.getClient();
|
|
1334
|
+
try {
|
|
1335
|
+
let sql = `CREATE DATABASE ${this.escapeIdentifier(input.name)} OWNER ${this.escapeIdentifier(input.owner)}`;
|
|
1336
|
+
if (input.encoding) {
|
|
1337
|
+
sql += ` ENCODING ${this.escapeLiteral(input.encoding)}`;
|
|
1338
|
+
}
|
|
1339
|
+
await client.query(sql);
|
|
1340
|
+
return this.getDatabase(input.name);
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
throw this.mapError(error);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
async getDatabase(name) {
|
|
1346
|
+
const client = await this.getClient();
|
|
1347
|
+
try {
|
|
1348
|
+
const result = await client.query(
|
|
1349
|
+
`SELECT
|
|
1350
|
+
d.datname,
|
|
1351
|
+
pg_catalog.pg_get_userbyid(d.datdba) AS owner,
|
|
1352
|
+
pg_encoding_to_char(d.encoding) AS encoding,
|
|
1353
|
+
pg_database_size(d.datname)::text AS size
|
|
1354
|
+
FROM pg_database d
|
|
1355
|
+
WHERE d.datname = $1`,
|
|
1356
|
+
[name]
|
|
1357
|
+
);
|
|
1358
|
+
if (result.rows.length === 0) {
|
|
1359
|
+
throw new NotFoundError("database", name, PROVIDER$1);
|
|
1360
|
+
}
|
|
1361
|
+
return this.rowToDatabase(result.rows[0]);
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
throw this.mapError(error);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
async dropDatabase(name) {
|
|
1367
|
+
const client = await this.getClient();
|
|
1368
|
+
try {
|
|
1369
|
+
await this.getDatabase(name);
|
|
1370
|
+
await client.query(`DROP DATABASE ${this.escapeIdentifier(name)}`);
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
throw this.mapError(error);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async listDatabases() {
|
|
1376
|
+
const client = await this.getClient();
|
|
1377
|
+
try {
|
|
1378
|
+
const result = await client.query(
|
|
1379
|
+
`SELECT
|
|
1380
|
+
d.datname,
|
|
1381
|
+
pg_catalog.pg_get_userbyid(d.datdba) AS owner,
|
|
1382
|
+
pg_encoding_to_char(d.encoding) AS encoding,
|
|
1383
|
+
pg_database_size(d.datname)::text AS size
|
|
1384
|
+
FROM pg_database d
|
|
1385
|
+
ORDER BY d.datname`
|
|
1386
|
+
);
|
|
1387
|
+
return result.rows.filter(
|
|
1388
|
+
(row) => !SYSTEM_DATABASES.has(row.datname)
|
|
1389
|
+
).map((row) => this.rowToDatabase(row));
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
throw this.mapError(error);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// ==========================================================================
|
|
1395
|
+
// Role Provisioning
|
|
1396
|
+
// ==========================================================================
|
|
1397
|
+
async createRole(input) {
|
|
1398
|
+
const client = await this.getClient();
|
|
1399
|
+
try {
|
|
1400
|
+
const options = [];
|
|
1401
|
+
if (input.login !== void 0) {
|
|
1402
|
+
options.push(input.login ? "LOGIN" : "NOLOGIN");
|
|
1403
|
+
}
|
|
1404
|
+
if (input.password) {
|
|
1405
|
+
options.push(`PASSWORD ${this.escapeLiteral(input.password)}`);
|
|
1406
|
+
}
|
|
1407
|
+
if (input.superuser !== void 0) {
|
|
1408
|
+
options.push(input.superuser ? "SUPERUSER" : "NOSUPERUSER");
|
|
1409
|
+
}
|
|
1410
|
+
if (input.createDb !== void 0) {
|
|
1411
|
+
options.push(input.createDb ? "CREATEDB" : "NOCREATEDB");
|
|
1412
|
+
}
|
|
1413
|
+
if (input.createRole !== void 0) {
|
|
1414
|
+
options.push(input.createRole ? "CREATEROLE" : "NOCREATEROLE");
|
|
1415
|
+
}
|
|
1416
|
+
let sql = `CREATE ROLE ${this.escapeIdentifier(input.name)}`;
|
|
1417
|
+
if (options.length > 0) {
|
|
1418
|
+
sql += ` WITH ${options.join(" ")}`;
|
|
1419
|
+
}
|
|
1420
|
+
await client.query(sql);
|
|
1421
|
+
return this.getRole(input.name);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
throw this.mapError(error);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
async getRole(name) {
|
|
1427
|
+
const client = await this.getClient();
|
|
1428
|
+
try {
|
|
1429
|
+
const result = await client.query(
|
|
1430
|
+
`SELECT rolname, rolcanlogin, rolsuper, rolcreatedb, rolcreaterole
|
|
1431
|
+
FROM pg_roles
|
|
1432
|
+
WHERE rolname = $1`,
|
|
1433
|
+
[name]
|
|
1434
|
+
);
|
|
1435
|
+
if (result.rows.length === 0) {
|
|
1436
|
+
throw new NotFoundError("role", name, PROVIDER$1);
|
|
1437
|
+
}
|
|
1438
|
+
return this.rowToPgRole(result.rows[0]);
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
throw this.mapError(error);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
async dropRole(name) {
|
|
1444
|
+
const client = await this.getClient();
|
|
1445
|
+
try {
|
|
1446
|
+
await this.getRole(name);
|
|
1447
|
+
await client.query(`DROP ROLE ${this.escapeIdentifier(name)}`);
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
throw this.mapError(error);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
async listRoles() {
|
|
1453
|
+
const client = await this.getClient();
|
|
1454
|
+
try {
|
|
1455
|
+
const result = await client.query(
|
|
1456
|
+
`SELECT rolname, rolcanlogin, rolsuper, rolcreatedb, rolcreaterole
|
|
1457
|
+
FROM pg_roles
|
|
1458
|
+
ORDER BY rolname`
|
|
1459
|
+
);
|
|
1460
|
+
return result.rows.filter(
|
|
1461
|
+
(row) => !this.isSystemRole(row.rolname)
|
|
1462
|
+
).map((row) => this.rowToPgRole(row));
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
throw this.mapError(error);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
// ==========================================================================
|
|
1468
|
+
// Access Control (GRANT/REVOKE on databases)
|
|
1469
|
+
// ==========================================================================
|
|
1470
|
+
async grantAccess(roleName, databaseName) {
|
|
1471
|
+
const client = await this.getClient();
|
|
1472
|
+
try {
|
|
1473
|
+
await client.query(
|
|
1474
|
+
`GRANT ALL ON DATABASE ${this.escapeIdentifier(databaseName)} TO ${this.escapeIdentifier(roleName)}`
|
|
1475
|
+
);
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
throw this.mapError(error);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
async revokeAccess(roleName, databaseName) {
|
|
1481
|
+
const client = await this.getClient();
|
|
1482
|
+
try {
|
|
1483
|
+
await client.query(
|
|
1484
|
+
`REVOKE ALL ON DATABASE ${this.escapeIdentifier(databaseName)} FROM ${this.escapeIdentifier(roleName)}`
|
|
1485
|
+
);
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
throw this.mapError(error);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
const postgres = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1492
|
+
__proto__: null,
|
|
1493
|
+
PostgresAdapter
|
|
1494
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1495
|
+
const PROVIDER = "stalwart";
|
|
1496
|
+
const DEFAULT_TIMEOUT = 3e4;
|
|
1497
|
+
class StalwartAdapter {
|
|
1498
|
+
options;
|
|
1499
|
+
authHeader;
|
|
1500
|
+
constructor(options) {
|
|
1501
|
+
this.options = options;
|
|
1502
|
+
this.authHeader = `Basic ${btoa(`${options.username}:${options.password}`)}`;
|
|
1503
|
+
}
|
|
1504
|
+
get timeout() {
|
|
1505
|
+
return this.options.timeout ?? DEFAULT_TIMEOUT;
|
|
1506
|
+
}
|
|
1507
|
+
// ==========================================================================
|
|
1508
|
+
// HTTP Helper
|
|
1509
|
+
// ==========================================================================
|
|
1510
|
+
async request(method, path, body) {
|
|
1511
|
+
const url = `${this.options.baseUrl}${path}`;
|
|
1512
|
+
const headers = {
|
|
1513
|
+
Authorization: this.authHeader,
|
|
1514
|
+
Accept: "application/json"
|
|
1515
|
+
};
|
|
1516
|
+
if (body !== void 0) {
|
|
1517
|
+
headers["Content-Type"] = "application/json";
|
|
1518
|
+
}
|
|
1519
|
+
let response;
|
|
1520
|
+
try {
|
|
1521
|
+
response = await fetch(url, {
|
|
1522
|
+
method,
|
|
1523
|
+
headers,
|
|
1524
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
1525
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
1526
|
+
});
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
1529
|
+
throw new ConnectionError(
|
|
1530
|
+
`Request timed out after ${this.timeout}ms: ${method} ${path}`,
|
|
1531
|
+
PROVIDER,
|
|
1532
|
+
error
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
throw new ConnectionError(
|
|
1536
|
+
`Failed to connect to Stalwart: ${error.message}`,
|
|
1537
|
+
PROVIDER,
|
|
1538
|
+
error
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
if (response.ok) {
|
|
1542
|
+
const text = await response.text();
|
|
1543
|
+
if (!text) return void 0;
|
|
1544
|
+
try {
|
|
1545
|
+
return JSON.parse(text);
|
|
1546
|
+
} catch {
|
|
1547
|
+
return text;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
const errorBody = await response.text().catch(() => "");
|
|
1551
|
+
switch (response.status) {
|
|
1552
|
+
case 401:
|
|
1553
|
+
case 403:
|
|
1554
|
+
throw new AuthenticationError(
|
|
1555
|
+
`Authentication failed: ${response.status} ${response.statusText}`,
|
|
1556
|
+
PROVIDER
|
|
1557
|
+
);
|
|
1558
|
+
case 404:
|
|
1559
|
+
throw new NotFoundError("resource", path, PROVIDER);
|
|
1560
|
+
case 409:
|
|
1561
|
+
throw new ConflictError("resource", path, PROVIDER);
|
|
1562
|
+
default:
|
|
1563
|
+
throw new DirectoryError(
|
|
1564
|
+
`Stalwart API error: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`,
|
|
1565
|
+
"API_ERROR",
|
|
1566
|
+
PROVIDER
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
// ==========================================================================
|
|
1571
|
+
// Connection
|
|
1572
|
+
// ==========================================================================
|
|
1573
|
+
async testConnection() {
|
|
1574
|
+
try {
|
|
1575
|
+
await this.request("GET", "/api/principal?type=individual&limit=1");
|
|
1576
|
+
return true;
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
if (error instanceof AuthenticationError) {
|
|
1579
|
+
throw error;
|
|
1580
|
+
}
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
async disconnect() {
|
|
1585
|
+
}
|
|
1586
|
+
// ==========================================================================
|
|
1587
|
+
// User CRUD
|
|
1588
|
+
// ==========================================================================
|
|
1589
|
+
async createUser(input) {
|
|
1590
|
+
const principal = {
|
|
1591
|
+
name: input.username,
|
|
1592
|
+
type: "individual",
|
|
1593
|
+
description: input.displayName,
|
|
1594
|
+
emails: input.email ? [input.email] : [],
|
|
1595
|
+
secrets: input.password ? [input.password] : void 0
|
|
1596
|
+
};
|
|
1597
|
+
await this.request("POST", "/api/principal", principal);
|
|
1598
|
+
return this.getUser(input.username);
|
|
1599
|
+
}
|
|
1600
|
+
async getUser(id) {
|
|
1601
|
+
const principal = await this.request(
|
|
1602
|
+
"GET",
|
|
1603
|
+
`/api/principal/${encodeURIComponent(id)}`
|
|
1604
|
+
);
|
|
1605
|
+
return this.principalToUser(principal);
|
|
1606
|
+
}
|
|
1607
|
+
async updateUser(id, input) {
|
|
1608
|
+
const patch = {};
|
|
1609
|
+
if (input.displayName !== void 0) patch.description = input.displayName;
|
|
1610
|
+
if (input.email !== void 0) patch.emails = [input.email];
|
|
1611
|
+
if (input.password !== void 0) patch.secrets = [input.password];
|
|
1612
|
+
await this.request(
|
|
1613
|
+
"PATCH",
|
|
1614
|
+
`/api/principal/${encodeURIComponent(id)}`,
|
|
1615
|
+
patch
|
|
1616
|
+
);
|
|
1617
|
+
return this.getUser(id);
|
|
1618
|
+
}
|
|
1619
|
+
async deleteUser(id) {
|
|
1620
|
+
await this.request("DELETE", `/api/principal/${encodeURIComponent(id)}`);
|
|
1621
|
+
}
|
|
1622
|
+
async listUsers() {
|
|
1623
|
+
const names = await this.request(
|
|
1624
|
+
"GET",
|
|
1625
|
+
"/api/principal?type=individual"
|
|
1626
|
+
);
|
|
1627
|
+
const users = await Promise.all(
|
|
1628
|
+
(names ?? []).map((name) => this.getUser(name))
|
|
1629
|
+
);
|
|
1630
|
+
return users;
|
|
1631
|
+
}
|
|
1632
|
+
// ==========================================================================
|
|
1633
|
+
// Group CRUD
|
|
1634
|
+
// ==========================================================================
|
|
1635
|
+
async createGroup(input) {
|
|
1636
|
+
const principal = {
|
|
1637
|
+
name: input.name,
|
|
1638
|
+
type: "group",
|
|
1639
|
+
description: input.description ?? input.displayName,
|
|
1640
|
+
members: input.members
|
|
1641
|
+
};
|
|
1642
|
+
await this.request("POST", "/api/principal", principal);
|
|
1643
|
+
return this.getGroup(input.name);
|
|
1644
|
+
}
|
|
1645
|
+
async getGroup(id) {
|
|
1646
|
+
const principal = await this.request(
|
|
1647
|
+
"GET",
|
|
1648
|
+
`/api/principal/${encodeURIComponent(id)}`
|
|
1649
|
+
);
|
|
1650
|
+
return this.principalToGroup(principal);
|
|
1651
|
+
}
|
|
1652
|
+
async updateGroup(id, input) {
|
|
1653
|
+
const patch = {};
|
|
1654
|
+
if (input.displayName !== void 0) patch.description = input.displayName;
|
|
1655
|
+
if (input.description !== void 0) patch.description = input.description;
|
|
1656
|
+
await this.request(
|
|
1657
|
+
"PATCH",
|
|
1658
|
+
`/api/principal/${encodeURIComponent(id)}`,
|
|
1659
|
+
patch
|
|
1660
|
+
);
|
|
1661
|
+
return this.getGroup(id);
|
|
1662
|
+
}
|
|
1663
|
+
async deleteGroup(id) {
|
|
1664
|
+
await this.request("DELETE", `/api/principal/${encodeURIComponent(id)}`);
|
|
1665
|
+
}
|
|
1666
|
+
async listGroups() {
|
|
1667
|
+
const names = await this.request(
|
|
1668
|
+
"GET",
|
|
1669
|
+
"/api/principal?type=group"
|
|
1670
|
+
);
|
|
1671
|
+
const groups = await Promise.all(
|
|
1672
|
+
(names ?? []).map((name) => this.getGroup(name))
|
|
1673
|
+
);
|
|
1674
|
+
return groups;
|
|
1675
|
+
}
|
|
1676
|
+
// ==========================================================================
|
|
1677
|
+
// Membership
|
|
1678
|
+
// ==========================================================================
|
|
1679
|
+
async addUserToGroup(userId, groupId) {
|
|
1680
|
+
const group = await this.request(
|
|
1681
|
+
"GET",
|
|
1682
|
+
`/api/principal/${encodeURIComponent(groupId)}`
|
|
1683
|
+
);
|
|
1684
|
+
const members = group.members ?? [];
|
|
1685
|
+
if (!members.includes(userId)) {
|
|
1686
|
+
members.push(userId);
|
|
1687
|
+
}
|
|
1688
|
+
await this.request(
|
|
1689
|
+
"PATCH",
|
|
1690
|
+
`/api/principal/${encodeURIComponent(groupId)}`,
|
|
1691
|
+
{ members }
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
async removeUserFromGroup(userId, groupId) {
|
|
1695
|
+
const group = await this.request(
|
|
1696
|
+
"GET",
|
|
1697
|
+
`/api/principal/${encodeURIComponent(groupId)}`
|
|
1698
|
+
);
|
|
1699
|
+
const members = (group.members ?? []).filter((m) => m !== userId);
|
|
1700
|
+
await this.request(
|
|
1701
|
+
"PATCH",
|
|
1702
|
+
`/api/principal/${encodeURIComponent(groupId)}`,
|
|
1703
|
+
{ members }
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
async getGroupMembers(groupId) {
|
|
1707
|
+
const group = await this.request(
|
|
1708
|
+
"GET",
|
|
1709
|
+
`/api/principal/${encodeURIComponent(groupId)}`
|
|
1710
|
+
);
|
|
1711
|
+
const members = group.members ?? [];
|
|
1712
|
+
const users = await Promise.all(members.map((name) => this.getUser(name)));
|
|
1713
|
+
return users;
|
|
1714
|
+
}
|
|
1715
|
+
async getUserGroups(userId) {
|
|
1716
|
+
const user = await this.request(
|
|
1717
|
+
"GET",
|
|
1718
|
+
`/api/principal/${encodeURIComponent(userId)}`
|
|
1719
|
+
);
|
|
1720
|
+
const groupNames = user.memberOf ?? [];
|
|
1721
|
+
const groups = await Promise.all(
|
|
1722
|
+
groupNames.map((name) => this.getGroup(name))
|
|
1723
|
+
);
|
|
1724
|
+
return groups;
|
|
1725
|
+
}
|
|
1726
|
+
// ==========================================================================
|
|
1727
|
+
// Domain CRUD
|
|
1728
|
+
// ==========================================================================
|
|
1729
|
+
async createDomain(input) {
|
|
1730
|
+
const principal = {
|
|
1731
|
+
name: input.name,
|
|
1732
|
+
type: "domain"
|
|
1733
|
+
};
|
|
1734
|
+
await this.request("POST", "/api/principal", principal);
|
|
1735
|
+
return this.getDomain(input.name);
|
|
1736
|
+
}
|
|
1737
|
+
async getDomain(id) {
|
|
1738
|
+
const principal = await this.request(
|
|
1739
|
+
"GET",
|
|
1740
|
+
`/api/principal/${encodeURIComponent(id)}`
|
|
1741
|
+
);
|
|
1742
|
+
return {
|
|
1743
|
+
id: principal.name,
|
|
1744
|
+
name: principal.name,
|
|
1745
|
+
active: true
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
async deleteDomain(id) {
|
|
1749
|
+
await this.request("DELETE", `/api/principal/${encodeURIComponent(id)}`);
|
|
1750
|
+
}
|
|
1751
|
+
async listDomains() {
|
|
1752
|
+
const names = await this.request(
|
|
1753
|
+
"GET",
|
|
1754
|
+
"/api/principal?type=domain"
|
|
1755
|
+
);
|
|
1756
|
+
const domains = await Promise.all(
|
|
1757
|
+
(names ?? []).map((name) => this.getDomain(name))
|
|
1758
|
+
);
|
|
1759
|
+
return domains;
|
|
1760
|
+
}
|
|
1761
|
+
// ==========================================================================
|
|
1762
|
+
// DKIM
|
|
1763
|
+
// ==========================================================================
|
|
1764
|
+
async createDkimKey(input) {
|
|
1765
|
+
const result = await this.request("POST", "/api/dkim", {
|
|
1766
|
+
domain: input.domain,
|
|
1767
|
+
selector: input.selector
|
|
1768
|
+
});
|
|
1769
|
+
return {
|
|
1770
|
+
id: result?.id ?? `${input.selector}._domainkey.${input.domain}`,
|
|
1771
|
+
domain: input.domain,
|
|
1772
|
+
selector: input.selector,
|
|
1773
|
+
publicKey: result?.publicKey
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
// ==========================================================================
|
|
1777
|
+
// DNS Records
|
|
1778
|
+
// ==========================================================================
|
|
1779
|
+
async getDnsRecords(domain) {
|
|
1780
|
+
const records = await this.request(
|
|
1781
|
+
"GET",
|
|
1782
|
+
`/api/dns/records/${encodeURIComponent(domain)}`
|
|
1783
|
+
);
|
|
1784
|
+
return records ?? [];
|
|
1785
|
+
}
|
|
1786
|
+
// ==========================================================================
|
|
1787
|
+
// Mailbox CRUD
|
|
1788
|
+
// ==========================================================================
|
|
1789
|
+
async createMailbox(input) {
|
|
1790
|
+
const atIndex = input.email.indexOf("@");
|
|
1791
|
+
const localPart = atIndex >= 0 ? input.email.slice(0, atIndex) : input.email;
|
|
1792
|
+
const principal = {
|
|
1793
|
+
name: localPart,
|
|
1794
|
+
type: "individual",
|
|
1795
|
+
description: input.name,
|
|
1796
|
+
emails: [input.email],
|
|
1797
|
+
secrets: [input.password],
|
|
1798
|
+
quota: input.quota
|
|
1799
|
+
};
|
|
1800
|
+
await this.request("POST", "/api/principal", principal);
|
|
1801
|
+
return this.getMailbox(localPart);
|
|
1802
|
+
}
|
|
1803
|
+
async getMailbox(id) {
|
|
1804
|
+
const principal = await this.request(
|
|
1805
|
+
"GET",
|
|
1806
|
+
`/api/principal/${encodeURIComponent(id)}`
|
|
1807
|
+
);
|
|
1808
|
+
return this.principalToMailbox(principal);
|
|
1809
|
+
}
|
|
1810
|
+
async updateMailbox(id, input) {
|
|
1811
|
+
const patch = {};
|
|
1812
|
+
if (input.name !== void 0) patch.description = input.name;
|
|
1813
|
+
if (input.password !== void 0) patch.secrets = [input.password];
|
|
1814
|
+
if (input.quota !== void 0) patch.quota = input.quota;
|
|
1815
|
+
await this.request(
|
|
1816
|
+
"PATCH",
|
|
1817
|
+
`/api/principal/${encodeURIComponent(id)}`,
|
|
1818
|
+
patch
|
|
1819
|
+
);
|
|
1820
|
+
return this.getMailbox(id);
|
|
1821
|
+
}
|
|
1822
|
+
async deleteMailbox(id) {
|
|
1823
|
+
await this.request("DELETE", `/api/principal/${encodeURIComponent(id)}`);
|
|
1824
|
+
}
|
|
1825
|
+
async listMailboxes() {
|
|
1826
|
+
const names = await this.request(
|
|
1827
|
+
"GET",
|
|
1828
|
+
"/api/principal?type=individual"
|
|
1829
|
+
);
|
|
1830
|
+
const mailboxes = await Promise.all(
|
|
1831
|
+
(names ?? []).map((name) => this.getMailbox(name))
|
|
1832
|
+
);
|
|
1833
|
+
return mailboxes;
|
|
1834
|
+
}
|
|
1835
|
+
// ==========================================================================
|
|
1836
|
+
// Principal Mapping Helpers
|
|
1837
|
+
// ==========================================================================
|
|
1838
|
+
principalToUser(principal) {
|
|
1839
|
+
return {
|
|
1840
|
+
id: principal.name,
|
|
1841
|
+
username: principal.name,
|
|
1842
|
+
displayName: principal.description,
|
|
1843
|
+
email: principal.emails?.[0],
|
|
1844
|
+
active: true,
|
|
1845
|
+
groups: principal.memberOf
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
principalToGroup(principal) {
|
|
1849
|
+
return {
|
|
1850
|
+
id: principal.name,
|
|
1851
|
+
name: principal.name,
|
|
1852
|
+
displayName: principal.description,
|
|
1853
|
+
description: principal.description,
|
|
1854
|
+
members: principal.members
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
principalToMailbox(principal) {
|
|
1858
|
+
return {
|
|
1859
|
+
id: principal.name,
|
|
1860
|
+
name: principal.description ?? principal.name,
|
|
1861
|
+
email: principal.emails?.[0] ?? "",
|
|
1862
|
+
quota: principal.quota,
|
|
1863
|
+
active: true
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
const stalwart = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1868
|
+
__proto__: null,
|
|
1869
|
+
StalwartAdapter
|
|
1870
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1871
|
+
function isKanidmOptions(opts) {
|
|
1872
|
+
return opts.type === "kanidm";
|
|
1873
|
+
}
|
|
1874
|
+
function isStalwartOptions(opts) {
|
|
1875
|
+
return opts.type === "stalwart";
|
|
1876
|
+
}
|
|
1877
|
+
function isPostgresOptions(opts) {
|
|
1878
|
+
return opts.type === "postgres";
|
|
1879
|
+
}
|
|
1880
|
+
function isAwsOptions(opts) {
|
|
1881
|
+
return opts.type === "aws";
|
|
1882
|
+
}
|
|
1883
|
+
async function getDirectoryAdapter(options) {
|
|
1884
|
+
if (isKanidmOptions(options)) {
|
|
1885
|
+
const { KanidmAdapter: KanidmAdapter2 } = await Promise.resolve().then(() => kanidm);
|
|
1886
|
+
return new KanidmAdapter2(options);
|
|
1887
|
+
}
|
|
1888
|
+
if (isStalwartOptions(options)) {
|
|
1889
|
+
const { StalwartAdapter: StalwartAdapter2 } = await Promise.resolve().then(() => stalwart);
|
|
1890
|
+
return new StalwartAdapter2(options);
|
|
1891
|
+
}
|
|
1892
|
+
if (isPostgresOptions(options)) {
|
|
1893
|
+
const { PostgresAdapter: PostgresAdapter2 } = await Promise.resolve().then(() => postgres);
|
|
1894
|
+
return new PostgresAdapter2(options);
|
|
1895
|
+
}
|
|
1896
|
+
if (isAwsOptions(options)) {
|
|
1897
|
+
const { AwsAdapter: AwsAdapter2 } = await Promise.resolve().then(() => aws);
|
|
1898
|
+
return new AwsAdapter2(options);
|
|
1899
|
+
}
|
|
1900
|
+
throw new Error(
|
|
1901
|
+
`Unknown directory adapter type: ${options.type}`
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
async function getKanidmAdapter(options) {
|
|
1905
|
+
const { KanidmAdapter: KanidmAdapter2 } = await Promise.resolve().then(() => kanidm);
|
|
1906
|
+
return new KanidmAdapter2({ type: "kanidm", ...options });
|
|
1907
|
+
}
|
|
1908
|
+
async function getStalwartAdapter(options) {
|
|
1909
|
+
const { StalwartAdapter: StalwartAdapter2 } = await Promise.resolve().then(() => stalwart);
|
|
1910
|
+
return new StalwartAdapter2({ type: "stalwart", ...options });
|
|
1911
|
+
}
|
|
1912
|
+
async function getPostgresAdapter(options) {
|
|
1913
|
+
const { PostgresAdapter: PostgresAdapter2 } = await Promise.resolve().then(() => postgres);
|
|
1914
|
+
return new PostgresAdapter2({ type: "postgres", ...options });
|
|
1915
|
+
}
|
|
1916
|
+
async function getAwsAdapter(options) {
|
|
1917
|
+
const { AwsAdapter: AwsAdapter2 } = await Promise.resolve().then(() => aws);
|
|
1918
|
+
return new AwsAdapter2({ type: "aws", ...options });
|
|
1919
|
+
}
|
|
1920
|
+
export {
|
|
1921
|
+
AuthenticationError,
|
|
1922
|
+
AwsAdapter,
|
|
1923
|
+
ConflictError,
|
|
1924
|
+
ConnectionError,
|
|
1925
|
+
DirectoryError,
|
|
1926
|
+
KanidmAdapter,
|
|
1927
|
+
NotFoundError,
|
|
1928
|
+
PostgresAdapter,
|
|
1929
|
+
RateLimitError,
|
|
1930
|
+
StalwartAdapter,
|
|
1931
|
+
ValidationError,
|
|
1932
|
+
getAwsAdapter,
|
|
1933
|
+
getDirectoryAdapter,
|
|
1934
|
+
getKanidmAdapter,
|
|
1935
|
+
getPostgresAdapter,
|
|
1936
|
+
getStalwartAdapter,
|
|
1937
|
+
isAwsOptions,
|
|
1938
|
+
isKanidmOptions,
|
|
1939
|
+
isPostgresOptions,
|
|
1940
|
+
isStalwartOptions
|
|
1941
|
+
};
|
|
1942
|
+
//# sourceMappingURL=index.js.map
|