@jskit-ai/users-core 0.1.31 → 0.1.33
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 +21 -19
- package/package.json +6 -6
- package/src/server/UsersCoreServiceProvider.js +1 -3
- package/src/server/accountNotifications/accountNotificationsService.js +3 -3
- package/src/server/accountNotifications/registerAccountNotifications.js +1 -1
- package/src/server/accountPreferences/accountPreferencesService.js +3 -3
- package/src/server/accountPreferences/registerAccountPreferences.js +1 -1
- package/src/server/accountProfile/accountProfileActions.js +8 -2
- package/src/server/accountProfile/accountProfileService.js +10 -10
- package/src/server/accountProfile/avatarService.js +26 -67
- package/src/server/accountProfile/avatarStorageService.js +14 -95
- package/src/server/accountProfile/bootAccountProfileRoutes.js +13 -15
- package/src/server/accountProfile/registerAccountProfile.js +2 -2
- package/src/server/accountSecurity/accountSecurityService.js +3 -3
- package/src/server/accountSecurity/registerAccountSecurity.js +1 -1
- package/src/server/common/contributors/workspaceActionContextContributor.js +24 -17
- package/src/server/common/formatters/workspaceFormatter.js +2 -2
- package/src/server/common/registerCommonRepositories.js +3 -3
- package/src/server/common/repositories/{userProfilesRepository.js → usersRepository.js} +7 -7
- package/src/server/common/repositories/workspaceInvitesRepository.js +2 -2
- package/src/server/common/repositories/workspaceMembershipsRepository.js +9 -9
- package/src/server/common/repositories/workspacesRepository.js +2 -2
- package/src/server/common/services/accountContextService.js +4 -4
- package/src/server/common/services/authProfileSyncService.js +15 -15
- package/src/server/common/services/workspaceContextService.js +3 -3
- package/src/server/common/validators/authenticatedUserValidator.js +2 -2
- package/src/server/registerWorkspaceBootstrap.js +1 -1
- package/src/server/registerWorkspaceCore.js +5 -2
- package/src/server/workspaceBootstrapContributor.js +6 -6
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +2 -2
- package/src/server/workspaceMembers/workspaceMembersActions.js +2 -2
- package/src/server/workspaceMembers/workspaceMembersService.js +11 -11
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +1 -1
- package/src/shared/resources/workspaceMembersResource.js +11 -11
- package/src/shared/resources/workspacePendingInvitationsResource.js +2 -2
- package/src/shared/resources/workspaceResource.js +2 -2
- package/src/shared/roles.js +37 -12
- package/templates/config/roles.js +27 -0
- package/templates/migrations/users_core_initial.cjs +5 -5
- package/test/authProfileSyncService.test.js +8 -8
- package/test/avatarService.test.js +6 -6
- package/test/roles.test.js +90 -5
- package/test/usersRouteRequestInputValidator.test.js +4 -4
- package/test/workspaceActionContextContributor.test.js +107 -14
- package/test/workspaceAuthPolicyContextResolver.test.js +2 -2
- package/test/workspaceBootstrapContributor.test.js +8 -8
- package/test/workspaceInvitesRepository.test.js +3 -3
- package/test/workspaceMembersService.test.js +14 -12
- package/test/workspacePendingInvitationsResource.test.js +2 -2
- package/test/workspacePendingInvitationsService.test.js +3 -3
- package/test/workspaceService.test.js +22 -18
- package/test/workspaceSettingsResource.test.js +4 -2
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +0 -40
- package/templates/config/workspaceRoles.js +0 -30
- package/test/registerAvatarMultipartSupport.test.js +0 -63
|
@@ -19,12 +19,13 @@ test("workspace action context contributor resolves workspace context for worksp
|
|
|
19
19
|
slug: "acme"
|
|
20
20
|
},
|
|
21
21
|
membership: {
|
|
22
|
-
|
|
22
|
+
roleSid: "owner"
|
|
23
23
|
},
|
|
24
24
|
permissions: ["workspace.settings.update"]
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
},
|
|
28
|
+
workspaceSurfaceIds: ["admin", "app"]
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
const request = {
|
|
@@ -35,7 +36,10 @@ test("workspace action context contributor resolves workspace context for worksp
|
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const contribution = await contributor.contribute({
|
|
38
|
-
|
|
39
|
+
definition: {
|
|
40
|
+
id: "workspace.settings.update",
|
|
41
|
+
surfaces: ["admin", "app"]
|
|
42
|
+
},
|
|
39
43
|
input: {
|
|
40
44
|
workspaceSlug: "Acme"
|
|
41
45
|
},
|
|
@@ -64,7 +68,7 @@ test("workspace action context contributor resolves workspace context for worksp
|
|
|
64
68
|
slug: "acme"
|
|
65
69
|
},
|
|
66
70
|
membership: {
|
|
67
|
-
|
|
71
|
+
roleSid: "owner"
|
|
68
72
|
},
|
|
69
73
|
permissions: ["workspace.settings.update"]
|
|
70
74
|
}
|
|
@@ -74,7 +78,7 @@ test("workspace action context contributor resolves workspace context for worksp
|
|
|
74
78
|
slug: "acme"
|
|
75
79
|
},
|
|
76
80
|
membership: {
|
|
77
|
-
|
|
81
|
+
roleSid: "owner"
|
|
78
82
|
},
|
|
79
83
|
permissions: ["workspace.settings.update"]
|
|
80
84
|
});
|
|
@@ -113,12 +117,13 @@ test("workspace action context contributor always resolves and stores resolved c
|
|
|
113
117
|
ownerUserId: 77
|
|
114
118
|
},
|
|
115
119
|
membership: {
|
|
116
|
-
|
|
120
|
+
roleSid: "owner"
|
|
117
121
|
},
|
|
118
122
|
permissions: ["workspace.settings.update"]
|
|
119
123
|
};
|
|
120
124
|
}
|
|
121
|
-
}
|
|
125
|
+
},
|
|
126
|
+
workspaceSurfaceIds: ["admin", "app"]
|
|
122
127
|
});
|
|
123
128
|
|
|
124
129
|
const request = {
|
|
@@ -128,7 +133,10 @@ test("workspace action context contributor always resolves and stores resolved c
|
|
|
128
133
|
};
|
|
129
134
|
|
|
130
135
|
const contribution = await contributor.contribute({
|
|
131
|
-
|
|
136
|
+
definition: {
|
|
137
|
+
id: "workspace.members.list",
|
|
138
|
+
surfaces: ["admin", "app"]
|
|
139
|
+
},
|
|
132
140
|
input: {
|
|
133
141
|
workspaceSlug: "acme"
|
|
134
142
|
},
|
|
@@ -161,13 +169,13 @@ test("workspace action context contributor always resolves and stores resolved c
|
|
|
161
169
|
ownerUserId: 77
|
|
162
170
|
},
|
|
163
171
|
membership: {
|
|
164
|
-
|
|
172
|
+
roleSid: "owner"
|
|
165
173
|
},
|
|
166
174
|
permissions: ["workspace.settings.update"]
|
|
167
175
|
}
|
|
168
176
|
},
|
|
169
177
|
membership: {
|
|
170
|
-
|
|
178
|
+
roleSid: "owner"
|
|
171
179
|
},
|
|
172
180
|
permissions: ["workspace.settings.update"]
|
|
173
181
|
});
|
|
@@ -185,7 +193,7 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
185
193
|
slug: "acme"
|
|
186
194
|
},
|
|
187
195
|
membership: {
|
|
188
|
-
|
|
196
|
+
roleSid: "admin"
|
|
189
197
|
},
|
|
190
198
|
permissions: ["assistant.chat.use"]
|
|
191
199
|
};
|
|
@@ -205,7 +213,10 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
205
213
|
};
|
|
206
214
|
|
|
207
215
|
const contribution = await contributor.contribute({
|
|
208
|
-
|
|
216
|
+
definition: {
|
|
217
|
+
id: "assistant.conversations.list",
|
|
218
|
+
surfaces: ["admin"]
|
|
219
|
+
},
|
|
209
220
|
input: {
|
|
210
221
|
workspaceSlug: "acme"
|
|
211
222
|
},
|
|
@@ -234,7 +245,7 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
234
245
|
slug: "acme"
|
|
235
246
|
},
|
|
236
247
|
membership: {
|
|
237
|
-
|
|
248
|
+
roleSid: "admin"
|
|
238
249
|
},
|
|
239
250
|
permissions: ["assistant.chat.use"]
|
|
240
251
|
}
|
|
@@ -244,8 +255,90 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
244
255
|
slug: "acme"
|
|
245
256
|
},
|
|
246
257
|
membership: {
|
|
247
|
-
|
|
258
|
+
roleSid: "admin"
|
|
248
259
|
},
|
|
249
260
|
permissions: ["assistant.chat.use"]
|
|
250
261
|
});
|
|
251
262
|
});
|
|
263
|
+
|
|
264
|
+
test("workspace action context contributor resolves context for workspace surfaces even when route visibility is public", async () => {
|
|
265
|
+
const calls = [];
|
|
266
|
+
const contributor = createWorkspaceActionContextContributor({
|
|
267
|
+
workspaceService: {
|
|
268
|
+
async resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options) {
|
|
269
|
+
calls.push({ user, workspaceSlug, options });
|
|
270
|
+
return {
|
|
271
|
+
workspace: {
|
|
272
|
+
id: 77,
|
|
273
|
+
slug: "acme"
|
|
274
|
+
},
|
|
275
|
+
membership: {
|
|
276
|
+
roleSid: "member"
|
|
277
|
+
},
|
|
278
|
+
permissions: ["crud.breeds.list"]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
workspaceSurfaceIds: ["admin", "app"]
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const request = {
|
|
286
|
+
user: {
|
|
287
|
+
id: 42
|
|
288
|
+
},
|
|
289
|
+
routeOptions: {
|
|
290
|
+
config: {
|
|
291
|
+
surface: "admin",
|
|
292
|
+
visibility: "public"
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const contribution = await contributor.contribute({
|
|
298
|
+
definition: {
|
|
299
|
+
id: "crud.breeds.list",
|
|
300
|
+
surfaces: ["admin"]
|
|
301
|
+
},
|
|
302
|
+
input: {
|
|
303
|
+
workspaceSlug: "acme"
|
|
304
|
+
},
|
|
305
|
+
context: {
|
|
306
|
+
requestMeta: {
|
|
307
|
+
request
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
request
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
assert.deepEqual(calls, [
|
|
314
|
+
{
|
|
315
|
+
user: request.user,
|
|
316
|
+
workspaceSlug: "acme",
|
|
317
|
+
options: {
|
|
318
|
+
request
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
]);
|
|
322
|
+
assert.deepEqual(contribution, {
|
|
323
|
+
requestMeta: {
|
|
324
|
+
resolvedWorkspaceContext: {
|
|
325
|
+
workspace: {
|
|
326
|
+
id: 77,
|
|
327
|
+
slug: "acme"
|
|
328
|
+
},
|
|
329
|
+
membership: {
|
|
330
|
+
roleSid: "member"
|
|
331
|
+
},
|
|
332
|
+
permissions: ["crud.breeds.list"]
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
workspace: {
|
|
336
|
+
id: 77,
|
|
337
|
+
slug: "acme"
|
|
338
|
+
},
|
|
339
|
+
membership: {
|
|
340
|
+
roleSid: "member"
|
|
341
|
+
},
|
|
342
|
+
permissions: ["crud.breeds.list"]
|
|
343
|
+
});
|
|
344
|
+
});
|
|
@@ -45,7 +45,7 @@ test("workspace auth policy context resolver resolves workspace context from use
|
|
|
45
45
|
slug: workspaceSlug
|
|
46
46
|
},
|
|
47
47
|
membership: {
|
|
48
|
-
|
|
48
|
+
roleSid: "owner"
|
|
49
49
|
},
|
|
50
50
|
permissions: ["projects.read"]
|
|
51
51
|
};
|
|
@@ -85,7 +85,7 @@ test("workspace auth policy context resolver resolves workspace context from use
|
|
|
85
85
|
slug: "acme"
|
|
86
86
|
},
|
|
87
87
|
membership: {
|
|
88
|
-
|
|
88
|
+
roleSid: "owner"
|
|
89
89
|
},
|
|
90
90
|
permissions: ["projects.read"]
|
|
91
91
|
});
|
|
@@ -10,7 +10,7 @@ function createAuthenticatedProfile(overrides = {}) {
|
|
|
10
10
|
return {
|
|
11
11
|
id: 7,
|
|
12
12
|
authProvider: "local",
|
|
13
|
-
|
|
13
|
+
authProviderUserSid: "user-7",
|
|
14
14
|
username: "tester",
|
|
15
15
|
displayName: "Test User",
|
|
16
16
|
email: "test@example.com",
|
|
@@ -39,7 +39,7 @@ test("workspace bootstrap contributor passes actor context to pending invites se
|
|
|
39
39
|
return [];
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
|
|
42
|
+
usersRepository: {
|
|
43
43
|
async findByIdentity() {
|
|
44
44
|
return profile;
|
|
45
45
|
}
|
|
@@ -101,7 +101,7 @@ test("workspace bootstrap contributor seeds the initial console owner on authent
|
|
|
101
101
|
return [];
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
|
-
|
|
104
|
+
usersRepository: {
|
|
105
105
|
async findByIdentity() {
|
|
106
106
|
return profile;
|
|
107
107
|
}
|
|
@@ -162,7 +162,7 @@ test("workspace bootstrap contributor emits canonical tenancy profile from users
|
|
|
162
162
|
return [];
|
|
163
163
|
}
|
|
164
164
|
},
|
|
165
|
-
|
|
165
|
+
usersRepository: {
|
|
166
166
|
async findByIdentity() {
|
|
167
167
|
return null;
|
|
168
168
|
}
|
|
@@ -228,7 +228,7 @@ test("workspace bootstrap contributor resolves workspace slug from bootstrap que
|
|
|
228
228
|
return [];
|
|
229
229
|
}
|
|
230
230
|
},
|
|
231
|
-
|
|
231
|
+
usersRepository: {
|
|
232
232
|
async findByIdentity() {
|
|
233
233
|
return profile;
|
|
234
234
|
}
|
|
@@ -291,7 +291,7 @@ test("workspace bootstrap contributor returns global payload with requestedWorks
|
|
|
291
291
|
return [];
|
|
292
292
|
}
|
|
293
293
|
},
|
|
294
|
-
|
|
294
|
+
usersRepository: {
|
|
295
295
|
async findByIdentity() {
|
|
296
296
|
return profile;
|
|
297
297
|
}
|
|
@@ -363,7 +363,7 @@ test("workspace bootstrap contributor returns requestedWorkspace=not_found when
|
|
|
363
363
|
return [];
|
|
364
364
|
}
|
|
365
365
|
},
|
|
366
|
-
|
|
366
|
+
usersRepository: {
|
|
367
367
|
async findByIdentity() {
|
|
368
368
|
return profile;
|
|
369
369
|
}
|
|
@@ -427,7 +427,7 @@ test("workspace bootstrap contributor returns requestedWorkspace=unauthenticated
|
|
|
427
427
|
return [];
|
|
428
428
|
}
|
|
429
429
|
},
|
|
430
|
-
|
|
430
|
+
usersRepository: {
|
|
431
431
|
async findByIdentity() {
|
|
432
432
|
return null;
|
|
433
433
|
}
|
|
@@ -11,7 +11,7 @@ function createKnexStub() {
|
|
|
11
11
|
id: 1,
|
|
12
12
|
workspace_id: 1,
|
|
13
13
|
email: "invitee@example.com",
|
|
14
|
-
|
|
14
|
+
role_sid: "member",
|
|
15
15
|
status: "pending",
|
|
16
16
|
token_hash: "hash",
|
|
17
17
|
invited_by_user_id: 1,
|
|
@@ -57,7 +57,7 @@ test("workspaceInvitesRepository.insert normalizes expiresAt ISO input to databa
|
|
|
57
57
|
await repository.insert({
|
|
58
58
|
workspaceId: 1,
|
|
59
59
|
email: "invitee@example.com",
|
|
60
|
-
|
|
60
|
+
roleSid: "member",
|
|
61
61
|
status: "pending",
|
|
62
62
|
tokenHash: "hash",
|
|
63
63
|
invitedByUserId: 1,
|
|
@@ -76,7 +76,7 @@ test("workspaceInvitesRepository.findPendingByTokenHash reads from invites table
|
|
|
76
76
|
id: 44,
|
|
77
77
|
workspace_id: 9,
|
|
78
78
|
email: "invitee@example.com",
|
|
79
|
-
|
|
79
|
+
role_sid: "member",
|
|
80
80
|
status: "pending",
|
|
81
81
|
token_hash: "hash-token",
|
|
82
82
|
invited_by_user_id: 1,
|
|
@@ -16,8 +16,10 @@ function authorizedOptions(permissions = []) {
|
|
|
16
16
|
|
|
17
17
|
function createRoleCatalog() {
|
|
18
18
|
return createWorkspaceRoleCatalog({
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
roleCatalog: {
|
|
20
|
+
workspace: {
|
|
21
|
+
defaultInviteRole: "member"
|
|
22
|
+
},
|
|
21
23
|
roles: {
|
|
22
24
|
owner: {
|
|
23
25
|
assignable: false,
|
|
@@ -52,7 +54,7 @@ function createFixture() {
|
|
|
52
54
|
return [
|
|
53
55
|
{
|
|
54
56
|
userId: 11,
|
|
55
|
-
|
|
57
|
+
roleSid: "member",
|
|
56
58
|
status: "active",
|
|
57
59
|
displayName: "Alice",
|
|
58
60
|
email: "alice@example.com"
|
|
@@ -65,7 +67,7 @@ function createFixture() {
|
|
|
65
67
|
return {
|
|
66
68
|
workspaceId: 7,
|
|
67
69
|
userId: 11,
|
|
68
|
-
|
|
70
|
+
roleSid: "member",
|
|
69
71
|
status: "active"
|
|
70
72
|
};
|
|
71
73
|
},
|
|
@@ -73,7 +75,7 @@ function createFixture() {
|
|
|
73
75
|
assert.equal(Number(workspaceId), 7);
|
|
74
76
|
assert.equal(Number(userId), 11);
|
|
75
77
|
assert.deepEqual(patch, {
|
|
76
|
-
|
|
78
|
+
roleSid: "admin",
|
|
77
79
|
status: "active"
|
|
78
80
|
});
|
|
79
81
|
}
|
|
@@ -134,7 +136,7 @@ test("workspaceMembersService.createInvite uses configured inviteExpiresInMs", a
|
|
|
134
136
|
{ id: 11 },
|
|
135
137
|
{
|
|
136
138
|
email: "alice@example.com",
|
|
137
|
-
|
|
139
|
+
roleSid: "member"
|
|
138
140
|
},
|
|
139
141
|
authorizedOptions(["workspace.members.invite"])
|
|
140
142
|
);
|
|
@@ -260,13 +262,13 @@ test("workspaceMembersService.updateMemberRole returns the refreshed member list
|
|
|
260
262
|
workspace,
|
|
261
263
|
{
|
|
262
264
|
memberUserId: 11,
|
|
263
|
-
|
|
265
|
+
roleSid: "admin"
|
|
264
266
|
},
|
|
265
267
|
authorizedOptions(["workspace.members.manage"])
|
|
266
268
|
);
|
|
267
269
|
|
|
268
270
|
assert.equal(response.members.length, 1);
|
|
269
|
-
assert.equal(response.members[0].
|
|
271
|
+
assert.equal(response.members[0].roleSid, "member");
|
|
270
272
|
});
|
|
271
273
|
|
|
272
274
|
test("workspaceMembersService.removeMember marks membership revoked and returns refreshed members", async () => {
|
|
@@ -287,7 +289,7 @@ test("workspaceMembersService.removeMember marks membership revoked and returns
|
|
|
287
289
|
: [
|
|
288
290
|
{
|
|
289
291
|
userId: 11,
|
|
290
|
-
|
|
292
|
+
roleSid: "member",
|
|
291
293
|
status: "active",
|
|
292
294
|
displayName: "Alice",
|
|
293
295
|
email: "alice@example.com"
|
|
@@ -300,7 +302,7 @@ test("workspaceMembersService.removeMember marks membership revoked and returns
|
|
|
300
302
|
return {
|
|
301
303
|
workspaceId: 7,
|
|
302
304
|
userId: 11,
|
|
303
|
-
|
|
305
|
+
roleSid: "member",
|
|
304
306
|
status: "active"
|
|
305
307
|
};
|
|
306
308
|
},
|
|
@@ -308,7 +310,7 @@ test("workspaceMembersService.removeMember marks membership revoked and returns
|
|
|
308
310
|
assert.equal(Number(workspaceId), 7);
|
|
309
311
|
assert.equal(Number(userId), 11);
|
|
310
312
|
assert.deepEqual(patch, {
|
|
311
|
-
|
|
313
|
+
roleSid: "member",
|
|
312
314
|
status: "revoked"
|
|
313
315
|
});
|
|
314
316
|
removed = true;
|
|
@@ -359,7 +361,7 @@ test("workspaceMembersService.removeMember rejects removing the owner", async ()
|
|
|
359
361
|
return {
|
|
360
362
|
workspaceId: 7,
|
|
361
363
|
userId: 9,
|
|
362
|
-
|
|
364
|
+
roleSid: "owner",
|
|
363
365
|
status: "active"
|
|
364
366
|
};
|
|
365
367
|
},
|
|
@@ -14,7 +14,7 @@ test("workspacePendingInvitationsResource output normalizer shapes raw invite ro
|
|
|
14
14
|
workspaceSlug: "tonymobily3",
|
|
15
15
|
workspaceName: "",
|
|
16
16
|
workspaceAvatarUrl: "",
|
|
17
|
-
|
|
17
|
+
roleSid: "Member",
|
|
18
18
|
status: "Pending",
|
|
19
19
|
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
20
20
|
tokenHash
|
|
@@ -29,7 +29,7 @@ test("workspacePendingInvitationsResource output normalizer shapes raw invite ro
|
|
|
29
29
|
workspaceSlug: "tonymobily3",
|
|
30
30
|
workspaceName: "tonymobily3",
|
|
31
31
|
workspaceAvatarUrl: "",
|
|
32
|
-
|
|
32
|
+
roleSid: "member",
|
|
33
33
|
status: "pending",
|
|
34
34
|
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
35
35
|
token: encodeInviteTokenHash(tokenHash)
|
|
@@ -64,7 +64,7 @@ test("listPendingInvitesForUser returns raw pending invite rows for the action l
|
|
|
64
64
|
workspaceSlug: "tonymobily3",
|
|
65
65
|
workspaceName: "TonyMobily3",
|
|
66
66
|
workspaceAvatarUrl: "",
|
|
67
|
-
|
|
67
|
+
roleSid: "member",
|
|
68
68
|
status: "pending",
|
|
69
69
|
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
70
70
|
tokenHash
|
|
@@ -91,7 +91,7 @@ test("acceptInviteByToken accepts opaque invite token and resolves invite by dec
|
|
|
91
91
|
id: 44,
|
|
92
92
|
workspaceId: 1,
|
|
93
93
|
email: "chiaramobily@gmail.com",
|
|
94
|
-
|
|
94
|
+
roleSid: "member",
|
|
95
95
|
status: "pending",
|
|
96
96
|
tokenHash,
|
|
97
97
|
expiresAt: "2030-01-01T00:00:00.000Z"
|
|
@@ -125,7 +125,7 @@ test("refuseInviteByToken revokes the invite and returns refused", async () => {
|
|
|
125
125
|
id: 45,
|
|
126
126
|
workspaceId: 1,
|
|
127
127
|
email: "chiaramobily@gmail.com",
|
|
128
|
-
|
|
128
|
+
roleSid: "member",
|
|
129
129
|
status: "pending",
|
|
130
130
|
tokenHash,
|
|
131
131
|
expiresAt: "2030-01-01T00:00:00.000Z"
|
|
@@ -2,9 +2,11 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { createService } from "../src/server/common/services/workspaceContextService.js";
|
|
4
4
|
|
|
5
|
-
function
|
|
5
|
+
function createRoleCatalog() {
|
|
6
6
|
return {
|
|
7
|
-
|
|
7
|
+
workspace: {
|
|
8
|
+
defaultInviteRole: "member"
|
|
9
|
+
},
|
|
8
10
|
roles: {
|
|
9
11
|
owner: {
|
|
10
12
|
assignable: false,
|
|
@@ -21,7 +23,7 @@ function createWorkspaceRoles() {
|
|
|
21
23
|
function createWorkspaceServiceFixture({
|
|
22
24
|
tenancyMode = "workspaces",
|
|
23
25
|
tenancyPolicy = {},
|
|
24
|
-
|
|
26
|
+
roleCatalog = createRoleCatalog(),
|
|
25
27
|
additionalWorkspaces = [],
|
|
26
28
|
userWorkspaceRows = null,
|
|
27
29
|
membershipResolver = null,
|
|
@@ -69,7 +71,7 @@ function createWorkspaceServiceFixture({
|
|
|
69
71
|
appConfig: {
|
|
70
72
|
tenancyMode,
|
|
71
73
|
tenancyPolicy,
|
|
72
|
-
|
|
74
|
+
roleCatalog: roleCatalog && typeof roleCatalog === "object" ? { ...roleCatalog } : roleCatalog
|
|
73
75
|
},
|
|
74
76
|
workspacesRepository: {
|
|
75
77
|
async findBySlug(slug) {
|
|
@@ -95,7 +97,7 @@ function createWorkspaceServiceFixture({
|
|
|
95
97
|
slug: "tonymobily3",
|
|
96
98
|
name: "TonyMobily3",
|
|
97
99
|
avatarUrl: "",
|
|
98
|
-
|
|
100
|
+
roleSid: "owner",
|
|
99
101
|
membershipStatus: "active"
|
|
100
102
|
},
|
|
101
103
|
{
|
|
@@ -103,7 +105,7 @@ function createWorkspaceServiceFixture({
|
|
|
103
105
|
slug: "pending-workspace",
|
|
104
106
|
name: "Pending Workspace",
|
|
105
107
|
avatarUrl: "",
|
|
106
|
-
|
|
108
|
+
roleSid: "member",
|
|
107
109
|
membershipStatus: "pending"
|
|
108
110
|
}
|
|
109
111
|
];
|
|
@@ -158,7 +160,7 @@ function createWorkspaceServiceFixture({
|
|
|
158
160
|
return {
|
|
159
161
|
workspaceId,
|
|
160
162
|
userId,
|
|
161
|
-
|
|
163
|
+
roleSid: "owner",
|
|
162
164
|
status: "active"
|
|
163
165
|
};
|
|
164
166
|
}
|
|
@@ -190,7 +192,7 @@ test("workspaceService.listWorkspacesForUser returns only accessible workspaces"
|
|
|
190
192
|
|
|
191
193
|
assert.equal(workspaces.length, 1);
|
|
192
194
|
assert.equal(workspaces[0].slug, "tonymobily3");
|
|
193
|
-
assert.equal(workspaces[0].
|
|
195
|
+
assert.equal(workspaces[0].roleSid, "owner");
|
|
194
196
|
assert.equal(calls.listForUserId, 1);
|
|
195
197
|
assert.equal(calls.insert, 0);
|
|
196
198
|
});
|
|
@@ -220,7 +222,7 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
|
|
|
220
222
|
slug: "chiaramobily",
|
|
221
223
|
name: "Chiara Personal",
|
|
222
224
|
avatarUrl: "",
|
|
223
|
-
|
|
225
|
+
roleSid: "owner",
|
|
224
226
|
membershipStatus: "active"
|
|
225
227
|
},
|
|
226
228
|
{
|
|
@@ -228,7 +230,7 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
|
|
|
228
230
|
slug: "tonymobily",
|
|
229
231
|
name: "Tony Workspace",
|
|
230
232
|
avatarUrl: "",
|
|
231
|
-
|
|
233
|
+
roleSid: "member",
|
|
232
234
|
membershipStatus: "active"
|
|
233
235
|
},
|
|
234
236
|
{
|
|
@@ -236,7 +238,7 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
|
|
|
236
238
|
slug: "pending-workspace",
|
|
237
239
|
name: "Pending Workspace",
|
|
238
240
|
avatarUrl: "",
|
|
239
|
-
|
|
241
|
+
roleSid: "member",
|
|
240
242
|
membershipStatus: "pending"
|
|
241
243
|
}
|
|
242
244
|
]
|
|
@@ -393,7 +395,7 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug allows personal tena
|
|
|
393
395
|
);
|
|
394
396
|
|
|
395
397
|
assert.equal(context.workspace.slug, "team-alpha");
|
|
396
|
-
assert.equal(context.membership.
|
|
398
|
+
assert.equal(context.membership.roleSid, "owner");
|
|
397
399
|
assert.deepEqual(context.permissions, ["*"]);
|
|
398
400
|
});
|
|
399
401
|
|
|
@@ -404,7 +406,7 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug grants owner access
|
|
|
404
406
|
const service = createService({
|
|
405
407
|
appConfig: {
|
|
406
408
|
tenancyMode: "personal",
|
|
407
|
-
|
|
409
|
+
roleCatalog: createRoleCatalog()
|
|
408
410
|
},
|
|
409
411
|
workspacesRepository: {
|
|
410
412
|
async findBySlug(slug) {
|
|
@@ -439,7 +441,7 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug grants owner access
|
|
|
439
441
|
membershipRecord = {
|
|
440
442
|
workspaceId,
|
|
441
443
|
userId,
|
|
442
|
-
|
|
444
|
+
roleSid: "owner",
|
|
443
445
|
status: "active"
|
|
444
446
|
};
|
|
445
447
|
return membershipRecord;
|
|
@@ -464,14 +466,16 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug grants owner access
|
|
|
464
466
|
);
|
|
465
467
|
|
|
466
468
|
assert.equal(ensuredMembershipCount, 1);
|
|
467
|
-
assert.equal(context.membership.
|
|
469
|
+
assert.equal(context.membership.roleSid, "owner");
|
|
468
470
|
assert.deepEqual(context.permissions, ["*"]);
|
|
469
471
|
});
|
|
470
472
|
|
|
471
|
-
test("workspaceService.resolveWorkspaceContextForUserBySlug resolves permissions from appConfig.
|
|
473
|
+
test("workspaceService.resolveWorkspaceContextForUserBySlug resolves permissions from appConfig.roleCatalog", async () => {
|
|
472
474
|
const { service } = createWorkspaceServiceFixture({
|
|
473
|
-
|
|
474
|
-
|
|
475
|
+
roleCatalog: {
|
|
476
|
+
workspace: {
|
|
477
|
+
defaultInviteRole: "member"
|
|
478
|
+
},
|
|
475
479
|
roles: {
|
|
476
480
|
owner: {
|
|
477
481
|
assignable: false,
|
|
@@ -8,8 +8,10 @@ import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
|
|
|
8
8
|
|
|
9
9
|
function createRoleCatalog() {
|
|
10
10
|
return createWorkspaceRoleCatalog({
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
roleCatalog: {
|
|
12
|
+
workspace: {
|
|
13
|
+
defaultInviteRole: "member"
|
|
14
|
+
},
|
|
13
15
|
roles: {
|
|
14
16
|
owner: {
|
|
15
17
|
assignable: false,
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import fastifyMultipart from "@fastify/multipart";
|
|
2
|
-
|
|
3
|
-
async function registerAvatarMultipartSupport(app) {
|
|
4
|
-
if (!app || typeof app.has !== "function" || typeof app.make !== "function") {
|
|
5
|
-
throw new Error("registerAvatarMultipartSupport requires application has()/make().");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
if (!app.has("jskit.fastify")) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const fastify = app.make("jskit.fastify");
|
|
13
|
-
if (!fastify || typeof fastify.register !== "function") {
|
|
14
|
-
throw new Error("registerAvatarMultipartSupport requires Fastify register().");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (fastify["jskit.users-core.avatar.multipart.support"] === true) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (typeof fastify.hasContentTypeParser === "function" && fastify.hasContentTypeParser("multipart")) {
|
|
22
|
-
Object.defineProperty(fastify, "jskit.users-core.avatar.multipart.support", {
|
|
23
|
-
value: true,
|
|
24
|
-
configurable: false,
|
|
25
|
-
enumerable: false,
|
|
26
|
-
writable: false
|
|
27
|
-
});
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
await fastify.register(fastifyMultipart);
|
|
32
|
-
Object.defineProperty(fastify, "jskit.users-core.avatar.multipart.support", {
|
|
33
|
-
value: true,
|
|
34
|
-
configurable: false,
|
|
35
|
-
enumerable: false,
|
|
36
|
-
writable: false
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { registerAvatarMultipartSupport };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export const workspaceRoles = {};
|
|
2
|
-
|
|
3
|
-
workspaceRoles.defaultInviteRole = "member";
|
|
4
|
-
workspaceRoles.roles = {};
|
|
5
|
-
|
|
6
|
-
workspaceRoles.roles.owner = {
|
|
7
|
-
assignable: false,
|
|
8
|
-
permissions: []
|
|
9
|
-
};
|
|
10
|
-
workspaceRoles.roles.owner.permissions.push("*");
|
|
11
|
-
|
|
12
|
-
workspaceRoles.roles.admin = {
|
|
13
|
-
assignable: true,
|
|
14
|
-
permissions: []
|
|
15
|
-
};
|
|
16
|
-
workspaceRoles.roles.admin.permissions.push(
|
|
17
|
-
"workspace.roles.view",
|
|
18
|
-
"workspace.settings.view",
|
|
19
|
-
"workspace.settings.update",
|
|
20
|
-
"workspace.members.view",
|
|
21
|
-
"workspace.members.invite",
|
|
22
|
-
"workspace.members.manage",
|
|
23
|
-
"workspace.invites.revoke"
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
workspaceRoles.roles.member = {
|
|
27
|
-
assignable: true,
|
|
28
|
-
permissions: []
|
|
29
|
-
};
|
|
30
|
-
workspaceRoles.roles.member.permissions.push("workspace.settings.view");
|