@jskit-ai/users-core 0.1.48 → 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 +7 -7
- package/package.json +7 -17
- 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 +0 -1
- package/src/server/registerUsersCore.js +2 -14
- package/src/server/usersBootstrapContributor.js +2 -64
- package/src/shared/index.js +2 -99
- package/src/shared/settings.js +1 -119
- package/test/authProfileSyncService.test.js +19 -10
- package/test/registerServiceRealtimeEvents.test.js +0 -86
- package/test/registerUsersCore.test.js +6 -15
- package/test/repositoryContracts.test.js +1 -9
- package/test/resourcesCanonical.test.js +0 -16
- package/test/settingsFieldRegistriesSingleton.test.js +0 -5
- package/test/usersBootstrapContributor.test.js +2 -26
- package/test/usersRouteResources.test.js +0 -16
- 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/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 -118
- package/src/server/support/workspaceInvitationsPolicy.js +0 -45
- package/src/server/support/workspaceRouteInput.js +0 -22
- package/src/server/workspaceBootstrapContributor.js +0 -212
- 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/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/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 -85
- 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
|
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createService } from "../src/server/common/services/authProfileSyncService.js";
|
|
4
4
|
|
|
5
|
-
test("authProfileSyncService.syncIdentityProfile uses shared transaction for profile upsert and
|
|
5
|
+
test("authProfileSyncService.syncIdentityProfile uses shared transaction for profile upsert and lifecycle contributors", async () => {
|
|
6
6
|
const calls = [];
|
|
7
7
|
const transaction = { trxId: "tx-1" };
|
|
8
8
|
|
|
@@ -33,11 +33,14 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
33
33
|
return { userId: Number(userId) };
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
lifecycleContributors: [
|
|
37
|
+
{
|
|
38
|
+
contributorId: "test.lifecycle",
|
|
39
|
+
async afterIdentityProfileSynced({ created, options = {} } = {}) {
|
|
40
|
+
calls.push({ step: "lifecycle", created, trx: options.trx || null });
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
|
-
|
|
43
|
+
]
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
const profile = await service.syncIdentityProfile({
|
|
@@ -52,7 +55,8 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
52
55
|
assert.equal(calls[1].step, "find");
|
|
53
56
|
assert.equal(calls[2].step, "upsert");
|
|
54
57
|
assert.equal(calls[3].step, "ensureUserSettings");
|
|
55
|
-
assert.equal(calls[4].step, "
|
|
58
|
+
assert.equal(calls[4].step, "lifecycle");
|
|
59
|
+
assert.equal(calls[4].created, true);
|
|
56
60
|
assert.equal(calls[1].trx, transaction);
|
|
57
61
|
assert.equal(calls[2].trx, transaction);
|
|
58
62
|
assert.equal(calls[3].trx, transaction);
|
|
@@ -87,11 +91,16 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
87
91
|
return { userId: "7" };
|
|
88
92
|
}
|
|
89
93
|
},
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
lifecycleContributors: [
|
|
95
|
+
{
|
|
96
|
+
contributorId: "test.lifecycle",
|
|
97
|
+
async afterIdentityProfileSynced({ created } = {}) {
|
|
98
|
+
if (created) {
|
|
99
|
+
provisionCalls += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
93
102
|
}
|
|
94
|
-
|
|
103
|
+
]
|
|
95
104
|
});
|
|
96
105
|
|
|
97
106
|
const profile = await service.syncIdentityProfile({
|
|
@@ -3,8 +3,6 @@ import test from "node:test";
|
|
|
3
3
|
import { registerAccountProfile } from "../src/server/accountProfile/registerAccountProfile.js";
|
|
4
4
|
import { registerAccountPreferences } from "../src/server/accountPreferences/registerAccountPreferences.js";
|
|
5
5
|
import { registerAccountNotifications } from "../src/server/accountNotifications/registerAccountNotifications.js";
|
|
6
|
-
import { registerWorkspaceMembers } from "../src/server/workspaceMembers/registerWorkspaceMembers.js";
|
|
7
|
-
import { registerWorkspacePendingInvitations } from "../src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js";
|
|
8
6
|
|
|
9
7
|
function createAppDouble() {
|
|
10
8
|
const serviceCalls = [];
|
|
@@ -57,87 +55,3 @@ test("account register functions publish account.settings.changed for update ope
|
|
|
57
55
|
assert.equal(notifications?.metadata?.events?.updateNotifications?.[0]?.realtime?.event, "account.settings.changed");
|
|
58
56
|
assert.equal(notifications?.metadata?.events?.updateNotifications?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
59
57
|
});
|
|
60
|
-
|
|
61
|
-
test("workspace register functions publish members/invites/workspace-list realtime events", async () => {
|
|
62
|
-
const membersApp = createAppDouble();
|
|
63
|
-
registerWorkspaceMembers(membersApp.app);
|
|
64
|
-
const members = findServiceCall(membersApp.serviceCalls, "users.workspace.members.service");
|
|
65
|
-
assert.equal(members?.metadata?.events?.updateMemberRole?.[0]?.realtime?.event, "workspace.members.changed");
|
|
66
|
-
assert.equal(members?.metadata?.events?.updateMemberRole?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
67
|
-
assert.equal(members?.metadata?.events?.removeMember?.[0]?.realtime?.event, "workspace.members.changed");
|
|
68
|
-
assert.equal(members?.metadata?.events?.removeMember?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
69
|
-
assert.equal(members?.metadata?.events?.createInvite?.[0]?.realtime?.event, "workspace.invites.changed");
|
|
70
|
-
assert.equal(members?.metadata?.events?.createInvite?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
71
|
-
assert.equal(members?.metadata?.events?.createInvite?.[1]?.entityId?.({ result: { createdInviteId: "91" } }), "91");
|
|
72
|
-
assert.equal(members?.metadata?.events?.createInvite?.[1]?.realtime?.audience?.preset, "event_scope");
|
|
73
|
-
assert.equal(typeof members?.metadata?.events?.createInvite?.[1]?.realtime?.audience?.userQuery, "function");
|
|
74
|
-
const createInviteAudienceQueryResult = await members?.metadata?.events?.createInvite?.[1]?.realtime?.audience?.userQuery({
|
|
75
|
-
knex() {
|
|
76
|
-
return {
|
|
77
|
-
join() {
|
|
78
|
-
return this;
|
|
79
|
-
},
|
|
80
|
-
where(field, value) {
|
|
81
|
-
assert.equal(field, "wi.id");
|
|
82
|
-
assert.equal(value, "91");
|
|
83
|
-
return this;
|
|
84
|
-
},
|
|
85
|
-
async first() {
|
|
86
|
-
return {
|
|
87
|
-
user_id: 55
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
event: {
|
|
93
|
-
entityId: "91"
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
assert.deepEqual(createInviteAudienceQueryResult, [{ userId: "55" }]);
|
|
97
|
-
assert.equal(members?.metadata?.events?.revokeInvite?.[0]?.realtime?.event, "workspace.invites.changed");
|
|
98
|
-
assert.equal(members?.metadata?.events?.revokeInvite?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
99
|
-
assert.equal(members?.metadata?.events?.revokeInvite?.[1]?.entityId?.({ result: { revokedInviteId: "19" } }), "19");
|
|
100
|
-
assert.equal(members?.metadata?.events?.revokeInvite?.[1]?.realtime?.audience?.preset, "event_scope");
|
|
101
|
-
assert.equal(typeof members?.metadata?.events?.revokeInvite?.[1]?.realtime?.audience?.userQuery, "function");
|
|
102
|
-
|
|
103
|
-
const pendingApp = createAppDouble();
|
|
104
|
-
registerWorkspacePendingInvitations(pendingApp.app);
|
|
105
|
-
const pending = findServiceCall(pendingApp.serviceCalls, "users.workspace.pending-invitations.service");
|
|
106
|
-
const acceptInviteEvents = Array.isArray(pending?.metadata?.events?.acceptInviteByToken)
|
|
107
|
-
? pending.metadata.events.acceptInviteByToken
|
|
108
|
-
: [];
|
|
109
|
-
const acceptInviteRealtimeEvents = acceptInviteEvents.map((entry) => entry?.realtime?.event).filter(Boolean);
|
|
110
|
-
assert.ok(acceptInviteRealtimeEvents.includes("workspace.invitations.pending.changed"));
|
|
111
|
-
assert.ok(acceptInviteRealtimeEvents.includes("users.bootstrap.changed"));
|
|
112
|
-
assert.ok(acceptInviteRealtimeEvents.includes("workspaces.changed"));
|
|
113
|
-
assert.ok(acceptInviteRealtimeEvents.includes("workspace.members.changed"));
|
|
114
|
-
assert.ok(acceptInviteRealtimeEvents.includes("workspace.invites.changed"));
|
|
115
|
-
|
|
116
|
-
const acceptedMembersChange = acceptInviteEvents.find(
|
|
117
|
-
(entry) => entry?.realtime?.event === "workspace.members.changed"
|
|
118
|
-
);
|
|
119
|
-
assert.equal(acceptedMembersChange?.entityId?.({ result: { workspaceId: "9" } }), "9");
|
|
120
|
-
assert.deepEqual(
|
|
121
|
-
acceptedMembersChange?.realtime?.audience?.({
|
|
122
|
-
event: {
|
|
123
|
-
entityId: "9"
|
|
124
|
-
}
|
|
125
|
-
}),
|
|
126
|
-
{
|
|
127
|
-
workspaceId: "9"
|
|
128
|
-
}
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const acceptedInvitesChange = acceptInviteEvents.find(
|
|
132
|
-
(entry) => entry?.realtime?.event === "workspace.invites.changed"
|
|
133
|
-
);
|
|
134
|
-
assert.equal(acceptedInvitesChange?.entityId?.({ result: { workspaceId: "9" } }), "9");
|
|
135
|
-
|
|
136
|
-
const refuseInviteEvents = Array.isArray(pending?.metadata?.events?.refuseInviteByToken)
|
|
137
|
-
? pending.metadata.events.refuseInviteByToken
|
|
138
|
-
: [];
|
|
139
|
-
const refuseInviteRealtimeEvents = refuseInviteEvents.map((entry) => entry?.realtime?.event).filter(Boolean);
|
|
140
|
-
assert.ok(refuseInviteRealtimeEvents.includes("workspace.invitations.pending.changed"));
|
|
141
|
-
assert.ok(refuseInviteRealtimeEvents.includes("users.bootstrap.changed"));
|
|
142
|
-
assert.ok(refuseInviteRealtimeEvents.includes("workspace.invites.changed"));
|
|
143
|
-
});
|
|
@@ -2,28 +2,19 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { registerUsersCore } from "../src/server/registerUsersCore.js";
|
|
4
4
|
|
|
5
|
-
test("registerUsersCore
|
|
6
|
-
const
|
|
5
|
+
test("registerUsersCore only binds users-core singletons", () => {
|
|
6
|
+
const singletonTokens = [];
|
|
7
7
|
const app = {
|
|
8
|
-
singleton() {
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
actionSurfaceSource(sourceName, resolver) {
|
|
12
|
-
calls.push({
|
|
13
|
-
sourceName: String(sourceName || ""),
|
|
14
|
-
resolverType: typeof resolver
|
|
15
|
-
});
|
|
8
|
+
singleton(token) {
|
|
9
|
+
singletonTokens.push(String(token || ""));
|
|
16
10
|
return this;
|
|
17
11
|
}
|
|
18
12
|
};
|
|
19
13
|
|
|
20
14
|
registerUsersCore(app);
|
|
21
15
|
|
|
22
|
-
assert.deepEqual(
|
|
23
|
-
|
|
24
|
-
sourceName: "workspace",
|
|
25
|
-
resolverType: "function"
|
|
26
|
-
}
|
|
16
|
+
assert.deepEqual(singletonTokens.sort(), [
|
|
17
|
+
"users.profile.sync.service"
|
|
27
18
|
]);
|
|
28
19
|
});
|
|
29
20
|
|
|
@@ -2,10 +2,6 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createRepository as createUsersRepository } from "../src/server/common/repositories/usersRepository.js";
|
|
4
4
|
import { createRepository as createUserSettingsRepository } from "../src/server/common/repositories/userSettingsRepository.js";
|
|
5
|
-
import { createRepository as createWorkspaceInvitesRepository } from "../src/server/common/repositories/workspaceInvitesRepository.js";
|
|
6
|
-
import { createRepository as createWorkspaceMembershipsRepository } from "../src/server/common/repositories/workspaceMembershipsRepository.js";
|
|
7
|
-
import { createRepository as createWorkspacesRepository } from "../src/server/common/repositories/workspacesRepository.js";
|
|
8
|
-
import { createRepository as createWorkspaceSettingsRepository } from "../src/server/workspaceSettings/workspaceSettingsRepository.js";
|
|
9
5
|
|
|
10
6
|
function createKnexStub() {
|
|
11
7
|
const knex = Object.assign(() => {
|
|
@@ -23,11 +19,7 @@ test("users-core repositories expose withTransaction", async () => {
|
|
|
23
19
|
const knex = createKnexStub();
|
|
24
20
|
const repositories = [
|
|
25
21
|
createUsersRepository(knex),
|
|
26
|
-
createUserSettingsRepository(knex)
|
|
27
|
-
createWorkspaceInvitesRepository(knex),
|
|
28
|
-
createWorkspaceMembershipsRepository(knex),
|
|
29
|
-
createWorkspacesRepository(knex),
|
|
30
|
-
createWorkspaceSettingsRepository(knex)
|
|
22
|
+
createUserSettingsRepository(knex)
|
|
31
23
|
];
|
|
32
24
|
|
|
33
25
|
for (const repository of repositories) {
|
|
@@ -4,9 +4,6 @@ import path from "node:path";
|
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import "../test-support/registerDefaultSettingsFields.js";
|
|
7
|
-
import { workspaceMembersResource } from "../src/shared/resources/workspaceMembersResource.js";
|
|
8
|
-
import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
|
|
9
|
-
import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
10
7
|
import { userProfileResource } from "../src/shared/resources/userProfileResource.js";
|
|
11
8
|
import { userSettingsResource } from "../src/shared/resources/userSettingsResource.js";
|
|
12
9
|
|
|
@@ -30,8 +27,6 @@ function assertResourceOperationMessages(resource, operationName, label) {
|
|
|
30
27
|
|
|
31
28
|
test("users-core resources expose messages for all operations", () => {
|
|
32
29
|
const resources = {
|
|
33
|
-
workspace: workspaceResource,
|
|
34
|
-
workspaceSettings: workspaceSettingsResource,
|
|
35
30
|
userProfile: userProfileResource,
|
|
36
31
|
userSettings: userSettingsResource
|
|
37
32
|
};
|
|
@@ -44,18 +39,7 @@ test("users-core resources expose messages for all operations", () => {
|
|
|
44
39
|
});
|
|
45
40
|
|
|
46
41
|
test("users-core specialized resource operations expose messages and validators", () => {
|
|
47
|
-
const workspaceMembersOperationSpecs = [
|
|
48
|
-
{ label: "workspaceMembers.rolesList", operation: workspaceMembersResource.operations.rolesList },
|
|
49
|
-
{ label: "workspaceMembers.membersList", operation: workspaceMembersResource.operations.membersList },
|
|
50
|
-
{ label: "workspaceMembers.updateMemberRole", operation: workspaceMembersResource.operations.updateMemberRole },
|
|
51
|
-
{ label: "workspaceMembers.removeMember", operation: workspaceMembersResource.operations.removeMember },
|
|
52
|
-
{ label: "workspaceMembers.invitesList", operation: workspaceMembersResource.operations.invitesList },
|
|
53
|
-
{ label: "workspaceMembers.createInvite", operation: workspaceMembersResource.operations.createInvite },
|
|
54
|
-
{ label: "workspaceMembers.revokeInvite", operation: workspaceMembersResource.operations.revokeInvite },
|
|
55
|
-
{ label: "workspaceMembers.redeemInvite", operation: workspaceMembersResource.operations.redeemInvite }
|
|
56
|
-
];
|
|
57
42
|
const operationSpecs = [
|
|
58
|
-
...workspaceMembersOperationSpecs,
|
|
59
43
|
{ label: "userProfile.avatarUpload", operation: userProfileResource.operations.avatarUpload },
|
|
60
44
|
{ label: "userProfile.avatarDelete", operation: userProfileResource.operations.avatarDelete },
|
|
61
45
|
{ label: "userSettings.passwordChange", operation: userSettingsResource.operations.passwordChange },
|
|
@@ -6,13 +6,8 @@ async function importWithIdentity(url, identity) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
test("settings field registries stay shared across module identities", async () => {
|
|
9
|
-
const workspaceModuleUrl = new URL("../src/shared/resources/workspaceSettingsFields.js", import.meta.url);
|
|
10
9
|
const userModuleUrl = new URL("../src/shared/resources/userSettingsFields.js", import.meta.url);
|
|
11
10
|
|
|
12
|
-
const workspaceA = await importWithIdentity(workspaceModuleUrl, "workspace-a");
|
|
13
|
-
const workspaceB = await importWithIdentity(workspaceModuleUrl, "workspace-b");
|
|
14
|
-
assert.equal(workspaceA.workspaceSettingsFields, workspaceB.workspaceSettingsFields);
|
|
15
|
-
|
|
16
11
|
const userA = await importWithIdentity(userModuleUrl, "user-a");
|
|
17
12
|
const userB = await importWithIdentity(userModuleUrl, "user-b");
|
|
18
13
|
assert.equal(userA.userSettingsFields, userB.userSettingsFields);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createUsersBootstrapContributor } from "../src/server/usersBootstrapContributor.js";
|
|
4
|
-
import {
|
|
5
|
-
TENANCY_MODE_PERSONAL,
|
|
6
|
-
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
7
|
-
} from "../src/shared/tenancyProfile.js";
|
|
8
4
|
|
|
9
5
|
function createAuthenticatedProfile(overrides = {}) {
|
|
10
6
|
return {
|
|
@@ -93,7 +89,7 @@ test("users bootstrap contributor exposes the generic authenticated bootstrap pa
|
|
|
93
89
|
assert.deepEqual(payload.surfaceAccess, {
|
|
94
90
|
consoleowner: true
|
|
95
91
|
});
|
|
96
|
-
assert.equal(payload.app.features.
|
|
92
|
+
assert.equal(payload.app.features.assistantEnabled, false);
|
|
97
93
|
assert.deepEqual(payload.session.oauthProviders, [
|
|
98
94
|
{
|
|
99
95
|
id: "google",
|
|
@@ -101,12 +97,11 @@ test("users bootstrap contributor exposes the generic authenticated bootstrap pa
|
|
|
101
97
|
}
|
|
102
98
|
]);
|
|
103
99
|
assert.equal(payload.session.oauthDefaultProvider, "google");
|
|
104
|
-
assert.deepEqual(payload.workspaces, []);
|
|
105
100
|
assert.deepEqual(payload.userSettings, {});
|
|
106
101
|
assert.equal(payload.requestMeta.hasRequest, true);
|
|
107
102
|
});
|
|
108
103
|
|
|
109
|
-
test("users bootstrap contributor emits
|
|
104
|
+
test("users bootstrap contributor emits anonymous bootstrap payload without workspace fields", async () => {
|
|
110
105
|
const contributor = createUsersBootstrapContributor({
|
|
111
106
|
usersRepository: {
|
|
112
107
|
async findById() {
|
|
@@ -118,15 +113,6 @@ test("users bootstrap contributor emits canonical tenancy profile for anonymous
|
|
|
118
113
|
return createUserSettings();
|
|
119
114
|
}
|
|
120
115
|
},
|
|
121
|
-
tenancyProfile: {
|
|
122
|
-
mode: TENANCY_MODE_PERSONAL,
|
|
123
|
-
workspace: {
|
|
124
|
-
enabled: true,
|
|
125
|
-
autoProvision: true,
|
|
126
|
-
allowSelfCreate: false,
|
|
127
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
116
|
appConfig: {
|
|
131
117
|
tenancyMode: "none"
|
|
132
118
|
},
|
|
@@ -156,21 +142,11 @@ test("users bootstrap contributor emits canonical tenancy profile for anonymous
|
|
|
156
142
|
reply: {}
|
|
157
143
|
});
|
|
158
144
|
|
|
159
|
-
assert.deepEqual(payload.tenancy, {
|
|
160
|
-
mode: TENANCY_MODE_PERSONAL,
|
|
161
|
-
workspace: {
|
|
162
|
-
enabled: true,
|
|
163
|
-
autoProvision: true,
|
|
164
|
-
allowSelfCreate: false,
|
|
165
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
145
|
assert.deepEqual(payload.session, {
|
|
169
146
|
authenticated: false,
|
|
170
147
|
oauthProviders: [],
|
|
171
148
|
oauthDefaultProvider: null
|
|
172
149
|
});
|
|
173
|
-
assert.deepEqual(payload.workspaces, []);
|
|
174
150
|
assert.deepEqual(payload.surfaceAccess, {
|
|
175
151
|
consoleowner: false
|
|
176
152
|
});
|
|
@@ -7,9 +7,6 @@ import { deriveResourceRequiredMetadata } from "@jskit-ai/kernel/_testable";
|
|
|
7
7
|
import "../test-support/registerDefaultSettingsFields.js";
|
|
8
8
|
import { userProfileResource } from "../src/shared/resources/userProfileResource.js";
|
|
9
9
|
import { userSettingsResource } from "../src/shared/resources/userSettingsResource.js";
|
|
10
|
-
import { workspaceMembersResource } from "../src/shared/resources/workspaceMembersResource.js";
|
|
11
|
-
import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
|
|
12
|
-
import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
13
10
|
|
|
14
11
|
function assertResourceShape(resource, label) {
|
|
15
12
|
assert.ok(resource, `${label} resource must exist.`);
|
|
@@ -48,8 +45,6 @@ function assertResourceShape(resource, label) {
|
|
|
48
45
|
|
|
49
46
|
test("workspace and account resources expose canonical validators", () => {
|
|
50
47
|
const resourcesByLabel = {
|
|
51
|
-
workspace: workspaceResource,
|
|
52
|
-
workspaceSettings: workspaceSettingsResource,
|
|
53
48
|
userProfile: userProfileResource,
|
|
54
49
|
userSettings: userSettingsResource
|
|
55
50
|
};
|
|
@@ -60,18 +55,7 @@ test("workspace and account resources expose canonical validators", () => {
|
|
|
60
55
|
});
|
|
61
56
|
|
|
62
57
|
test("specialized settings and invite operations expose canonical validators", () => {
|
|
63
|
-
const workspaceMembersOperationSpecs = [
|
|
64
|
-
{ label: "workspaceMembers.rolesList", operation: workspaceMembersResource.operations.rolesList },
|
|
65
|
-
{ label: "workspaceMembers.membersList", operation: workspaceMembersResource.operations.membersList },
|
|
66
|
-
{ label: "workspaceMembers.updateMemberRole", operation: workspaceMembersResource.operations.updateMemberRole },
|
|
67
|
-
{ label: "workspaceMembers.removeMember", operation: workspaceMembersResource.operations.removeMember },
|
|
68
|
-
{ label: "workspaceMembers.invitesList", operation: workspaceMembersResource.operations.invitesList },
|
|
69
|
-
{ label: "workspaceMembers.createInvite", operation: workspaceMembersResource.operations.createInvite },
|
|
70
|
-
{ label: "workspaceMembers.revokeInvite", operation: workspaceMembersResource.operations.revokeInvite },
|
|
71
|
-
{ label: "workspaceMembers.redeemInvite", operation: workspaceMembersResource.operations.redeemInvite }
|
|
72
|
-
];
|
|
73
58
|
const operationSpecs = [
|
|
74
|
-
...workspaceMembersOperationSpecs,
|
|
75
59
|
{ label: "userProfile.avatarUpload", operation: userProfileResource.operations.avatarUpload },
|
|
76
60
|
{ label: "userProfile.avatarDelete", operation: userProfileResource.operations.avatarDelete },
|
|
77
61
|
{ label: "userSettings.passwordChange", operation: userSettingsResource.operations.passwordChange },
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { bootWorkspaceDirectoryRoutes } from "./workspaceDirectory/bootWorkspaceDirectoryRoutes.js";
|
|
2
|
-
import { registerWorkspaceDirectory } from "./workspaceDirectory/registerWorkspaceDirectory.js";
|
|
3
|
-
import {
|
|
4
|
-
registerWorkspacePendingInvitations
|
|
5
|
-
} from "./workspacePendingInvitations/registerWorkspacePendingInvitations.js";
|
|
6
|
-
import { bootWorkspacePendingInvitations } from "./workspacePendingInvitations/bootWorkspacePendingInvitations.js";
|
|
7
|
-
import { registerWorkspaceMembers } from "./workspaceMembers/registerWorkspaceMembers.js";
|
|
8
|
-
import { bootWorkspaceMembers } from "./workspaceMembers/bootWorkspaceMembers.js";
|
|
9
|
-
import { registerWorkspaceSettings } from "./workspaceSettings/registerWorkspaceSettings.js";
|
|
10
|
-
import { bootWorkspaceSettings } from "./workspaceSettings/bootWorkspaceSettings.js";
|
|
11
|
-
import { registerWorkspaceRepositories } from "./registerWorkspaceRepositories.js";
|
|
12
|
-
import { registerWorkspaceCore } from "./registerWorkspaceCore.js";
|
|
13
|
-
import { registerWorkspaceBootstrap } from "./registerWorkspaceBootstrap.js";
|
|
14
|
-
|
|
15
|
-
class UsersWorkspacesServiceProvider {
|
|
16
|
-
static id = "workspaces.core";
|
|
17
|
-
|
|
18
|
-
static dependsOn = ["users.core"];
|
|
19
|
-
|
|
20
|
-
register(app) {
|
|
21
|
-
registerWorkspaceRepositories(app);
|
|
22
|
-
registerWorkspaceCore(app);
|
|
23
|
-
registerWorkspaceBootstrap(app);
|
|
24
|
-
registerWorkspaceDirectory(app);
|
|
25
|
-
registerWorkspaceMembers(app);
|
|
26
|
-
registerWorkspaceSettings(app);
|
|
27
|
-
registerWorkspacePendingInvitations(app);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async boot(app) {
|
|
31
|
-
if (app.make("users.workspace.enabled") !== true) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
bootWorkspaceDirectoryRoutes(app);
|
|
36
|
-
if (app.make("users.workspace.invitations.enabled") === true) {
|
|
37
|
-
bootWorkspacePendingInvitations(app);
|
|
38
|
-
}
|
|
39
|
-
bootWorkspaceSettings(app);
|
|
40
|
-
bootWorkspaceMembers(app);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { UsersWorkspacesServiceProvider };
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeObject,
|
|
3
|
-
requireServiceMethod
|
|
4
|
-
} from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
|
|
5
|
-
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
6
|
-
import {
|
|
7
|
-
checkRouteVisibility,
|
|
8
|
-
USERS_ROUTE_VISIBILITY_PUBLIC,
|
|
9
|
-
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
10
|
-
USERS_ROUTE_VISIBILITY_WORKSPACE_USER
|
|
11
|
-
} from "../../../shared/support/usersVisibility.js";
|
|
12
|
-
import { resolveActionUser } from "../support/resolveActionUser.js";
|
|
13
|
-
const WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET = new Set([
|
|
14
|
-
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
15
|
-
USERS_ROUTE_VISIBILITY_WORKSPACE_USER
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
function normalizeWorkspaceSurfaceIds(surfaceIds = []) {
|
|
19
|
-
const source = Array.isArray(surfaceIds) ? surfaceIds : [];
|
|
20
|
-
const normalized = new Set();
|
|
21
|
-
|
|
22
|
-
for (const entry of source) {
|
|
23
|
-
const surfaceId = normalizeSurfaceId(entry);
|
|
24
|
-
if (!surfaceId) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
normalized.add(surfaceId);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return normalized;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function createWorkspaceActionContextContributor({ workspaceService, workspaceSurfaceIds = [] } = {}) {
|
|
34
|
-
const contributorId = "users.workspace.context";
|
|
35
|
-
const workspaceSurfaceIdSet = normalizeWorkspaceSurfaceIds(workspaceSurfaceIds);
|
|
36
|
-
|
|
37
|
-
requireServiceMethod(workspaceService, "resolveWorkspaceContextForUserBySlug", contributorId);
|
|
38
|
-
|
|
39
|
-
return Object.freeze({
|
|
40
|
-
contributorId,
|
|
41
|
-
async contribute({ definition = null, input, context, request } = {}) {
|
|
42
|
-
const payload = normalizeObject(input);
|
|
43
|
-
if (!Object.hasOwn(payload, "workspaceSlug")) {
|
|
44
|
-
return {};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const actionSurfaces = Array.isArray(definition?.surfaces) ? definition.surfaces : [];
|
|
48
|
-
const hasWorkspaceActionSurface = actionSurfaces.some((surfaceId) => workspaceSurfaceIdSet.has(surfaceId));
|
|
49
|
-
const routeSurfaceId = normalizeSurfaceId(request?.routeOptions?.config?.surface);
|
|
50
|
-
const hasWorkspaceSurface = workspaceSurfaceIdSet.has(routeSurfaceId);
|
|
51
|
-
const routeVisibilityInput =
|
|
52
|
-
request && request.routeOptions && request.routeOptions.config
|
|
53
|
-
? request.routeOptions.config.visibility
|
|
54
|
-
: USERS_ROUTE_VISIBILITY_PUBLIC;
|
|
55
|
-
const routeVisibility = checkRouteVisibility(routeVisibilityInput);
|
|
56
|
-
const hasWorkspaceRouteVisibility = WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET.has(routeVisibility);
|
|
57
|
-
if (!hasWorkspaceActionSurface && !hasWorkspaceRouteVisibility && !hasWorkspaceSurface) {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(
|
|
62
|
-
resolveActionUser(context, payload),
|
|
63
|
-
payload.workspaceSlug,
|
|
64
|
-
{ request }
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const contribution = {
|
|
68
|
-
requestMeta: {
|
|
69
|
-
resolvedWorkspaceContext
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (!context?.workspace) {
|
|
74
|
-
contribution.workspace = resolvedWorkspaceContext.workspace;
|
|
75
|
-
}
|
|
76
|
-
if (!context?.membership) {
|
|
77
|
-
contribution.membership = resolvedWorkspaceContext.membership;
|
|
78
|
-
}
|
|
79
|
-
if (!Array.isArray(context?.permissions) || context.permissions.length < 1) {
|
|
80
|
-
contribution.permissions = resolvedWorkspaceContext.permissions;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return contribution;
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export { createWorkspaceActionContextContributor };
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
-
|
|
3
|
-
function createWorkspaceAuthPolicyContextResolver({ workspaceService } = {}) {
|
|
4
|
-
if (!workspaceService || typeof workspaceService.resolveWorkspaceContextForUserBySlug !== "function") {
|
|
5
|
-
throw new Error(
|
|
6
|
-
"workspace auth policy context resolver requires workspaceService.resolveWorkspaceContextForUserBySlug()."
|
|
7
|
-
);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return async function resolveWorkspaceAuthPolicyContext({ request, actor, meta } = {}) {
|
|
11
|
-
const contextPolicy = normalizeText(meta?.contextPolicy || "none").toLowerCase() || "none";
|
|
12
|
-
const permission = normalizeText(meta?.permission);
|
|
13
|
-
if (contextPolicy === "none" && !permission) {
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const workspaceSlug = normalizeText(request?.params?.workspaceSlug).toLowerCase();
|
|
18
|
-
if (!workspaceSlug || !actor) {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(actor, workspaceSlug, {
|
|
23
|
-
request
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
workspace: resolvedWorkspaceContext?.workspace || null,
|
|
28
|
-
membership: resolvedWorkspaceContext?.membership || null,
|
|
29
|
-
permissions: Array.isArray(resolvedWorkspaceContext?.permissions) ? resolvedWorkspaceContext.permissions : []
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export { createWorkspaceAuthPolicyContextResolver };
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { normalizeOpaqueId, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
-
|
|
3
|
-
function buildVisibilityContribution({ visibility, scopeOwnerId = null, userId = null } = {}) {
|
|
4
|
-
const requiresActorScope = visibility === "workspace_user";
|
|
5
|
-
const contribution = {
|
|
6
|
-
scopeKind: requiresActorScope ? "workspace_user" : "workspace",
|
|
7
|
-
requiresActorScope
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
if (scopeOwnerId) {
|
|
11
|
-
contribution.scopeOwnerId = scopeOwnerId;
|
|
12
|
-
}
|
|
13
|
-
if (requiresActorScope && userId != null) {
|
|
14
|
-
contribution.userId = userId;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return contribution;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function createWorkspaceRouteVisibilityResolver({ workspaceService } = {}) {
|
|
21
|
-
if (!workspaceService || typeof workspaceService.resolveWorkspaceContextForUserBySlug !== "function") {
|
|
22
|
-
throw new Error("workspace route visibility resolver requires workspaceService.resolveWorkspaceContextForUserBySlug().");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return Object.freeze({
|
|
26
|
-
resolverId: "users.workspace.visibility",
|
|
27
|
-
async resolve({ visibility, context, request, input } = {}) {
|
|
28
|
-
if (visibility !== "workspace" && visibility !== "workspace_user") {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const actor = context?.actor || request?.user || null;
|
|
33
|
-
const userId = normalizeOpaqueId(actor?.id);
|
|
34
|
-
const workspace =
|
|
35
|
-
context?.workspace || context?.requestMeta?.resolvedWorkspaceContext?.workspace || request?.workspace || null;
|
|
36
|
-
const scopeOwnerId = normalizeRecordId(workspace?.id, { fallback: null });
|
|
37
|
-
if (!scopeOwnerId) {
|
|
38
|
-
const workspaceSlug = normalizeText(input?.workspaceSlug).toLowerCase();
|
|
39
|
-
|
|
40
|
-
if (!workspaceSlug || !actor) {
|
|
41
|
-
return visibility === "workspace_user"
|
|
42
|
-
? buildVisibilityContribution({
|
|
43
|
-
visibility,
|
|
44
|
-
userId
|
|
45
|
-
})
|
|
46
|
-
: {};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(actor, workspaceSlug, {
|
|
50
|
-
request
|
|
51
|
-
});
|
|
52
|
-
const resolvedWorkspaceOwnerId = normalizeRecordId(resolvedWorkspaceContext?.workspace?.id, { fallback: null });
|
|
53
|
-
if (!resolvedWorkspaceOwnerId) {
|
|
54
|
-
return visibility === "workspace_user"
|
|
55
|
-
? buildVisibilityContribution({
|
|
56
|
-
visibility,
|
|
57
|
-
userId
|
|
58
|
-
})
|
|
59
|
-
: {};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return buildVisibilityContribution({
|
|
63
|
-
visibility,
|
|
64
|
-
scopeOwnerId: resolvedWorkspaceOwnerId,
|
|
65
|
-
userId
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return buildVisibilityContribution({
|
|
70
|
-
visibility,
|
|
71
|
-
scopeOwnerId,
|
|
72
|
-
userId
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export { createWorkspaceRouteVisibilityResolver };
|