@opengeni/api-router 2.2.0-canary.0 → 2.3.2-canary.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/app.js +1 -1
- package/dist/auth/managed-auth.d.ts +5 -2
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/organization-user-setup.d.ts +57 -0
- package/dist/{chunk-3TP54PPX.js → chunk-IBV7Z6F4.js} +3545 -2009
- package/dist/chunk-IBV7Z6F4.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/integrations/slack-app-home.d.ts +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/session-view.d.ts +1 -0
- package/dist/routes/automations.d.ts +13 -0
- package/dist/routes/insights.d.ts +2 -1
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/package.json +18 -18
- package/src/app.ts +56 -5
- package/src/auth/managed-auth.ts +29 -34
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/organization-user-setup.ts +217 -0
- package/src/http/auth.ts +15 -0
- package/src/http/sse.ts +62 -13
- package/src/integrations/slack-app-home.ts +2 -2
- package/src/mcp/company-brain-governed-writes.ts +4 -4
- package/src/mcp/company-profile-agent-admin.ts +11 -18
- package/src/mcp/remember.ts +4 -4
- package/src/mcp/server.ts +50 -4
- package/src/mcp/session-view.ts +8 -2
- package/src/routes/automations.ts +3 -3
- package/src/routes/documents.ts +2 -0
- package/src/routes/insights.ts +61 -19
- package/src/routes/managed-onboarding.ts +317 -0
- package/src/routes/organization-memberships.ts +212 -155
- package/src/routes/pr-review-github.ts +844 -0
- package/src/routes/pr-review.ts +20 -0
- package/src/routes/rigs.ts +37 -4
- package/src/routes/sessions.ts +101 -0
- package/src/sandbox/channel-a.ts +34 -7
- package/src/sandbox/viewer.ts +47 -11
- package/dist/chunk-3TP54PPX.js.map +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AddOrganizationWorkspaceMemberRequest,
|
|
3
2
|
AcceptOrganizationInvitationRequest,
|
|
4
3
|
CreateOrganizationRequest,
|
|
5
4
|
CreateOrganizationResponse,
|
|
@@ -8,24 +7,28 @@ import {
|
|
|
8
7
|
ListManagedOrganizationMembershipsResponse,
|
|
9
8
|
ListOrganizationInvitationsPageQuery,
|
|
10
9
|
ListOrganizationInvitationsPageResponse,
|
|
11
|
-
|
|
10
|
+
ListOrganizationAdministrationMembersResponse,
|
|
12
11
|
OrganizationAdministrationOverview,
|
|
13
12
|
OrganizationInvitation,
|
|
13
|
+
OrganizationUserSetupDelivery,
|
|
14
14
|
OrganizationMember,
|
|
15
15
|
OrganizationPrivateSessionSettings,
|
|
16
16
|
OrganizationRetentionPolicy,
|
|
17
17
|
OrganizationSummary,
|
|
18
|
-
|
|
18
|
+
OrganizationWorkspaceAccess,
|
|
19
|
+
OrganizationWorkspaceAccessMember,
|
|
20
|
+
PutOrganizationWorkspaceMemberRequest,
|
|
19
21
|
RevokeOrganizationInvitationRequest,
|
|
22
|
+
RetryOrganizationUserSetupDeliveryRequest,
|
|
23
|
+
RevokeOrganizationWorkspaceMemberRequest,
|
|
24
|
+
RevokeOrganizationWorkspaceMemberResponse,
|
|
20
25
|
UpdateOrganizationMemberRequest,
|
|
21
26
|
UpdateOrganizationNameRequest,
|
|
22
27
|
UpdateOrganizationPrivateSessionSettingsRequest,
|
|
23
28
|
UpdateOrganizationRetentionPolicyRequest,
|
|
24
|
-
|
|
25
|
-
UpdateWorkspaceRequest,
|
|
29
|
+
UpdateOrganizationWorkspaceRequest,
|
|
26
30
|
UpdateWorkspaceSettingsRequest,
|
|
27
31
|
Workspace,
|
|
28
|
-
WorkspaceMember,
|
|
29
32
|
} from "@opengeni/contracts";
|
|
30
33
|
import {
|
|
31
34
|
getManagedSession,
|
|
@@ -35,33 +38,43 @@ import {
|
|
|
35
38
|
import {
|
|
36
39
|
acceptOrganizationInvitation,
|
|
37
40
|
bindPendingOrganizationInvitationsForVerifiedEmail,
|
|
41
|
+
claimOrganizationUserSetupDelivery,
|
|
38
42
|
createManagedOrganization,
|
|
39
|
-
|
|
43
|
+
createOrganizationWorkspace,
|
|
40
44
|
createOrganizationInvitation,
|
|
41
45
|
ensureManagedAccessForUserWithOrganizationMemberships,
|
|
42
|
-
getManagedUserProfilesByIds,
|
|
43
46
|
getOrganizationAdministrationOverview,
|
|
47
|
+
getOrganizationInvitationForAdministration,
|
|
44
48
|
getOrganizationPrivateSessionSettings,
|
|
45
49
|
getSelfOrganizationInvitation,
|
|
46
50
|
getOrganizationRetentionPolicy,
|
|
47
|
-
|
|
51
|
+
listOrganizationAdministrationMembers,
|
|
48
52
|
listOrganizationInvitations,
|
|
49
53
|
listSelfOrganizationInvitations,
|
|
50
54
|
nestedPostgresSqlState,
|
|
55
|
+
prepareOrganizationUserSetupDelivery,
|
|
51
56
|
revokeOrganizationInvitation,
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
settleOrganizationUserSetupDelivery,
|
|
58
|
+
putOrganizationWorkspaceMember,
|
|
59
|
+
revokeOrganizationWorkspaceMember,
|
|
60
|
+
updateOrganizationWorkspace,
|
|
54
61
|
updateOrganizationSharedWorkspaceSettings,
|
|
55
62
|
updateOrganizationMember,
|
|
56
63
|
updateOrganizationName,
|
|
57
64
|
updateOrganizationPrivateSessionSettings,
|
|
58
65
|
updateOrganizationRetentionPolicy,
|
|
59
|
-
upsertOrganizationSharedWorkspaceMember,
|
|
60
66
|
} from "@opengeni/db";
|
|
61
67
|
import type { Context, Hono } from "hono";
|
|
62
68
|
import { HTTPException } from "hono/http-exception";
|
|
63
69
|
import { z } from "zod";
|
|
64
70
|
|
|
71
|
+
import {
|
|
72
|
+
assertOrganizationUserSetupDeliveryConfigured,
|
|
73
|
+
deriveOrganizationUserSetupToken,
|
|
74
|
+
organizationUserSetupPayloadDigest,
|
|
75
|
+
renderOrganizationUserSetupEmail,
|
|
76
|
+
} from "../auth/organization-user-setup";
|
|
77
|
+
|
|
65
78
|
const OrganizationId = z.string().uuid();
|
|
66
79
|
const WorkspaceId = z.string().uuid();
|
|
67
80
|
const MembershipId = z.string().uuid();
|
|
@@ -118,19 +131,6 @@ function rethrowMembershipError(error: unknown): never {
|
|
|
118
131
|
throw error;
|
|
119
132
|
}
|
|
120
133
|
|
|
121
|
-
function parseWorkspacePermissions(permissions: string[]) {
|
|
122
|
-
const parsed = z.array(Permission).max(128).safeParse(permissions);
|
|
123
|
-
if (!parsed.success) {
|
|
124
|
-
throw new HTTPException(422, { message: "invalid workspace permissions" });
|
|
125
|
-
}
|
|
126
|
-
return parsed.data;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function normalizeAgentInstructions(value: string | null): string | null {
|
|
130
|
-
if (value === null) return null;
|
|
131
|
-
return value.trim() || null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
134
|
export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
135
135
|
app.post("/v1/organizations", async (context) => {
|
|
136
136
|
const { session, subjectId } = await requireManagedHuman(context, deps);
|
|
@@ -159,6 +159,7 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
159
159
|
email: session.user.email,
|
|
160
160
|
name: session.user.name,
|
|
161
161
|
emailVerified: session.user.emailVerified,
|
|
162
|
+
provisionFallbackOrganization: false,
|
|
162
163
|
});
|
|
163
164
|
return context.json(
|
|
164
165
|
ListManagedOrganizationMembershipsResponse.parse({
|
|
@@ -166,6 +167,11 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
166
167
|
}),
|
|
167
168
|
);
|
|
168
169
|
} catch (error) {
|
|
170
|
+
// Not dead after 0348: a terminal-only membership is now a bounded EMPTY
|
|
171
|
+
// projection rather than a 42501, but the verified-email binder still
|
|
172
|
+
// raises 42501 when the session claims `emailVerified` and the durable
|
|
173
|
+
// `auth_users` row disagrees (an email change clears it), so this stays
|
|
174
|
+
// the correct deny for a stale-verification session.
|
|
169
175
|
if (nestedPostgresSqlState(error) === "42501") {
|
|
170
176
|
throw new HTTPException(403, {
|
|
171
177
|
message: "organization membership is not active",
|
|
@@ -256,21 +262,17 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
256
262
|
"organization id",
|
|
257
263
|
);
|
|
258
264
|
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
259
|
-
const payload = await parseBody(context,
|
|
265
|
+
const payload = await parseBody(context, UpdateOrganizationWorkspaceRequest);
|
|
260
266
|
try {
|
|
261
267
|
return context.json(
|
|
262
|
-
|
|
263
|
-
await
|
|
268
|
+
OrganizationWorkspaceAccess.parse(
|
|
269
|
+
await updateOrganizationWorkspace(deps.db, {
|
|
264
270
|
organizationId,
|
|
265
271
|
workspaceId,
|
|
266
272
|
actorSubjectId: subjectId,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
? {
|
|
271
|
-
agentInstructions: normalizeAgentInstructions(payload.agentInstructions),
|
|
272
|
-
}
|
|
273
|
-
: {}),
|
|
273
|
+
name: payload.name.trim(),
|
|
274
|
+
expectedUpdatedAt: payload.expectedUpdatedAt,
|
|
275
|
+
operationId: payload.operationId,
|
|
274
276
|
}),
|
|
275
277
|
),
|
|
276
278
|
);
|
|
@@ -289,15 +291,11 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
289
291
|
const payload = await parseBody(context, CreateOrganizationWorkspaceRequest);
|
|
290
292
|
try {
|
|
291
293
|
return context.json(
|
|
292
|
-
|
|
293
|
-
await
|
|
294
|
+
OrganizationWorkspaceAccess.parse(
|
|
295
|
+
await createOrganizationWorkspace(deps.db, {
|
|
294
296
|
organizationId,
|
|
295
297
|
actorSubjectId: subjectId,
|
|
296
298
|
name: payload.name.trim(),
|
|
297
|
-
...(payload.slug !== undefined ? { slug: payload.slug?.trim() || null } : {}),
|
|
298
|
-
...(payload.agentInstructions !== undefined
|
|
299
|
-
? { agentInstructions: normalizeAgentInstructions(payload.agentInstructions) }
|
|
300
|
-
: {}),
|
|
301
299
|
operationId: payload.operationId,
|
|
302
300
|
}),
|
|
303
301
|
),
|
|
@@ -336,121 +334,69 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
336
334
|
},
|
|
337
335
|
);
|
|
338
336
|
|
|
339
|
-
app.
|
|
340
|
-
|
|
341
|
-
const organizationId = parseId(
|
|
342
|
-
OrganizationId,
|
|
343
|
-
context.req.param("organizationId"),
|
|
344
|
-
"organization id",
|
|
345
|
-
);
|
|
346
|
-
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
347
|
-
const payload = await parseBody(context, AddOrganizationWorkspaceMemberRequest);
|
|
348
|
-
try {
|
|
349
|
-
const members = await listOrganizationMembers(deps.db, {
|
|
350
|
-
organizationId,
|
|
351
|
-
actorSubjectId: subjectId,
|
|
352
|
-
});
|
|
353
|
-
const target = members.find(
|
|
354
|
-
(member) => member.id === payload.organizationMembershipId && member.status === "active",
|
|
355
|
-
);
|
|
356
|
-
if (!target) {
|
|
357
|
-
throw new HTTPException(404, {
|
|
358
|
-
message: "active organization member not found",
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
const [profile] = await getManagedUserProfilesByIds(deps.db, [
|
|
362
|
-
target.subjectId.slice("user:".length),
|
|
363
|
-
]);
|
|
364
|
-
return context.json(
|
|
365
|
-
WorkspaceMember.parse(
|
|
366
|
-
await upsertOrganizationSharedWorkspaceMember(deps.db, {
|
|
367
|
-
organizationId,
|
|
368
|
-
workspaceId,
|
|
369
|
-
actorSubjectId: subjectId,
|
|
370
|
-
targetOrganizationMembershipId: target.id,
|
|
371
|
-
subjectLabel: profile?.email ?? profile?.name ?? null,
|
|
372
|
-
role: payload.role ?? "member",
|
|
373
|
-
permissions: parseWorkspacePermissions(payload.permissions),
|
|
374
|
-
requireExisting: false,
|
|
375
|
-
}),
|
|
376
|
-
),
|
|
377
|
-
201,
|
|
378
|
-
);
|
|
379
|
-
} catch (error) {
|
|
380
|
-
if (error instanceof HTTPException) throw error;
|
|
381
|
-
rethrowMembershipError(error);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
app.patch(
|
|
386
|
-
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:subjectId",
|
|
337
|
+
app.put(
|
|
338
|
+
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:membershipId",
|
|
387
339
|
async (context) => {
|
|
388
|
-
const { subjectId
|
|
340
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
389
341
|
const organizationId = parseId(
|
|
390
342
|
OrganizationId,
|
|
391
343
|
context.req.param("organizationId"),
|
|
392
344
|
"organization id",
|
|
393
345
|
);
|
|
394
346
|
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
395
|
-
const
|
|
396
|
-
|
|
347
|
+
const membershipId = parseId(
|
|
348
|
+
MembershipId,
|
|
349
|
+
context.req.param("membershipId"),
|
|
350
|
+
"membership id",
|
|
351
|
+
);
|
|
352
|
+
const payload = await parseBody(context, PutOrganizationWorkspaceMemberRequest);
|
|
397
353
|
try {
|
|
398
|
-
const members = await listOrganizationMembers(deps.db, {
|
|
399
|
-
organizationId,
|
|
400
|
-
actorSubjectId,
|
|
401
|
-
});
|
|
402
|
-
const target = members.find(
|
|
403
|
-
(member) => member.subjectId === targetSubjectId && member.status === "active",
|
|
404
|
-
);
|
|
405
|
-
if (!target) {
|
|
406
|
-
throw new HTTPException(404, {
|
|
407
|
-
message: "active organization member not found",
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
const [profile] = await getManagedUserProfilesByIds(deps.db, [
|
|
411
|
-
target.subjectId.slice("user:".length),
|
|
412
|
-
]);
|
|
413
354
|
return context.json(
|
|
414
|
-
|
|
415
|
-
await
|
|
355
|
+
OrganizationWorkspaceAccessMember.parse(
|
|
356
|
+
await putOrganizationWorkspaceMember(deps.db, {
|
|
416
357
|
organizationId,
|
|
417
358
|
workspaceId,
|
|
418
|
-
actorSubjectId,
|
|
419
|
-
targetOrganizationMembershipId:
|
|
420
|
-
|
|
421
|
-
...(payload.role === undefined ? {} : { role: payload.role }),
|
|
422
|
-
permissions: payload.permissions,
|
|
423
|
-
requireExisting: true,
|
|
359
|
+
actorSubjectId: subjectId,
|
|
360
|
+
targetOrganizationMembershipId: membershipId,
|
|
361
|
+
access: payload,
|
|
424
362
|
}),
|
|
425
363
|
),
|
|
426
364
|
);
|
|
427
365
|
} catch (error) {
|
|
428
|
-
if (error instanceof HTTPException) throw error;
|
|
429
366
|
rethrowMembershipError(error);
|
|
430
367
|
}
|
|
431
368
|
},
|
|
432
369
|
);
|
|
433
370
|
|
|
434
|
-
app.
|
|
435
|
-
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:
|
|
371
|
+
app.post(
|
|
372
|
+
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:membershipId/revoke",
|
|
436
373
|
async (context) => {
|
|
437
|
-
const { subjectId
|
|
374
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
438
375
|
const organizationId = parseId(
|
|
439
376
|
OrganizationId,
|
|
440
377
|
context.req.param("organizationId"),
|
|
441
378
|
"organization id",
|
|
442
379
|
);
|
|
443
380
|
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
444
|
-
const
|
|
381
|
+
const membershipId = parseId(
|
|
382
|
+
MembershipId,
|
|
383
|
+
context.req.param("membershipId"),
|
|
384
|
+
"membership id",
|
|
385
|
+
);
|
|
386
|
+
const payload = await parseBody(context, RevokeOrganizationWorkspaceMemberRequest);
|
|
445
387
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
388
|
+
return context.json(
|
|
389
|
+
RevokeOrganizationWorkspaceMemberResponse.parse(
|
|
390
|
+
await revokeOrganizationWorkspaceMember(deps.db, {
|
|
391
|
+
organizationId,
|
|
392
|
+
workspaceId,
|
|
393
|
+
actorSubjectId: subjectId,
|
|
394
|
+
targetOrganizationMembershipId: membershipId,
|
|
395
|
+
expectedUpdatedAt: payload.expectedUpdatedAt,
|
|
396
|
+
operationId: payload.operationId,
|
|
397
|
+
}),
|
|
398
|
+
),
|
|
399
|
+
);
|
|
454
400
|
} catch (error) {
|
|
455
401
|
rethrowMembershipError(error);
|
|
456
402
|
}
|
|
@@ -514,19 +460,42 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
514
460
|
"organization id",
|
|
515
461
|
);
|
|
516
462
|
const payload = await parseBody(context, CreateOrganizationInvitationRequest);
|
|
463
|
+
// Bearer construction configuration must fail before the invitation
|
|
464
|
+
// commits. Provider outcomes are journaled durably after this boundary.
|
|
517
465
|
try {
|
|
466
|
+
assertOrganizationUserSetupDeliveryConfigured(deps.settings, deps.managedEmailTransport);
|
|
467
|
+
} catch {
|
|
468
|
+
throw new HTTPException(503, {
|
|
469
|
+
message: "invited-user account setup delivery is not configured on this deployment",
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
const invitation = OrganizationInvitation.parse(
|
|
474
|
+
await createOrganizationInvitation(deps.db, {
|
|
475
|
+
organizationId,
|
|
476
|
+
actorSubjectId: subjectId,
|
|
477
|
+
operationId: payload.operationId,
|
|
478
|
+
targetSubjectId: null,
|
|
479
|
+
targetEmail: payload.email.trim().toLowerCase(),
|
|
480
|
+
...(payload.name === undefined ? {} : { targetName: payload.name }),
|
|
481
|
+
initialWorkspaceIds: payload.initialWorkspaceIds,
|
|
482
|
+
role: payload.role,
|
|
483
|
+
expiresAt: payload.expiresAt,
|
|
484
|
+
}),
|
|
485
|
+
);
|
|
486
|
+
await deliverOrganizationUserSetup(deps, {
|
|
487
|
+
organizationId,
|
|
488
|
+
actorSubjectId: subjectId,
|
|
489
|
+
invitationId: invitation.id,
|
|
490
|
+
invitationOperationId: payload.operationId,
|
|
491
|
+
operationId: payload.operationId,
|
|
492
|
+
});
|
|
518
493
|
return context.json(
|
|
519
494
|
OrganizationInvitation.parse(
|
|
520
|
-
await
|
|
495
|
+
await getOrganizationInvitationForAdministration(deps.db, {
|
|
521
496
|
organizationId,
|
|
522
497
|
actorSubjectId: subjectId,
|
|
523
|
-
|
|
524
|
-
targetSubjectId: null,
|
|
525
|
-
targetEmail: payload.email.trim().toLowerCase(),
|
|
526
|
-
...(payload.name === undefined ? {} : { targetName: payload.name }),
|
|
527
|
-
initialWorkspaceIds: payload.initialWorkspaceIds,
|
|
528
|
-
role: payload.role,
|
|
529
|
-
expiresAt: payload.expiresAt,
|
|
498
|
+
invitationId: invitation.id,
|
|
530
499
|
}),
|
|
531
500
|
),
|
|
532
501
|
201,
|
|
@@ -566,6 +535,45 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
566
535
|
}
|
|
567
536
|
});
|
|
568
537
|
|
|
538
|
+
app.post(
|
|
539
|
+
"/v1/organizations/:organizationId/invitations/:invitationId/delivery/retry",
|
|
540
|
+
async (context) => {
|
|
541
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
542
|
+
const organizationId = parseId(
|
|
543
|
+
OrganizationId,
|
|
544
|
+
context.req.param("organizationId"),
|
|
545
|
+
"organization id",
|
|
546
|
+
);
|
|
547
|
+
const invitationId = parseId(
|
|
548
|
+
InvitationId,
|
|
549
|
+
context.req.param("invitationId"),
|
|
550
|
+
"invitation id",
|
|
551
|
+
);
|
|
552
|
+
const payload = await parseBody(context, RetryOrganizationUserSetupDeliveryRequest);
|
|
553
|
+
try {
|
|
554
|
+
assertOrganizationUserSetupDeliveryConfigured(deps.settings, deps.managedEmailTransport);
|
|
555
|
+
} catch {
|
|
556
|
+
throw new HTTPException(503, {
|
|
557
|
+
message: "invited-user account setup delivery is not configured on this deployment",
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
return context.json(
|
|
562
|
+
OrganizationUserSetupDelivery.parse(
|
|
563
|
+
await deliverOrganizationUserSetup(deps, {
|
|
564
|
+
organizationId,
|
|
565
|
+
actorSubjectId: subjectId,
|
|
566
|
+
invitationId,
|
|
567
|
+
operationId: payload.operationId,
|
|
568
|
+
}),
|
|
569
|
+
),
|
|
570
|
+
);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
rethrowMembershipError(error);
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
|
|
569
577
|
app.post("/v1/organization-invitations/:invitationId/accept", async (context) => {
|
|
570
578
|
const { session, subjectId } = await requireManagedHuman(context, deps);
|
|
571
579
|
const invitationId = parseId(InvitationId, context.req.param("invitationId"), "invitation id");
|
|
@@ -639,26 +647,11 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
639
647
|
"organization id",
|
|
640
648
|
);
|
|
641
649
|
try {
|
|
642
|
-
const members = await listOrganizationMembers(deps.db, {
|
|
643
|
-
organizationId,
|
|
644
|
-
actorSubjectId: subjectId,
|
|
645
|
-
});
|
|
646
|
-
const profiles = await getManagedUserProfilesByIds(
|
|
647
|
-
deps.db,
|
|
648
|
-
members.flatMap((member) =>
|
|
649
|
-
member.subjectId.startsWith("user:") ? [member.subjectId.slice("user:".length)] : [],
|
|
650
|
-
),
|
|
651
|
-
);
|
|
652
|
-
const profileById = new Map(profiles.map((profile) => [profile.id, profile]));
|
|
653
650
|
return context.json(
|
|
654
|
-
|
|
655
|
-
members:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
...member,
|
|
659
|
-
name: profile?.name?.trim() || null,
|
|
660
|
-
email: profile?.email ?? null,
|
|
661
|
-
};
|
|
651
|
+
ListOrganizationAdministrationMembersResponse.parse({
|
|
652
|
+
members: await listOrganizationAdministrationMembers(deps.db, {
|
|
653
|
+
organizationId,
|
|
654
|
+
actorSubjectId: subjectId,
|
|
662
655
|
}),
|
|
663
656
|
}),
|
|
664
657
|
);
|
|
@@ -740,3 +733,67 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
740
733
|
}
|
|
741
734
|
});
|
|
742
735
|
}
|
|
736
|
+
|
|
737
|
+
async function deliverOrganizationUserSetup(
|
|
738
|
+
deps: ApiRouteDeps,
|
|
739
|
+
input: {
|
|
740
|
+
organizationId: string;
|
|
741
|
+
actorSubjectId: string;
|
|
742
|
+
invitationId: string;
|
|
743
|
+
invitationOperationId?: string;
|
|
744
|
+
operationId: string;
|
|
745
|
+
},
|
|
746
|
+
) {
|
|
747
|
+
const claim = await claimOrganizationUserSetupDelivery(deps.db, input);
|
|
748
|
+
if (!claim.claimed) return claim.delivery;
|
|
749
|
+
const setup = await deriveOrganizationUserSetupToken(deps.settings, {
|
|
750
|
+
invitationId: claim.invitationId,
|
|
751
|
+
deliveryId: claim.delivery.id,
|
|
752
|
+
});
|
|
753
|
+
const message = renderOrganizationUserSetupEmail({
|
|
754
|
+
senderEmail: deps.managedEmailTransport.sender,
|
|
755
|
+
recipientEmail: claim.recipientEmail,
|
|
756
|
+
recipientName: claim.recipientName,
|
|
757
|
+
organizationName: claim.organizationName,
|
|
758
|
+
organizationRole: claim.organizationRole,
|
|
759
|
+
sharedWorkspaceAccess: claim.sharedWorkspaceAccess,
|
|
760
|
+
setupUrl: setup.url,
|
|
761
|
+
});
|
|
762
|
+
await prepareOrganizationUserSetupDelivery(deps.db, {
|
|
763
|
+
organizationId: input.organizationId,
|
|
764
|
+
actorSubjectId: input.actorSubjectId,
|
|
765
|
+
deliveryId: claim.delivery.id,
|
|
766
|
+
attemptId: claim.attemptId,
|
|
767
|
+
claimHolderId: claim.claimHolderId,
|
|
768
|
+
tokenDigest: setup.digest,
|
|
769
|
+
payloadDigest: await organizationUserSetupPayloadDigest({
|
|
770
|
+
...message,
|
|
771
|
+
providerIdempotencyScope: deps.managedEmailTransport.idempotency.scope,
|
|
772
|
+
}),
|
|
773
|
+
providerIdempotencyScope: deps.managedEmailTransport.idempotency.scope,
|
|
774
|
+
providerIdempotencyRetentionSeconds: deps.managedEmailTransport.idempotency.retentionSeconds,
|
|
775
|
+
});
|
|
776
|
+
let outcome:
|
|
777
|
+
| { status: "sent"; providerMessageId: string | null }
|
|
778
|
+
| { status: "failed" | "outcome_unknown"; errorClass: string };
|
|
779
|
+
try {
|
|
780
|
+
outcome = await deps.managedEmailTransport.send({
|
|
781
|
+
kind: "organization_user_setup",
|
|
782
|
+
...message,
|
|
783
|
+
idempotencyKey: claim.providerKey,
|
|
784
|
+
});
|
|
785
|
+
} catch {
|
|
786
|
+
outcome = { status: "outcome_unknown", errorClass: "transport_threw" };
|
|
787
|
+
}
|
|
788
|
+
return await settleOrganizationUserSetupDelivery(deps.db, {
|
|
789
|
+
organizationId: input.organizationId,
|
|
790
|
+
actorSubjectId: input.actorSubjectId,
|
|
791
|
+
deliveryId: claim.delivery.id,
|
|
792
|
+
attemptId: claim.attemptId,
|
|
793
|
+
claimHolderId: claim.claimHolderId,
|
|
794
|
+
outcome: outcome.status,
|
|
795
|
+
...(outcome.status === "sent"
|
|
796
|
+
? { providerMessageId: outcome.providerMessageId }
|
|
797
|
+
: { errorClass: outcome.errorClass }),
|
|
798
|
+
});
|
|
799
|
+
}
|