@happyvertical/directory 0.80.0 → 0.80.1

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