@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/chunks/errors-DjVGsCVd.js +66 -0
- package/dist/chunks/errors-DjVGsCVd.js.map +1 -0
- package/dist/index.js +1561 -2161
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,2222 +1,1622 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
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
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
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
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
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
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
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
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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
|
-
|
|
1536
|
+
return opts.type === "kanidm";
|
|
2153
1537
|
}
|
|
2154
1538
|
function isStalwartOptions(opts) {
|
|
2155
|
-
|
|
1539
|
+
return opts.type === "stalwart";
|
|
2156
1540
|
}
|
|
2157
1541
|
function isPostgresOptions(opts) {
|
|
2158
|
-
|
|
1542
|
+
return opts.type === "postgres";
|
|
2159
1543
|
}
|
|
2160
1544
|
function isAwsOptions(opts) {
|
|
2161
|
-
|
|
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
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
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
|
-
|
|
2186
|
-
|
|
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
|
-
|
|
2190
|
-
|
|
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
|
-
|
|
2194
|
-
|
|
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
|
-
|
|
2198
|
-
|
|
1613
|
+
const { AwsAdapter } = await Promise.resolve().then(() => aws_exports);
|
|
1614
|
+
return new AwsAdapter({
|
|
1615
|
+
type: "aws",
|
|
1616
|
+
...options
|
|
1617
|
+
});
|
|
2199
1618
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
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
|