@jskit-ai/users-core 0.1.47 → 0.1.49
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/package.descriptor.mjs +9 -46
- package/package.json +8 -19
- package/src/server/UsersCoreServiceProvider.js +0 -4
- package/src/server/common/registerCommonRepositories.js +0 -5
- package/src/server/common/services/authProfileSyncService.js +28 -7
- package/src/server/common/support/realtimeServiceEvents.js +1 -59
- package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
- package/src/server/registerUsersBootstrap.js +1 -3
- package/src/server/registerUsersCore.js +2 -14
- package/src/server/usersBootstrapContributor.js +10 -85
- package/src/shared/index.js +2 -99
- package/src/shared/settings.js +1 -119
- package/templates/migrations/users_core_generic_initial.cjs +0 -16
- package/test/authProfileSyncService.test.js +19 -10
- package/test/registerServiceRealtimeEvents.test.js +0 -94
- package/test/registerUsersCore.test.js +6 -19
- package/test/repositoryContracts.test.js +1 -11
- package/test/resourcesCanonical.test.js +1 -19
- package/test/settingsFieldRegistriesSingleton.test.js +0 -10
- package/test/usersBootstrapContributor.test.js +20 -38
- package/test/usersRouteRequestInputValidator.test.js +2 -43
- package/test/usersRouteResources.test.js +2 -20
- package/test-support/registerDefaultSettingsFields.js +0 -1
- package/src/server/UsersWorkspacesServiceProvider.js +0 -44
- package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
- package/src/server/common/formatters/workspaceFormatter.js +0 -53
- package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
- package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
- package/src/server/common/repositories/workspacesRepository.js +0 -202
- package/src/server/common/services/workspaceContextService.js +0 -281
- package/src/server/common/support/workspaceRoutePaths.js +0 -17
- package/src/server/common/validators/routeParamsValidator.js +0 -62
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +0 -63
- package/src/server/consoleSettings/consoleService.js +0 -36
- package/src/server/consoleSettings/consoleSettingsActions.js +0 -55
- package/src/server/consoleSettings/consoleSettingsRepository.js +0 -115
- package/src/server/consoleSettings/consoleSettingsService.js +0 -40
- package/src/server/consoleSettings/registerConsoleSettings.js +0 -56
- package/src/server/registerWorkspaceBootstrap.js +0 -27
- package/src/server/registerWorkspaceCore.js +0 -73
- package/src/server/registerWorkspaceRepositories.js +0 -26
- package/src/server/support/resolveWorkspace.js +0 -16
- package/src/server/support/workspaceActionSurfaces.js +0 -135
- package/src/server/support/workspaceInvitationsPolicy.js +0 -45
- package/src/server/support/workspaceRouteInput.js +0 -22
- package/src/server/workspaceBootstrapContributor.js +0 -211
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
- package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
- package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
- package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
- package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
- package/src/shared/resources/consoleSettingsFields.js +0 -54
- package/src/shared/resources/consoleSettingsResource.js +0 -119
- package/src/shared/resources/workspaceMembersResource.js +0 -354
- package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
- package/src/shared/resources/workspaceResource.js +0 -176
- package/src/shared/resources/workspaceSettingsFields.js +0 -59
- package/src/shared/resources/workspaceSettingsResource.js +0 -169
- package/src/shared/roles.js +0 -161
- package/src/shared/support/usersApiPaths.js +0 -43
- package/src/shared/support/usersVisibility.js +0 -42
- package/src/shared/support/workspacePathModel.js +0 -145
- package/src/shared/tenancyMode.js +0 -35
- package/src/shared/tenancyProfile.js +0 -73
- package/templates/migrations/users_core_console_owner.cjs +0 -37
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +0 -11
- package/test/consoleService.test.js +0 -57
- package/test/consoleSettingsService.test.js +0 -86
- package/test/registerWorkspaceDirectory.test.js +0 -31
- package/test/registerWorkspaceSettings.test.js +0 -40
- package/test/roles.test.js +0 -159
- package/test/tenancyProfile.test.js +0 -67
- package/test/usersApiPaths.test.js +0 -49
- package/test/usersRouteValidators.test.js +0 -49
- package/test/usersVisibility.test.js +0 -27
- package/test/workspaceActionContextContributor.test.js +0 -344
- package/test/workspaceActionSurfaces.test.js +0 -105
- package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
- package/test/workspaceBootstrapContributor.test.js +0 -154
- package/test/workspaceInvitationsPolicy.test.js +0 -71
- package/test/workspaceInvitesRepository.test.js +0 -111
- package/test/workspaceMembersService.test.js +0 -398
- package/test/workspacePathModel.test.js +0 -93
- package/test/workspacePendingInvitationsResource.test.js +0 -38
- package/test/workspacePendingInvitationsService.test.js +0 -151
- package/test/workspaceRouteVisibilityResolver.test.js +0 -83
- package/test/workspaceService.test.js +0 -546
- package/test/workspaceSettingsActions.test.js +0 -52
- package/test/workspaceSettingsRepository.test.js +0 -202
- package/test/workspaceSettingsResource.test.js +0 -169
- package/test/workspaceSettingsService.test.js +0 -140
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { resolveWorkspace } from "../support/resolveWorkspace.js";
|
|
2
|
-
import { resolveActionUser } from "../common/support/resolveActionUser.js";
|
|
3
|
-
import { workspaceMembersResource } from "../../shared/resources/workspaceMembersResource.js";
|
|
4
|
-
import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
|
|
5
|
-
|
|
6
|
-
const workspaceMembersActions = Object.freeze([
|
|
7
|
-
{
|
|
8
|
-
id: "workspace.roles.list",
|
|
9
|
-
version: 1,
|
|
10
|
-
kind: "query",
|
|
11
|
-
channels: ["api", "automation", "internal"],
|
|
12
|
-
surfacesFrom: "workspace",
|
|
13
|
-
permission: {
|
|
14
|
-
require: "all",
|
|
15
|
-
permissions: ["workspace.roles.view"]
|
|
16
|
-
},
|
|
17
|
-
inputValidator: workspaceSlugParamsValidator,
|
|
18
|
-
outputValidator: workspaceMembersResource.operations.rolesList.outputValidator,
|
|
19
|
-
idempotency: "none",
|
|
20
|
-
audit: {
|
|
21
|
-
actionName: "workspace.roles.list"
|
|
22
|
-
},
|
|
23
|
-
observability: {},
|
|
24
|
-
async execute(_input, context, deps) {
|
|
25
|
-
return deps.workspaceMembersService.listRoles({ context });
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: "workspace.members.list",
|
|
30
|
-
version: 1,
|
|
31
|
-
kind: "query",
|
|
32
|
-
channels: ["api", "automation", "internal"],
|
|
33
|
-
surfacesFrom: "workspace",
|
|
34
|
-
permission: {
|
|
35
|
-
require: "all",
|
|
36
|
-
permissions: ["workspace.members.view"]
|
|
37
|
-
},
|
|
38
|
-
inputValidator: workspaceSlugParamsValidator,
|
|
39
|
-
outputValidator: workspaceMembersResource.operations.membersList.outputValidator,
|
|
40
|
-
idempotency: "none",
|
|
41
|
-
audit: {
|
|
42
|
-
actionName: "workspace.members.list"
|
|
43
|
-
},
|
|
44
|
-
observability: {},
|
|
45
|
-
async execute(input, context, deps) {
|
|
46
|
-
return deps.workspaceMembersService.listMembers(resolveWorkspace(context, input), {
|
|
47
|
-
context
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: "workspace.member.role.update",
|
|
53
|
-
version: 1,
|
|
54
|
-
kind: "command",
|
|
55
|
-
channels: ["api", "automation", "internal"],
|
|
56
|
-
surfacesFrom: "workspace",
|
|
57
|
-
permission: {
|
|
58
|
-
require: "all",
|
|
59
|
-
permissions: ["workspace.members.manage"]
|
|
60
|
-
},
|
|
61
|
-
inputValidator: [workspaceSlugParamsValidator, workspaceMembersResource.operations.updateMemberRole.inputValidator],
|
|
62
|
-
outputValidator: workspaceMembersResource.operations.updateMemberRole.outputValidator,
|
|
63
|
-
idempotency: "optional",
|
|
64
|
-
audit: {
|
|
65
|
-
actionName: "workspace.member.role.update"
|
|
66
|
-
},
|
|
67
|
-
observability: {},
|
|
68
|
-
async execute(input, context, deps) {
|
|
69
|
-
return deps.workspaceMembersService.updateMemberRole(resolveWorkspace(context, input), {
|
|
70
|
-
memberUserId: input.memberUserId,
|
|
71
|
-
roleSid: input.roleSid
|
|
72
|
-
}, {
|
|
73
|
-
context
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: "workspace.member.remove",
|
|
79
|
-
version: 1,
|
|
80
|
-
kind: "command",
|
|
81
|
-
channels: ["api", "automation", "internal"],
|
|
82
|
-
surfacesFrom: "workspace",
|
|
83
|
-
permission: {
|
|
84
|
-
require: "all",
|
|
85
|
-
permissions: ["workspace.members.manage"]
|
|
86
|
-
},
|
|
87
|
-
inputValidator: [workspaceSlugParamsValidator, workspaceMembersResource.operations.removeMember.inputValidator],
|
|
88
|
-
outputValidator: workspaceMembersResource.operations.removeMember.outputValidator,
|
|
89
|
-
idempotency: "optional",
|
|
90
|
-
audit: {
|
|
91
|
-
actionName: "workspace.member.remove"
|
|
92
|
-
},
|
|
93
|
-
observability: {},
|
|
94
|
-
async execute(input, context, deps) {
|
|
95
|
-
return deps.workspaceMembersService.removeMember(resolveWorkspace(context, input), {
|
|
96
|
-
memberUserId: input.memberUserId
|
|
97
|
-
}, {
|
|
98
|
-
context
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: "workspace.invites.list",
|
|
104
|
-
version: 1,
|
|
105
|
-
kind: "query",
|
|
106
|
-
channels: ["api", "automation", "internal"],
|
|
107
|
-
surfacesFrom: "workspace",
|
|
108
|
-
permission: {
|
|
109
|
-
require: "all",
|
|
110
|
-
permissions: ["workspace.members.view"]
|
|
111
|
-
},
|
|
112
|
-
inputValidator: workspaceSlugParamsValidator,
|
|
113
|
-
outputValidator: workspaceMembersResource.operations.invitesList.outputValidator,
|
|
114
|
-
idempotency: "none",
|
|
115
|
-
audit: {
|
|
116
|
-
actionName: "workspace.invites.list"
|
|
117
|
-
},
|
|
118
|
-
observability: {},
|
|
119
|
-
async execute(input, context, deps) {
|
|
120
|
-
return deps.workspaceMembersService.listInvites(resolveWorkspace(context, input), {
|
|
121
|
-
context
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
id: "workspace.invite.create",
|
|
127
|
-
version: 1,
|
|
128
|
-
kind: "command",
|
|
129
|
-
channels: ["api", "assistant_tool", "automation", "internal"],
|
|
130
|
-
surfacesFrom: "workspace",
|
|
131
|
-
permission: {
|
|
132
|
-
require: "all",
|
|
133
|
-
permissions: ["workspace.members.invite"]
|
|
134
|
-
},
|
|
135
|
-
inputValidator: [workspaceSlugParamsValidator, workspaceMembersResource.operations.createInvite.bodyValidator],
|
|
136
|
-
outputValidator: workspaceMembersResource.operations.createInvite.outputValidator,
|
|
137
|
-
idempotency: "optional",
|
|
138
|
-
audit: {
|
|
139
|
-
actionName: "workspace.invite.create"
|
|
140
|
-
},
|
|
141
|
-
observability: {},
|
|
142
|
-
extensions: {
|
|
143
|
-
assistant: {
|
|
144
|
-
description: "Invite a person to the workspace."
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
async execute(input, context, deps) {
|
|
148
|
-
return deps.workspaceMembersService.createInvite(
|
|
149
|
-
resolveWorkspace(context, input),
|
|
150
|
-
resolveActionUser(context, input),
|
|
151
|
-
{
|
|
152
|
-
email: input.email,
|
|
153
|
-
roleSid: input.roleSid
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
context
|
|
157
|
-
}
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
id: "workspace.invite.revoke",
|
|
163
|
-
version: 1,
|
|
164
|
-
kind: "command",
|
|
165
|
-
channels: ["api", "automation", "internal"],
|
|
166
|
-
surfacesFrom: "workspace",
|
|
167
|
-
permission: {
|
|
168
|
-
require: "all",
|
|
169
|
-
permissions: ["workspace.invites.revoke"]
|
|
170
|
-
},
|
|
171
|
-
inputValidator: [workspaceSlugParamsValidator, workspaceMembersResource.operations.revokeInvite.inputValidator],
|
|
172
|
-
outputValidator: workspaceMembersResource.operations.revokeInvite.outputValidator,
|
|
173
|
-
idempotency: "optional",
|
|
174
|
-
audit: {
|
|
175
|
-
actionName: "workspace.invite.revoke"
|
|
176
|
-
},
|
|
177
|
-
observability: {},
|
|
178
|
-
async execute(input, context, deps) {
|
|
179
|
-
return deps.workspaceMembersService.revokeInvite(resolveWorkspace(context, input), input.inviteId, {
|
|
180
|
-
context
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
export { workspaceMembersActions };
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
-
import { buildInviteToken, hashInviteToken } from "@jskit-ai/auth-core/server/inviteTokens";
|
|
3
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
4
|
-
import { OWNER_ROLE_ID, createWorkspaceRoleCatalog, cloneWorkspaceRoleCatalog } from "../../shared/roles.js";
|
|
5
|
-
|
|
6
|
-
function createService({
|
|
7
|
-
workspaceMembershipsRepository,
|
|
8
|
-
workspaceInvitesRepository,
|
|
9
|
-
inviteExpiresInMs,
|
|
10
|
-
roleCatalog = null,
|
|
11
|
-
workspaceInvitationsEnabled = true
|
|
12
|
-
} = {}) {
|
|
13
|
-
if (!workspaceMembershipsRepository || !workspaceInvitesRepository) {
|
|
14
|
-
throw new Error("workspaceMembersService requires membership and invite repositories.");
|
|
15
|
-
}
|
|
16
|
-
const resolvedInviteExpiresInMs = Number(inviteExpiresInMs);
|
|
17
|
-
if (!Number.isInteger(resolvedInviteExpiresInMs) || resolvedInviteExpiresInMs < 1) {
|
|
18
|
-
throw new Error("workspaceMembersService requires inviteExpiresInMs.");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const resolvedRoleCatalog = roleCatalog && typeof roleCatalog === "object" ? roleCatalog : createWorkspaceRoleCatalog();
|
|
22
|
-
const assignableRoleIds = Array.isArray(resolvedRoleCatalog.assignableRoleIds)
|
|
23
|
-
? [...resolvedRoleCatalog.assignableRoleIds]
|
|
24
|
-
: [];
|
|
25
|
-
const resolvedWorkspaceInvitationsEnabled = workspaceInvitationsEnabled === true;
|
|
26
|
-
|
|
27
|
-
function ensureWorkspaceInvitationsEnabled() {
|
|
28
|
-
if (resolvedWorkspaceInvitationsEnabled) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
throw new AppError(403, "Workspace invitations are disabled.");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function withRoleCatalog(payload = {}) {
|
|
35
|
-
return {
|
|
36
|
-
...payload,
|
|
37
|
-
roleCatalog: cloneWorkspaceRoleCatalog({
|
|
38
|
-
...resolvedRoleCatalog,
|
|
39
|
-
assignableRoleIds
|
|
40
|
-
})
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function listRoles(options = {}) {
|
|
45
|
-
return cloneWorkspaceRoleCatalog({
|
|
46
|
-
...resolvedRoleCatalog,
|
|
47
|
-
assignableRoleIds
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function listMembersPayload(workspace, options = {}) {
|
|
52
|
-
const members = await workspaceMembershipsRepository.listActiveByWorkspaceId(workspace.id, options);
|
|
53
|
-
|
|
54
|
-
return withRoleCatalog({
|
|
55
|
-
workspace,
|
|
56
|
-
members
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function listMembers(workspace, options = {}) {
|
|
61
|
-
return listMembersPayload(workspace, options);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function updateMemberRole(workspace, payload = {}, options = {}) {
|
|
65
|
-
const memberUserId = normalizeRecordId(payload.memberUserId, { fallback: null });
|
|
66
|
-
const roleSid = payload.roleSid;
|
|
67
|
-
if (!memberUserId) {
|
|
68
|
-
throw new AppError(400, "Validation failed.");
|
|
69
|
-
}
|
|
70
|
-
if (!assignableRoleIds.includes(roleSid)) {
|
|
71
|
-
throw new AppError(400, "Validation failed.", {
|
|
72
|
-
details: {
|
|
73
|
-
fieldErrors: {
|
|
74
|
-
roleSid: "Role is not assignable."
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const existingMembership = await workspaceMembershipsRepository.findByWorkspaceIdAndUserId(workspace.id, memberUserId, options);
|
|
81
|
-
if (!existingMembership || existingMembership.status !== "active") {
|
|
82
|
-
throw new AppError(404, "Member not found.");
|
|
83
|
-
}
|
|
84
|
-
if (memberUserId === normalizeRecordId(workspace.ownerUserId, { fallback: null }) || existingMembership.roleSid === OWNER_ROLE_ID) {
|
|
85
|
-
throw new AppError(409, "Cannot change workspace owner role.");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
await workspaceMembershipsRepository.upsertMembership(
|
|
89
|
-
workspace.id,
|
|
90
|
-
memberUserId,
|
|
91
|
-
{
|
|
92
|
-
roleSid,
|
|
93
|
-
status: "active"
|
|
94
|
-
},
|
|
95
|
-
options
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
return listMembersPayload(workspace, options);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function removeMember(workspace, payload = {}, options = {}) {
|
|
102
|
-
const memberUserId = normalizeRecordId(payload.memberUserId, { fallback: null });
|
|
103
|
-
if (!memberUserId) {
|
|
104
|
-
throw new AppError(400, "Validation failed.");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const existingMembership = await workspaceMembershipsRepository.findByWorkspaceIdAndUserId(workspace.id, memberUserId, options);
|
|
108
|
-
if (!existingMembership || existingMembership.status !== "active") {
|
|
109
|
-
throw new AppError(404, "Member not found.");
|
|
110
|
-
}
|
|
111
|
-
if (memberUserId === normalizeRecordId(workspace.ownerUserId, { fallback: null }) || existingMembership.roleSid === OWNER_ROLE_ID) {
|
|
112
|
-
throw new AppError(409, "Cannot remove workspace owner.");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await workspaceMembershipsRepository.upsertMembership(
|
|
116
|
-
workspace.id,
|
|
117
|
-
memberUserId,
|
|
118
|
-
{
|
|
119
|
-
roleSid: existingMembership.roleSid,
|
|
120
|
-
status: "revoked"
|
|
121
|
-
},
|
|
122
|
-
options
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
return listMembersPayload(workspace, options);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function listInvitesPayload(workspace, options = {}) {
|
|
129
|
-
ensureWorkspaceInvitationsEnabled();
|
|
130
|
-
const invites = await workspaceInvitesRepository.listPendingByWorkspaceIdWithWorkspace(workspace.id, options);
|
|
131
|
-
|
|
132
|
-
return withRoleCatalog({
|
|
133
|
-
workspace,
|
|
134
|
-
invites
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function listInvites(workspace, options = {}) {
|
|
139
|
-
return listInvitesPayload(workspace, options);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function createInvite(workspace, user, payload = {}, options = {}) {
|
|
143
|
-
const email = payload.email;
|
|
144
|
-
const roleSid = payload.roleSid;
|
|
145
|
-
if (!assignableRoleIds.includes(roleSid)) {
|
|
146
|
-
throw new AppError(400, "Validation failed.", {
|
|
147
|
-
details: {
|
|
148
|
-
fieldErrors: {
|
|
149
|
-
roleSid: "Role is not assignable."
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const token = buildInviteToken();
|
|
156
|
-
const tokenHash = hashInviteToken(token);
|
|
157
|
-
await workspaceInvitesRepository.expirePendingByWorkspaceIdAndEmail(workspace.id, email, options);
|
|
158
|
-
const createdInvite = await workspaceInvitesRepository.insert(
|
|
159
|
-
{
|
|
160
|
-
workspaceId: workspace.id,
|
|
161
|
-
email,
|
|
162
|
-
roleSid,
|
|
163
|
-
status: "pending",
|
|
164
|
-
tokenHash,
|
|
165
|
-
invitedByUserId: normalizeRecordId(user?.id, { fallback: null }),
|
|
166
|
-
expiresAt: new Date(Date.now() + resolvedInviteExpiresInMs).toISOString()
|
|
167
|
-
},
|
|
168
|
-
options
|
|
169
|
-
);
|
|
170
|
-
const createdInviteId = normalizeRecordId(createdInvite?.id, { fallback: null });
|
|
171
|
-
if (!createdInviteId) {
|
|
172
|
-
throw new Error("workspaceMembersService.createInvite expected repository to return created invite id.");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const response = await listInvitesPayload(workspace, options);
|
|
176
|
-
return {
|
|
177
|
-
...response,
|
|
178
|
-
inviteTokenPreview: token,
|
|
179
|
-
createdInviteId
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function revokeInvite(workspace, inviteId, options = {}) {
|
|
184
|
-
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
185
|
-
if (!normalizedInviteId) {
|
|
186
|
-
throw new AppError(400, "Validation failed.");
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const invite = await workspaceInvitesRepository.findPendingByIdForWorkspace(
|
|
190
|
-
normalizedInviteId,
|
|
191
|
-
workspace.id,
|
|
192
|
-
options
|
|
193
|
-
);
|
|
194
|
-
if (!invite) {
|
|
195
|
-
throw new AppError(404, "Invite not found.");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
await workspaceInvitesRepository.revokeById(normalizedInviteId, options);
|
|
199
|
-
const revokedInviteId = normalizeRecordId(invite?.id, { fallback: null });
|
|
200
|
-
if (!revokedInviteId) {
|
|
201
|
-
throw new Error("workspaceMembersService.revokeInvite expected repository to return pending invite id.");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const response = await listInvitesPayload(workspace, options);
|
|
205
|
-
return {
|
|
206
|
-
...response,
|
|
207
|
-
revokedInviteId
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return Object.freeze({
|
|
212
|
-
listRoles,
|
|
213
|
-
listMembers,
|
|
214
|
-
updateMemberRole,
|
|
215
|
-
removeMember,
|
|
216
|
-
listInvites,
|
|
217
|
-
createInvite,
|
|
218
|
-
revokeInvite
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export { createService };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
|
|
2
|
-
import { workspaceMembersResource } from "../../shared/resources/workspaceMembersResource.js";
|
|
3
|
-
import { workspacePendingInvitationsResource } from "../../shared/resources/workspacePendingInvitationsResource.js";
|
|
4
|
-
|
|
5
|
-
function bootWorkspacePendingInvitations(app) {
|
|
6
|
-
if (!app || typeof app.make !== "function") {
|
|
7
|
-
throw new Error("bootWorkspacePendingInvitations requires application make().");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const router = app.make("jskit.http.router");
|
|
11
|
-
|
|
12
|
-
router.register(
|
|
13
|
-
"GET",
|
|
14
|
-
"/api/workspace/invitations/pending",
|
|
15
|
-
{
|
|
16
|
-
auth: "required",
|
|
17
|
-
meta: {
|
|
18
|
-
tags: ["workspace"],
|
|
19
|
-
summary: "List pending workspace invitations for authenticated user"
|
|
20
|
-
},
|
|
21
|
-
responseValidators: withStandardErrorResponses({
|
|
22
|
-
200: workspacePendingInvitationsResource.operations.list.outputValidator
|
|
23
|
-
})
|
|
24
|
-
},
|
|
25
|
-
async function (request, reply) {
|
|
26
|
-
const response = await request.executeAction({
|
|
27
|
-
actionId: "workspace.invitations.pending.list"
|
|
28
|
-
});
|
|
29
|
-
reply.code(200).send(response);
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
router.register(
|
|
34
|
-
"POST",
|
|
35
|
-
"/api/workspace/invitations/redeem",
|
|
36
|
-
{
|
|
37
|
-
auth: "required",
|
|
38
|
-
meta: {
|
|
39
|
-
tags: ["workspace"],
|
|
40
|
-
summary: "Accept or refuse a workspace invitation using an invite token"
|
|
41
|
-
},
|
|
42
|
-
bodyValidator: workspaceMembersResource.operations.redeemInvite.bodyValidator,
|
|
43
|
-
responseValidators: withStandardErrorResponses(
|
|
44
|
-
{
|
|
45
|
-
200: workspaceMembersResource.operations.redeemInvite.outputValidator
|
|
46
|
-
},
|
|
47
|
-
{ includeValidation400: true }
|
|
48
|
-
)
|
|
49
|
-
},
|
|
50
|
-
async function (request, reply) {
|
|
51
|
-
const response = await request.executeAction({
|
|
52
|
-
actionId: "workspace.invite.redeem",
|
|
53
|
-
input: {
|
|
54
|
-
payload: request.input.body
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
reply.code(200).send(response);
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export { bootWorkspacePendingInvitations };
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
2
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
-
import { createService } from "./workspacePendingInvitationsService.js";
|
|
4
|
-
import { workspacePendingInvitationsActions } from "./workspacePendingInvitationsActions.js";
|
|
5
|
-
import { deepFreeze } from "../common/support/deepFreeze.js";
|
|
6
|
-
|
|
7
|
-
function workspaceAudienceFromEntityId({ event } = {}) {
|
|
8
|
-
const workspaceId = normalizeRecordId(event?.entityId, { fallback: null });
|
|
9
|
-
if (!workspaceId) {
|
|
10
|
-
return "none";
|
|
11
|
-
}
|
|
12
|
-
return {
|
|
13
|
-
workspaceId
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function actorUserEntityId({ options } = {}) {
|
|
18
|
-
return normalizeRecordId(options?.context?.actor?.id, { fallback: "" });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function createActorUserEvent({ source, entity, realtimeEvent }) {
|
|
22
|
-
return {
|
|
23
|
-
type: "entity.changed",
|
|
24
|
-
source,
|
|
25
|
-
entity,
|
|
26
|
-
operation: "updated",
|
|
27
|
-
entityId: actorUserEntityId,
|
|
28
|
-
realtime: {
|
|
29
|
-
event: realtimeEvent,
|
|
30
|
-
audience: "actor_user"
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function createWorkspaceAudienceEvent({ entity, realtimeEvent }) {
|
|
36
|
-
return {
|
|
37
|
-
type: "entity.changed",
|
|
38
|
-
source: "workspace",
|
|
39
|
-
entity,
|
|
40
|
-
operation: "updated",
|
|
41
|
-
entityId: ({ result }) => normalizeRecordId(result?.workspaceId, { fallback: "" }),
|
|
42
|
-
realtime: {
|
|
43
|
-
event: realtimeEvent,
|
|
44
|
-
audience: workspaceAudienceFromEntityId
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function createInviteDecisionEvents({ includeDirectoryAndMembers = false } = {}) {
|
|
50
|
-
const events = [
|
|
51
|
-
createActorUserEvent({
|
|
52
|
-
source: "workspace",
|
|
53
|
-
entity: "invitation",
|
|
54
|
-
realtimeEvent: "workspace.invitations.pending.changed"
|
|
55
|
-
}),
|
|
56
|
-
createActorUserEvent({
|
|
57
|
-
source: "users",
|
|
58
|
-
entity: "bootstrap",
|
|
59
|
-
realtimeEvent: "users.bootstrap.changed"
|
|
60
|
-
})
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
if (includeDirectoryAndMembers) {
|
|
64
|
-
events.push(
|
|
65
|
-
createActorUserEvent({
|
|
66
|
-
source: "workspace",
|
|
67
|
-
entity: "directory",
|
|
68
|
-
realtimeEvent: "workspaces.changed"
|
|
69
|
-
}),
|
|
70
|
-
createWorkspaceAudienceEvent({
|
|
71
|
-
entity: "member",
|
|
72
|
-
realtimeEvent: "workspace.members.changed"
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
events.push(
|
|
78
|
-
createWorkspaceAudienceEvent({
|
|
79
|
-
entity: "invite",
|
|
80
|
-
realtimeEvent: "workspace.invites.changed"
|
|
81
|
-
})
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
return events;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function registerWorkspacePendingInvitations(app) {
|
|
88
|
-
if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
|
|
89
|
-
throw new Error("registerWorkspacePendingInvitations requires application singleton()/service()/actions().");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
app.service(
|
|
93
|
-
"users.workspace.pending-invitations.service",
|
|
94
|
-
(scope) =>
|
|
95
|
-
createService({
|
|
96
|
-
workspaceInvitesRepository: scope.make("workspaceInvitesRepository"),
|
|
97
|
-
workspaceMembershipsRepository: scope.make("workspaceMembershipsRepository")
|
|
98
|
-
}),
|
|
99
|
-
{
|
|
100
|
-
events: deepFreeze({
|
|
101
|
-
acceptInviteByToken: createInviteDecisionEvents({
|
|
102
|
-
includeDirectoryAndMembers: true
|
|
103
|
-
}),
|
|
104
|
-
refuseInviteByToken: createInviteDecisionEvents()
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
app.actions(
|
|
110
|
-
withActionDefaults(workspacePendingInvitationsActions, {
|
|
111
|
-
domain: "workspace",
|
|
112
|
-
dependencies: {
|
|
113
|
-
workspacePendingInvitationsService: "users.workspace.pending-invitations.service"
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export { registerWorkspacePendingInvitations };
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EMPTY_INPUT_VALIDATOR
|
|
3
|
-
} from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
|
|
4
|
-
import { workspaceMembersResource } from "../../shared/resources/workspaceMembersResource.js";
|
|
5
|
-
import { workspacePendingInvitationsResource } from "../../shared/resources/workspacePendingInvitationsResource.js";
|
|
6
|
-
import { resolveActionUser } from "../common/support/resolveActionUser.js";
|
|
7
|
-
|
|
8
|
-
const workspacePendingInvitationsActions = Object.freeze([
|
|
9
|
-
{
|
|
10
|
-
id: "workspace.invitations.pending.list",
|
|
11
|
-
version: 1,
|
|
12
|
-
kind: "query",
|
|
13
|
-
channels: ["api", "automation", "internal"],
|
|
14
|
-
surfacesFrom: "enabled",
|
|
15
|
-
permission: {
|
|
16
|
-
require: "authenticated"
|
|
17
|
-
},
|
|
18
|
-
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
19
|
-
outputValidator: workspacePendingInvitationsResource.operations.list.outputValidator,
|
|
20
|
-
idempotency: "none",
|
|
21
|
-
audit: {
|
|
22
|
-
actionName: "workspace.invitations.pending.list"
|
|
23
|
-
},
|
|
24
|
-
observability: {},
|
|
25
|
-
async execute(input, context, deps) {
|
|
26
|
-
return {
|
|
27
|
-
pendingInvites: await deps.workspacePendingInvitationsService.listPendingInvitesForUser(resolveActionUser(context, input), {
|
|
28
|
-
context
|
|
29
|
-
})
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "workspace.invite.redeem",
|
|
35
|
-
version: 1,
|
|
36
|
-
kind: "command",
|
|
37
|
-
channels: ["api", "automation", "internal"],
|
|
38
|
-
surfacesFrom: "enabled",
|
|
39
|
-
permission: {
|
|
40
|
-
require: "authenticated"
|
|
41
|
-
},
|
|
42
|
-
inputValidator: {
|
|
43
|
-
payload: workspaceMembersResource.operations.redeemInvite.bodyValidator
|
|
44
|
-
},
|
|
45
|
-
outputValidator: workspaceMembersResource.operations.redeemInvite.outputValidator,
|
|
46
|
-
idempotency: "optional",
|
|
47
|
-
audit: {
|
|
48
|
-
actionName: "workspace.invite.redeem"
|
|
49
|
-
},
|
|
50
|
-
observability: {},
|
|
51
|
-
async execute(input, context, deps) {
|
|
52
|
-
const payload = input.payload || {};
|
|
53
|
-
const user = resolveActionUser(context, input);
|
|
54
|
-
|
|
55
|
-
if (payload.decision === "accept") {
|
|
56
|
-
return deps.workspacePendingInvitationsService.acceptInviteByToken({
|
|
57
|
-
user,
|
|
58
|
-
token: payload.token
|
|
59
|
-
}, {
|
|
60
|
-
context
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return deps.workspacePendingInvitationsService.refuseInviteByToken({
|
|
65
|
-
user,
|
|
66
|
-
token: payload.token
|
|
67
|
-
}, {
|
|
68
|
-
context
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
]);
|
|
73
|
-
|
|
74
|
-
export { workspacePendingInvitationsActions };
|