@jskit-ai/users-core 0.1.4
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 +464 -0
- package/package.json +35 -0
- package/src/server/UsersCoreServiceProvider.js +74 -0
- package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
- package/src/server/accountNotifications/accountNotificationsService.js +41 -0
- package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
- package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
- package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
- package/src/server/accountPreferences/accountPreferencesService.js +41 -0
- package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
- package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
- package/src/server/accountProfile/accountProfileActions.js +137 -0
- package/src/server/accountProfile/accountProfileService.js +124 -0
- package/src/server/accountProfile/avatarService.js +141 -0
- package/src/server/accountProfile/avatarStorageService.js +132 -0
- package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
- package/src/server/accountProfile/registerAccountProfile.js +62 -0
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
- package/src/server/accountSecurity/accountSecurityActions.js +144 -0
- package/src/server/accountSecurity/accountSecurityService.js +103 -0
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
- package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
- package/src/server/common/README.md +21 -0
- package/src/server/common/contributors/README.md +11 -0
- package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
- package/src/server/common/diTokens.js +21 -0
- package/src/server/common/formatters/README.md +11 -0
- package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
- package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
- package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
- package/src/server/common/formatters/workspaceFormatter.js +46 -0
- package/src/server/common/registerCommonRepositories.js +45 -0
- package/src/server/common/registerSharedApi.js +9 -0
- package/src/server/common/repositories/README.md +24 -0
- package/src/server/common/repositories/repositoryUtils.js +50 -0
- package/src/server/common/repositories/userProfilesRepository.js +251 -0
- package/src/server/common/repositories/userSettingsRepository.js +179 -0
- package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
- package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
- package/src/server/common/repositories/workspacesRepository.js +183 -0
- package/src/server/common/routes/README.md +11 -0
- package/src/server/common/services/README.md +12 -0
- package/src/server/common/services/accountContextService.js +31 -0
- package/src/server/common/services/authProfileSyncService.js +128 -0
- package/src/server/common/services/workspaceContextService.js +270 -0
- package/src/server/common/support/deepFreeze.js +17 -0
- package/src/server/common/support/realtimeServiceEvents.js +94 -0
- package/src/server/common/support/resolveActionUser.js +11 -0
- package/src/server/common/support/workspaceRoutePaths.js +17 -0
- package/src/server/common/validators/README.md +11 -0
- package/src/server/common/validators/authenticatedUserValidator.js +42 -0
- package/src/server/common/validators/routeParamsValidator.js +62 -0
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
- package/src/server/consoleSettings/consoleService.js +36 -0
- package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
- package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
- package/src/server/consoleSettings/consoleSettingsService.js +40 -0
- package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
- package/src/server/registerWorkspaceBootstrap.js +36 -0
- package/src/server/registerWorkspaceCore.js +95 -0
- package/src/server/support/resolveWorkspace.js +16 -0
- package/src/server/support/workspaceActionSurfaces.js +135 -0
- package/src/server/support/workspaceInvitationsPolicy.js +45 -0
- package/src/server/support/workspaceRouteInput.js +22 -0
- package/src/server/workspaceBootstrapContributor.js +401 -0
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
- package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
- package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
- package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
- package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
- package/src/shared/events/usersEvents.js +19 -0
- package/src/shared/index.js +91 -0
- package/src/shared/operationMessages.js +16 -0
- package/src/shared/resources/consoleSettingsFields.js +55 -0
- package/src/shared/resources/consoleSettingsResource.js +139 -0
- package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
- package/src/shared/resources/userProfileResource.js +148 -0
- package/src/shared/resources/userSettingsFields.js +71 -0
- package/src/shared/resources/userSettingsResource.js +416 -0
- package/src/shared/resources/workspaceMembersResource.js +352 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
- package/src/shared/resources/workspaceResource.js +149 -0
- package/src/shared/resources/workspaceSettingsFields.js +60 -0
- package/src/shared/resources/workspaceSettingsResource.js +178 -0
- package/src/shared/roles.js +136 -0
- package/src/shared/settings.js +31 -0
- package/src/shared/support/usersApiPaths.js +34 -0
- package/src/shared/support/usersVisibility.js +45 -0
- package/src/shared/support/workspacePathModel.js +145 -0
- package/src/shared/tenancyMode.js +35 -0
- package/src/shared/tenancyProfile.js +73 -0
- package/templates/config/workspaceRoles.js +30 -0
- package/templates/migrations/users_core_console_owner.cjs +39 -0
- package/templates/migrations/users_core_initial.cjs +118 -0
- package/templates/migrations/users_core_profile_username.cjs +98 -0
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
- package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
- package/test/authProfileSyncService.test.js +119 -0
- package/test/avatarService.test.js +114 -0
- package/test/avatarStorageService.test.js +61 -0
- package/test/consoleService.test.js +57 -0
- package/test/consoleSettingsService.test.js +86 -0
- package/test/exportsContract.test.js +38 -0
- package/test/registerAvatarMultipartSupport.test.js +64 -0
- package/test/registerServiceRealtimeEvents.test.js +160 -0
- package/test/registerWorkspaceDirectory.test.js +26 -0
- package/test/registerWorkspaceSettings.test.js +44 -0
- package/test/resourcesCanonical.test.js +90 -0
- package/test/roles.test.js +74 -0
- package/test/settingsFieldRegistriesSingleton.test.js +24 -0
- package/test/tenancyProfile.test.js +67 -0
- package/test/userSettingsResource.test.js +31 -0
- package/test/usersApiPaths.test.js +31 -0
- package/test/usersRouteRequestInputValidator.test.js +556 -0
- package/test/usersRouteResources.test.js +113 -0
- package/test/usersRouteValidators.test.js +49 -0
- package/test/usersVisibility.test.js +22 -0
- package/test/workspaceActionContextContributor.test.js +251 -0
- package/test/workspaceActionSurfaces.test.js +105 -0
- package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
- package/test/workspaceBootstrapContributor.test.js +466 -0
- package/test/workspaceInvitationsPolicy.test.js +71 -0
- package/test/workspaceInvitesRepository.test.js +111 -0
- package/test/workspaceMembersService.test.js +400 -0
- package/test/workspacePathModel.test.js +93 -0
- package/test/workspacePendingInvitationsResource.test.js +38 -0
- package/test/workspacePendingInvitationsService.test.js +151 -0
- package/test/workspaceRouteVisibilityResolver.test.js +83 -0
- package/test/workspaceService.test.js +480 -0
- package/test/workspaceSettingsActions.test.js +42 -0
- package/test/workspaceSettingsRepository.test.js +156 -0
- package/test/workspaceSettingsResource.test.js +156 -0
- package/test/workspaceSettingsService.test.js +120 -0
- package/test-support/registerDefaultSettingsFields.js +3 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { Type } from "@fastify/type-provider-typebox";
|
|
4
|
+
import { compileRouteValidator } from "@jskit-ai/kernel/_testable";
|
|
5
|
+
import { routeParamsValidator } from "../src/server/common/validators/routeParamsValidator.js";
|
|
6
|
+
|
|
7
|
+
test("routeParamsValidator exposes a shared route params validator", () => {
|
|
8
|
+
assert.equal(typeof routeParamsValidator.schema, "object");
|
|
9
|
+
assert.equal(typeof routeParamsValidator.normalize, "function");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("route validator pipeline uses the shared params validator and merges query arrays automatically", () => {
|
|
13
|
+
const paginationQueryValidator = Object.freeze({
|
|
14
|
+
schema: Type.Object(
|
|
15
|
+
{
|
|
16
|
+
cursor: Type.Optional(Type.String({ minLength: 1 })),
|
|
17
|
+
limit: Type.Optional(Type.String({ pattern: "^[0-9]+$" }))
|
|
18
|
+
},
|
|
19
|
+
{ additionalProperties: false }
|
|
20
|
+
)
|
|
21
|
+
});
|
|
22
|
+
const searchQueryValidator = Object.freeze({
|
|
23
|
+
schema: Type.Object(
|
|
24
|
+
{
|
|
25
|
+
search: Type.Optional(Type.String({ minLength: 1 }))
|
|
26
|
+
},
|
|
27
|
+
{ additionalProperties: false }
|
|
28
|
+
)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const compiled = compileRouteValidator({
|
|
32
|
+
paramsValidator: routeParamsValidator,
|
|
33
|
+
queryValidator: [paginationQueryValidator, searchQueryValidator]
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
assert.equal(compiled.schema.params.type, "object");
|
|
37
|
+
assert.equal(compiled.schema.params.additionalProperties, false);
|
|
38
|
+
assert.equal(typeof compiled.schema.params.properties.workspaceSlug, "object");
|
|
39
|
+
assert.equal(typeof compiled.schema.params.properties.memberUserId, "object");
|
|
40
|
+
assert.equal(typeof compiled.schema.params.properties.inviteId, "object");
|
|
41
|
+
assert.equal(typeof compiled.schema.params.properties.provider, "object");
|
|
42
|
+
assert.equal(compiled.input.params({ workspaceSlug: " ACME " }).workspaceSlug, "acme");
|
|
43
|
+
|
|
44
|
+
assert.equal(compiled.schema.querystring.type, "object");
|
|
45
|
+
assert.equal(compiled.schema.querystring.additionalProperties, false);
|
|
46
|
+
assert.equal(typeof compiled.schema.querystring.properties.cursor, "object");
|
|
47
|
+
assert.equal(typeof compiled.schema.querystring.properties.limit, "object");
|
|
48
|
+
assert.equal(typeof compiled.schema.querystring.properties.search, "object");
|
|
49
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
isWorkspaceVisibility,
|
|
5
|
+
normalizeScopedRouteVisibility
|
|
6
|
+
} from "../src/shared/support/usersVisibility.js";
|
|
7
|
+
|
|
8
|
+
test("normalizeScopedRouteVisibility normalizes users visibility levels", () => {
|
|
9
|
+
assert.equal(normalizeScopedRouteVisibility("WORKSPACE"), "workspace");
|
|
10
|
+
assert.equal(normalizeScopedRouteVisibility("workspace_user"), "workspace_user");
|
|
11
|
+
assert.equal(normalizeScopedRouteVisibility("user"), "user");
|
|
12
|
+
assert.equal(normalizeScopedRouteVisibility(""), "public");
|
|
13
|
+
assert.equal(normalizeScopedRouteVisibility("unknown"), "public");
|
|
14
|
+
assert.equal(normalizeScopedRouteVisibility("unknown", { fallback: "workspace" }), "workspace");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("isWorkspaceVisibility recognizes workspace-only visibility levels", () => {
|
|
18
|
+
assert.equal(isWorkspaceVisibility("workspace"), true);
|
|
19
|
+
assert.equal(isWorkspaceVisibility("workspace_user"), true);
|
|
20
|
+
assert.equal(isWorkspaceVisibility("public"), false);
|
|
21
|
+
assert.equal(isWorkspaceVisibility("user"), false);
|
|
22
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createWorkspaceActionContextContributor } from "../src/server/common/contributors/workspaceActionContextContributor.js";
|
|
4
|
+
|
|
5
|
+
test("workspace action context contributor resolves workspace context for workspace actions", async () => {
|
|
6
|
+
const calls = [];
|
|
7
|
+
const contributor = createWorkspaceActionContextContributor({
|
|
8
|
+
workspaceService: {
|
|
9
|
+
async resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options) {
|
|
10
|
+
calls.push({
|
|
11
|
+
user,
|
|
12
|
+
workspaceSlug,
|
|
13
|
+
options
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
workspace: {
|
|
18
|
+
id: 10,
|
|
19
|
+
slug: "acme"
|
|
20
|
+
},
|
|
21
|
+
membership: {
|
|
22
|
+
roleId: "owner"
|
|
23
|
+
},
|
|
24
|
+
permissions: ["workspace.settings.update"]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const request = {
|
|
31
|
+
user: {
|
|
32
|
+
id: 42,
|
|
33
|
+
email: "user@example.com"
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const contribution = await contributor.contribute({
|
|
38
|
+
actionId: "workspace.settings.update",
|
|
39
|
+
input: {
|
|
40
|
+
workspaceSlug: "Acme"
|
|
41
|
+
},
|
|
42
|
+
context: {
|
|
43
|
+
requestMeta: {
|
|
44
|
+
request
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
request
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(calls, [
|
|
51
|
+
{
|
|
52
|
+
user: request.user,
|
|
53
|
+
workspaceSlug: "Acme",
|
|
54
|
+
options: {
|
|
55
|
+
request
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]);
|
|
59
|
+
assert.deepEqual(contribution, {
|
|
60
|
+
requestMeta: {
|
|
61
|
+
resolvedWorkspaceContext: {
|
|
62
|
+
workspace: {
|
|
63
|
+
id: 10,
|
|
64
|
+
slug: "acme"
|
|
65
|
+
},
|
|
66
|
+
membership: {
|
|
67
|
+
roleId: "owner"
|
|
68
|
+
},
|
|
69
|
+
permissions: ["workspace.settings.update"]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
workspace: {
|
|
73
|
+
id: 10,
|
|
74
|
+
slug: "acme"
|
|
75
|
+
},
|
|
76
|
+
membership: {
|
|
77
|
+
roleId: "owner"
|
|
78
|
+
},
|
|
79
|
+
permissions: ["workspace.settings.update"]
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("workspace action context contributor ignores actions that do not require workspace context", async () => {
|
|
84
|
+
const contributor = createWorkspaceActionContextContributor({
|
|
85
|
+
workspaceService: {
|
|
86
|
+
async resolveWorkspaceContextForUserBySlug() {
|
|
87
|
+
throw new Error("should not be called");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const contribution = await contributor.contribute({
|
|
93
|
+
actionId: "workspace.workspaces.list",
|
|
94
|
+
input: {
|
|
95
|
+
workspaceSlug: "acme"
|
|
96
|
+
},
|
|
97
|
+
context: {}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
assert.deepEqual(contribution, {});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("workspace action context contributor always resolves and stores resolved context", async () => {
|
|
104
|
+
const calls = [];
|
|
105
|
+
const contributor = createWorkspaceActionContextContributor({
|
|
106
|
+
workspaceService: {
|
|
107
|
+
async resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options) {
|
|
108
|
+
calls.push({ user, workspaceSlug, options });
|
|
109
|
+
return {
|
|
110
|
+
workspace: {
|
|
111
|
+
id: 10,
|
|
112
|
+
slug: "acme",
|
|
113
|
+
ownerUserId: 77
|
|
114
|
+
},
|
|
115
|
+
membership: {
|
|
116
|
+
roleId: "owner"
|
|
117
|
+
},
|
|
118
|
+
permissions: ["workspace.settings.update"]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const request = {
|
|
125
|
+
user: {
|
|
126
|
+
id: 42
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const contribution = await contributor.contribute({
|
|
131
|
+
actionId: "workspace.members.list",
|
|
132
|
+
input: {
|
|
133
|
+
workspaceSlug: "acme"
|
|
134
|
+
},
|
|
135
|
+
context: {
|
|
136
|
+
workspace: {
|
|
137
|
+
id: 1
|
|
138
|
+
},
|
|
139
|
+
requestMeta: {
|
|
140
|
+
request
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
request
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
assert.deepEqual(calls, [
|
|
147
|
+
{
|
|
148
|
+
user: request.user,
|
|
149
|
+
workspaceSlug: "acme",
|
|
150
|
+
options: {
|
|
151
|
+
request
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
]);
|
|
155
|
+
assert.deepEqual(contribution, {
|
|
156
|
+
requestMeta: {
|
|
157
|
+
resolvedWorkspaceContext: {
|
|
158
|
+
workspace: {
|
|
159
|
+
id: 10,
|
|
160
|
+
slug: "acme",
|
|
161
|
+
ownerUserId: 77
|
|
162
|
+
},
|
|
163
|
+
membership: {
|
|
164
|
+
roleId: "owner"
|
|
165
|
+
},
|
|
166
|
+
permissions: ["workspace.settings.update"]
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
membership: {
|
|
170
|
+
roleId: "owner"
|
|
171
|
+
},
|
|
172
|
+
permissions: ["workspace.settings.update"]
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("workspace action context contributor resolves context for workspace-visible routes outside legacy action list", async () => {
|
|
177
|
+
const calls = [];
|
|
178
|
+
const contributor = createWorkspaceActionContextContributor({
|
|
179
|
+
workspaceService: {
|
|
180
|
+
async resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options) {
|
|
181
|
+
calls.push({ user, workspaceSlug, options });
|
|
182
|
+
return {
|
|
183
|
+
workspace: {
|
|
184
|
+
id: 33,
|
|
185
|
+
slug: "acme"
|
|
186
|
+
},
|
|
187
|
+
membership: {
|
|
188
|
+
roleId: "admin"
|
|
189
|
+
},
|
|
190
|
+
permissions: ["assistant.chat.use"]
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const request = {
|
|
197
|
+
user: {
|
|
198
|
+
id: 42
|
|
199
|
+
},
|
|
200
|
+
routeOptions: {
|
|
201
|
+
config: {
|
|
202
|
+
visibility: "workspace"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const contribution = await contributor.contribute({
|
|
208
|
+
actionId: "assistant.conversations.list",
|
|
209
|
+
input: {
|
|
210
|
+
workspaceSlug: "acme"
|
|
211
|
+
},
|
|
212
|
+
context: {
|
|
213
|
+
requestMeta: {
|
|
214
|
+
request
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
request
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
assert.deepEqual(calls, [
|
|
221
|
+
{
|
|
222
|
+
user: request.user,
|
|
223
|
+
workspaceSlug: "acme",
|
|
224
|
+
options: {
|
|
225
|
+
request
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
]);
|
|
229
|
+
assert.deepEqual(contribution, {
|
|
230
|
+
requestMeta: {
|
|
231
|
+
resolvedWorkspaceContext: {
|
|
232
|
+
workspace: {
|
|
233
|
+
id: 33,
|
|
234
|
+
slug: "acme"
|
|
235
|
+
},
|
|
236
|
+
membership: {
|
|
237
|
+
roleId: "admin"
|
|
238
|
+
},
|
|
239
|
+
permissions: ["assistant.chat.use"]
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
workspace: {
|
|
243
|
+
id: 33,
|
|
244
|
+
slug: "acme"
|
|
245
|
+
},
|
|
246
|
+
membership: {
|
|
247
|
+
roleId: "admin"
|
|
248
|
+
},
|
|
249
|
+
permissions: ["assistant.chat.use"]
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
materializeWorkspaceActionSurfacesFromAppConfig,
|
|
5
|
+
resolveConsoleSurfaceIdsFromAppConfig,
|
|
6
|
+
resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig
|
|
7
|
+
} from "../src/server/support/workspaceActionSurfaces.js";
|
|
8
|
+
|
|
9
|
+
test("materializeWorkspaceActionSurfacesFromAppConfig resolves workspace surfaces from appConfig", () => {
|
|
10
|
+
const actionDefinitions = [
|
|
11
|
+
{
|
|
12
|
+
id: "workspace.settings.read",
|
|
13
|
+
surfacesFrom: "workspace"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "auth.session.read",
|
|
17
|
+
surfacesFrom: "enabled"
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const materialized = materializeWorkspaceActionSurfacesFromAppConfig(actionDefinitions, {
|
|
22
|
+
appConfig: {
|
|
23
|
+
surfaceDefinitions: {
|
|
24
|
+
app: { id: "app", enabled: true, requiresWorkspace: true },
|
|
25
|
+
admin: { id: "admin", enabled: true, requiresWorkspace: true },
|
|
26
|
+
console: { id: "console", enabled: true, requiresWorkspace: false }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
assert.deepEqual(materialized, [
|
|
32
|
+
{
|
|
33
|
+
id: "workspace.settings.read",
|
|
34
|
+
surfaces: ["app", "admin"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "auth.session.read",
|
|
38
|
+
surfacesFrom: "enabled"
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("materializeWorkspaceActionSurfacesFromAppConfig drops workspace actions when no workspace surfaces are enabled", () => {
|
|
44
|
+
const actionDefinitions = [
|
|
45
|
+
{
|
|
46
|
+
id: "workspace.settings.read",
|
|
47
|
+
surfacesFrom: "workspace"
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const materialized = materializeWorkspaceActionSurfacesFromAppConfig(actionDefinitions, {
|
|
52
|
+
appConfig: {
|
|
53
|
+
surfaceDefinitions: {
|
|
54
|
+
app: { id: "app", enabled: true, requiresWorkspace: false },
|
|
55
|
+
console: { id: "console", enabled: true, requiresWorkspace: false }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.deepEqual(materialized, []);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig picks a workspace surface when app default is non-workspace", () => {
|
|
64
|
+
const surfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig({
|
|
65
|
+
surfaceDefaultId: "home",
|
|
66
|
+
surfaceDefinitions: {
|
|
67
|
+
home: { id: "home", enabled: true, requiresWorkspace: false },
|
|
68
|
+
app: { id: "app", enabled: true, requiresWorkspace: true },
|
|
69
|
+
admin: { id: "admin", enabled: true, requiresWorkspace: true }
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.equal(surfaceId, "app");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig falls back to app default when no workspace surfaces exist", () => {
|
|
77
|
+
const surfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig({
|
|
78
|
+
surfaceDefaultId: "home",
|
|
79
|
+
surfaceDefinitions: {
|
|
80
|
+
home: { id: "home", enabled: true, requiresWorkspace: false },
|
|
81
|
+
console: { id: "console", enabled: true, requiresWorkspace: false }
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
assert.equal(surfaceId, "home");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("resolveConsoleSurfaceIdsFromAppConfig resolves all enabled console-owner surfaces", () => {
|
|
89
|
+
const surfaceIds = resolveConsoleSurfaceIdsFromAppConfig({
|
|
90
|
+
surfaceDefinitions: {
|
|
91
|
+
home: { id: "home", enabled: true, requiresWorkspace: false, accessPolicyId: "public" },
|
|
92
|
+
console: { id: "console", enabled: true, requiresWorkspace: false, accessPolicyId: "console_owner" },
|
|
93
|
+
opsConsole: { id: "opsConsole", enabled: true, requiresWorkspace: false, accessPolicyId: "console_owner" },
|
|
94
|
+
app: { id: "app", enabled: true, requiresWorkspace: true, accessPolicyId: "workspace_member" },
|
|
95
|
+
disabledConsole: {
|
|
96
|
+
id: "disabledConsole",
|
|
97
|
+
enabled: false,
|
|
98
|
+
requiresWorkspace: false,
|
|
99
|
+
accessPolicyId: "console_owner"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
assert.deepEqual(surfaceIds, ["console", "opsconsole"]);
|
|
105
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createWorkspaceAuthPolicyContextResolver } from "../src/server/common/contributors/workspaceAuthPolicyContextResolver.js";
|
|
4
|
+
|
|
5
|
+
test("workspace auth policy context resolver returns empty context when policy does not require workspace context", async () => {
|
|
6
|
+
const resolver = createWorkspaceAuthPolicyContextResolver({
|
|
7
|
+
workspaceService: {
|
|
8
|
+
async resolveWorkspaceContextForUserBySlug() {
|
|
9
|
+
throw new Error("must not be called");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const resolved = await resolver({
|
|
15
|
+
request: {
|
|
16
|
+
params: {
|
|
17
|
+
workspaceSlug: "acme"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
actor: {
|
|
21
|
+
id: 7
|
|
22
|
+
},
|
|
23
|
+
meta: {
|
|
24
|
+
contextPolicy: "none",
|
|
25
|
+
permission: ""
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
assert.deepEqual(resolved, {});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("workspace auth policy context resolver resolves workspace context from users workspace service", async () => {
|
|
33
|
+
const calls = [];
|
|
34
|
+
const resolver = createWorkspaceAuthPolicyContextResolver({
|
|
35
|
+
workspaceService: {
|
|
36
|
+
async resolveWorkspaceContextForUserBySlug(actor, workspaceSlug, options) {
|
|
37
|
+
calls.push({
|
|
38
|
+
actor,
|
|
39
|
+
workspaceSlug,
|
|
40
|
+
options
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
workspace: {
|
|
44
|
+
id: 11,
|
|
45
|
+
slug: workspaceSlug
|
|
46
|
+
},
|
|
47
|
+
membership: {
|
|
48
|
+
roleId: "owner"
|
|
49
|
+
},
|
|
50
|
+
permissions: ["projects.read"]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const request = {
|
|
57
|
+
params: {
|
|
58
|
+
workspaceSlug: "ACME"
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const actor = {
|
|
62
|
+
id: 7
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const resolved = await resolver({
|
|
66
|
+
request,
|
|
67
|
+
actor,
|
|
68
|
+
meta: {
|
|
69
|
+
contextPolicy: "required"
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.deepEqual(calls, [
|
|
74
|
+
{
|
|
75
|
+
actor,
|
|
76
|
+
workspaceSlug: "acme",
|
|
77
|
+
options: {
|
|
78
|
+
request
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]);
|
|
82
|
+
assert.deepEqual(resolved, {
|
|
83
|
+
workspace: {
|
|
84
|
+
id: 11,
|
|
85
|
+
slug: "acme"
|
|
86
|
+
},
|
|
87
|
+
membership: {
|
|
88
|
+
roleId: "owner"
|
|
89
|
+
},
|
|
90
|
+
permissions: ["projects.read"]
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("workspace auth policy context resolver skips workspace lookup when workspace slug is absent", async () => {
|
|
95
|
+
let called = false;
|
|
96
|
+
const resolver = createWorkspaceAuthPolicyContextResolver({
|
|
97
|
+
workspaceService: {
|
|
98
|
+
async resolveWorkspaceContextForUserBySlug() {
|
|
99
|
+
called = true;
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const resolved = await resolver({
|
|
106
|
+
request: {
|
|
107
|
+
params: {}
|
|
108
|
+
},
|
|
109
|
+
actor: {
|
|
110
|
+
id: 7
|
|
111
|
+
},
|
|
112
|
+
meta: {
|
|
113
|
+
contextPolicy: "required"
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert.equal(called, false);
|
|
118
|
+
assert.deepEqual(resolved, {});
|
|
119
|
+
});
|