@jskit-ai/users-core 0.1.30 → 0.1.32
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 +8 -6
- package/package.json +6 -6
- package/src/server/UsersCoreServiceProvider.js +1 -3
- package/src/server/accountProfile/avatarService.js +18 -59
- package/src/server/accountProfile/avatarStorageService.js +14 -95
- package/src/server/accountProfile/bootAccountProfileRoutes.js +10 -14
- package/src/server/common/formatters/workspaceFormatter.js +2 -2
- package/src/server/common/repositories/userProfilesRepository.js +6 -6
- 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/authProfileSyncService.js +5 -5
- package/src/server/common/services/workspaceContextService.js +3 -3
- package/src/server/common/validators/authenticatedUserValidator.js +2 -2
- package/src/server/workspaceBootstrapContributor.js +2 -2
- 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 +8 -8
- package/templates/migrations/users_core_initial.cjs +5 -5
- package/test/authProfileSyncService.test.js +5 -5
- package/test/avatarService.test.js +4 -4
- package/test/usersRouteRequestInputValidator.test.js +4 -4
- package/test/workspaceActionContextContributor.test.js +9 -9
- package/test/workspaceAuthPolicyContextResolver.test.js +2 -2
- package/test/workspaceBootstrapContributor.test.js +1 -1
- package/test/workspaceInvitesRepository.test.js +3 -3
- package/test/workspaceMembersService.test.js +10 -10
- package/test/workspacePendingInvitationsResource.test.js +2 -2
- package/test/workspacePendingInvitationsService.test.js +3 -3
- package/test/workspaceService.test.js +10 -10
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +0 -40
- package/test/registerAvatarMultipartSupport.test.js +0 -63
|
@@ -103,7 +103,7 @@ function bootWorkspaceMembers(app) {
|
|
|
103
103
|
input: {
|
|
104
104
|
workspaceSlug: request.input.params.workspaceSlug,
|
|
105
105
|
memberUserId: request.input.params.memberUserId,
|
|
106
|
-
|
|
106
|
+
roleSid: request.input.body.roleSid
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
reply.code(200).send(response);
|
|
@@ -195,7 +195,7 @@ function bootWorkspaceMembers(app) {
|
|
|
195
195
|
input: {
|
|
196
196
|
workspaceSlug: request.input.params.workspaceSlug,
|
|
197
197
|
email: request.input.body.email,
|
|
198
|
-
|
|
198
|
+
roleSid: request.input.body.roleSid
|
|
199
199
|
}
|
|
200
200
|
});
|
|
201
201
|
reply.code(200).send(response);
|
|
@@ -68,7 +68,7 @@ const workspaceMembersActions = Object.freeze([
|
|
|
68
68
|
async execute(input, context, deps) {
|
|
69
69
|
return deps.workspaceMembersService.updateMemberRole(resolveWorkspace(context, input), {
|
|
70
70
|
memberUserId: input.memberUserId,
|
|
71
|
-
|
|
71
|
+
roleSid: input.roleSid
|
|
72
72
|
}, {
|
|
73
73
|
context
|
|
74
74
|
});
|
|
@@ -150,7 +150,7 @@ const workspaceMembersActions = Object.freeze([
|
|
|
150
150
|
resolveActionUser(context, input),
|
|
151
151
|
{
|
|
152
152
|
email: input.email,
|
|
153
|
-
|
|
153
|
+
roleSid: input.roleSid
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
context
|
|
@@ -62,12 +62,12 @@ function createService({
|
|
|
62
62
|
|
|
63
63
|
async function updateMemberRole(workspace, payload = {}, options = {}) {
|
|
64
64
|
const memberUserId = payload.memberUserId;
|
|
65
|
-
const
|
|
66
|
-
if (!assignableRoleIds.includes(
|
|
65
|
+
const roleSid = payload.roleSid;
|
|
66
|
+
if (!assignableRoleIds.includes(roleSid)) {
|
|
67
67
|
throw new AppError(400, "Validation failed.", {
|
|
68
68
|
details: {
|
|
69
69
|
fieldErrors: {
|
|
70
|
-
|
|
70
|
+
roleSid: "Role is not assignable."
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
});
|
|
@@ -77,7 +77,7 @@ function createService({
|
|
|
77
77
|
if (!existingMembership || existingMembership.status !== "active") {
|
|
78
78
|
throw new AppError(404, "Member not found.");
|
|
79
79
|
}
|
|
80
|
-
if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.
|
|
80
|
+
if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.roleSid === OWNER_ROLE_ID) {
|
|
81
81
|
throw new AppError(409, "Cannot change workspace owner role.");
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ function createService({
|
|
|
85
85
|
workspace.id,
|
|
86
86
|
memberUserId,
|
|
87
87
|
{
|
|
88
|
-
|
|
88
|
+
roleSid,
|
|
89
89
|
status: "active"
|
|
90
90
|
},
|
|
91
91
|
options
|
|
@@ -101,7 +101,7 @@ function createService({
|
|
|
101
101
|
if (!existingMembership || existingMembership.status !== "active") {
|
|
102
102
|
throw new AppError(404, "Member not found.");
|
|
103
103
|
}
|
|
104
|
-
if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.
|
|
104
|
+
if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.roleSid === OWNER_ROLE_ID) {
|
|
105
105
|
throw new AppError(409, "Cannot remove workspace owner.");
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -109,7 +109,7 @@ function createService({
|
|
|
109
109
|
workspace.id,
|
|
110
110
|
memberUserId,
|
|
111
111
|
{
|
|
112
|
-
|
|
112
|
+
roleSid: existingMembership.roleSid,
|
|
113
113
|
status: "revoked"
|
|
114
114
|
},
|
|
115
115
|
options
|
|
@@ -134,12 +134,12 @@ function createService({
|
|
|
134
134
|
|
|
135
135
|
async function createInvite(workspace, user, payload = {}, options = {}) {
|
|
136
136
|
const email = payload.email;
|
|
137
|
-
const
|
|
138
|
-
if (!assignableRoleIds.includes(
|
|
137
|
+
const roleSid = payload.roleSid;
|
|
138
|
+
if (!assignableRoleIds.includes(roleSid)) {
|
|
139
139
|
throw new AppError(400, "Validation failed.", {
|
|
140
140
|
details: {
|
|
141
141
|
fieldErrors: {
|
|
142
|
-
|
|
142
|
+
roleSid: "Role is not assignable."
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
});
|
|
@@ -152,7 +152,7 @@ function createService({
|
|
|
152
152
|
{
|
|
153
153
|
workspaceId: workspace.id,
|
|
154
154
|
email,
|
|
155
|
-
|
|
155
|
+
roleSid,
|
|
156
156
|
status: "pending",
|
|
157
157
|
tokenHash,
|
|
158
158
|
invitedByUserId: Number(user?.id || 0) || null,
|
|
@@ -19,7 +19,7 @@ const workspaceSummaryOutputSchema = Type.Object(
|
|
|
19
19
|
const memberSummaryOutputSchema = Type.Object(
|
|
20
20
|
{
|
|
21
21
|
userId: Type.Integer({ minimum: 1 }),
|
|
22
|
-
|
|
22
|
+
roleSid: Type.String({ minLength: 1 }),
|
|
23
23
|
status: Type.String({ minLength: 1 }),
|
|
24
24
|
displayName: Type.String(),
|
|
25
25
|
email: Type.String({ minLength: 1 }),
|
|
@@ -32,7 +32,7 @@ const inviteSummaryOutputSchema = Type.Object(
|
|
|
32
32
|
{
|
|
33
33
|
id: Type.Integer({ minimum: 1 }),
|
|
34
34
|
email: Type.String({ minLength: 3, format: "email" }),
|
|
35
|
-
|
|
35
|
+
roleSid: Type.String({ minLength: 1 }),
|
|
36
36
|
status: Type.String({ minLength: 1 }),
|
|
37
37
|
expiresAt: Type.String({ minLength: 1 }),
|
|
38
38
|
invitedByUserId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()])
|
|
@@ -57,13 +57,13 @@ function normalizeMemberSummary(member, workspace) {
|
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
59
|
userId: Number(source.userId),
|
|
60
|
-
|
|
60
|
+
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
61
61
|
status: normalizeLowerText(source.status || "active") || "active",
|
|
62
62
|
displayName: normalizeText(source.displayName),
|
|
63
63
|
email: normalizeLowerText(source.email),
|
|
64
64
|
isOwner:
|
|
65
65
|
Number(source.userId) === Number(workspace.ownerUserId) ||
|
|
66
|
-
normalizeLowerText(source.
|
|
66
|
+
normalizeLowerText(source.roleSid) === OWNER_ROLE_ID
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -73,7 +73,7 @@ function normalizeInviteSummary(invite) {
|
|
|
73
73
|
return {
|
|
74
74
|
id: Number(source.id),
|
|
75
75
|
email: normalizeLowerText(source.email),
|
|
76
|
-
|
|
76
|
+
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
77
77
|
status: normalizeLowerText(source.status || "pending") || "pending",
|
|
78
78
|
expiresAt: source.expiresAt,
|
|
79
79
|
invitedByUserId: source.invitedByUserId == null ? null : Number(source.invitedByUserId)
|
|
@@ -160,7 +160,7 @@ const workspaceInvitesOutputValidator = Object.freeze({
|
|
|
160
160
|
const updateMemberRoleBodyValidator = Object.freeze({
|
|
161
161
|
schema: Type.Object(
|
|
162
162
|
{
|
|
163
|
-
|
|
163
|
+
roleSid: Type.String({ minLength: 1 })
|
|
164
164
|
},
|
|
165
165
|
{ additionalProperties: false }
|
|
166
166
|
),
|
|
@@ -168,7 +168,7 @@ const updateMemberRoleBodyValidator = Object.freeze({
|
|
|
168
168
|
const source = normalizeObjectInput(payload);
|
|
169
169
|
|
|
170
170
|
return {
|
|
171
|
-
|
|
171
|
+
roleSid: normalizeLowerText(source.roleSid)
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
});
|
|
@@ -177,7 +177,7 @@ const updateMemberRoleInputValidator = Object.freeze({
|
|
|
177
177
|
schema: Type.Object(
|
|
178
178
|
{
|
|
179
179
|
memberUserId: Type.Integer({ minimum: 1 }),
|
|
180
|
-
|
|
180
|
+
roleSid: Type.String({ minLength: 1 })
|
|
181
181
|
},
|
|
182
182
|
{ additionalProperties: false }
|
|
183
183
|
),
|
|
@@ -186,7 +186,7 @@ const updateMemberRoleInputValidator = Object.freeze({
|
|
|
186
186
|
|
|
187
187
|
return {
|
|
188
188
|
memberUserId: normalizePositiveInteger(source.memberUserId),
|
|
189
|
-
|
|
189
|
+
roleSid: normalizeLowerText(source.roleSid)
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
});
|
|
@@ -211,7 +211,7 @@ const createInviteBodyValidator = Object.freeze({
|
|
|
211
211
|
schema: Type.Object(
|
|
212
212
|
{
|
|
213
213
|
email: Type.String({ minLength: 3, format: "email" }),
|
|
214
|
-
|
|
214
|
+
roleSid: Type.String({ minLength: 1 })
|
|
215
215
|
},
|
|
216
216
|
{ additionalProperties: false }
|
|
217
217
|
),
|
|
@@ -220,7 +220,7 @@ const createInviteBodyValidator = Object.freeze({
|
|
|
220
220
|
|
|
221
221
|
return {
|
|
222
222
|
email: normalizeLowerText(source.email),
|
|
223
|
-
|
|
223
|
+
roleSid: normalizeLowerText(source.roleSid || "member") || "member"
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
226
|
});
|
|
@@ -25,7 +25,7 @@ function normalizePendingInvite(invite) {
|
|
|
25
25
|
workspaceSlug: normalizeText(invite?.workspaceSlug),
|
|
26
26
|
workspaceName: normalizeText(invite?.workspaceName || invite?.workspaceSlug),
|
|
27
27
|
workspaceAvatarUrl: normalizeText(invite?.workspaceAvatarUrl),
|
|
28
|
-
|
|
28
|
+
roleSid: normalizeLowerText(invite?.roleSid || "member") || "member",
|
|
29
29
|
status: normalizeLowerText(invite?.status || "pending") || "pending",
|
|
30
30
|
expiresAt: invite?.expiresAt || null,
|
|
31
31
|
token: encodeInviteTokenHash(tokenHash)
|
|
@@ -44,7 +44,7 @@ const pendingInviteRecordValidator = Object.freeze({
|
|
|
44
44
|
workspaceSlug: Type.String({ minLength: 1 }),
|
|
45
45
|
workspaceName: Type.String({ minLength: 1 }),
|
|
46
46
|
workspaceAvatarUrl: Type.String(),
|
|
47
|
-
|
|
47
|
+
roleSid: Type.String({ minLength: 1 }),
|
|
48
48
|
status: Type.String({ minLength: 1 }),
|
|
49
49
|
expiresAt: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
|
|
50
50
|
token: Type.String({ minLength: 1 })
|
|
@@ -63,7 +63,7 @@ function normalizeWorkspaceListItemOutput(payload = {}) {
|
|
|
63
63
|
slug: normalizeLowerText(source.slug),
|
|
64
64
|
name: normalizeText(source.name),
|
|
65
65
|
avatarUrl: normalizeText(source.avatarUrl),
|
|
66
|
-
|
|
66
|
+
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
67
67
|
isAccessible: source.isAccessible !== false
|
|
68
68
|
};
|
|
69
69
|
}
|
|
@@ -85,7 +85,7 @@ const listItemSchema = Type.Object(
|
|
|
85
85
|
slug: Type.String({ minLength: 1 }),
|
|
86
86
|
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
87
87
|
avatarUrl: Type.String(),
|
|
88
|
-
|
|
88
|
+
roleSid: Type.String({ minLength: 1 }),
|
|
89
89
|
isAccessible: Type.Boolean()
|
|
90
90
|
},
|
|
91
91
|
{ additionalProperties: false }
|
package/src/shared/roles.js
CHANGED
|
@@ -20,13 +20,13 @@ function normalizeRoleId(value) {
|
|
|
20
20
|
.toLowerCase();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function createRoleDescriptor(
|
|
23
|
+
function createRoleDescriptor(roleSid, configuredDefinition) {
|
|
24
24
|
const source = asRecord(configuredDefinition);
|
|
25
|
-
const assignable =
|
|
25
|
+
const assignable = roleSid === OWNER_ROLE_ID ? false : source.assignable === true;
|
|
26
26
|
const permissions = normalizePermissionList(source.permissions);
|
|
27
27
|
|
|
28
28
|
return Object.freeze({
|
|
29
|
-
id:
|
|
29
|
+
id: roleSid,
|
|
30
30
|
assignable,
|
|
31
31
|
permissions: Object.freeze([...permissions])
|
|
32
32
|
});
|
|
@@ -46,8 +46,8 @@ function normalizeConfiguredRoles(appConfig = {}) {
|
|
|
46
46
|
const configuredRoles = asRecord(workspaceRoles.roles);
|
|
47
47
|
const normalizedRoles = {};
|
|
48
48
|
|
|
49
|
-
for (const [
|
|
50
|
-
const normalizedRoleId = normalizeRoleId(
|
|
49
|
+
for (const [roleSid, roleDefinition] of Object.entries(configuredRoles)) {
|
|
50
|
+
const normalizedRoleId = normalizeRoleId(roleSid);
|
|
51
51
|
if (!normalizedRoleId) {
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
@@ -60,7 +60,7 @@ function normalizeConfiguredRoles(appConfig = {}) {
|
|
|
60
60
|
function createWorkspaceRoleCatalog(appConfig = {}) {
|
|
61
61
|
const configuredRoles = normalizeConfiguredRoles(appConfig);
|
|
62
62
|
const roleIds = listConfiguredRoleIds(appConfig);
|
|
63
|
-
const roles = roleIds.map((
|
|
63
|
+
const roles = roleIds.map((roleSid) => createRoleDescriptor(roleSid, configuredRoles[roleSid]));
|
|
64
64
|
const assignableRoleIds = roles.filter((role) => role.assignable).map((role) => role.id);
|
|
65
65
|
const configuredDefaultInviteRole = resolveConfiguredDefaultInviteRole(appConfig);
|
|
66
66
|
const defaultInviteRole = assignableRoleIds.includes(configuredDefaultInviteRole)
|
|
@@ -109,8 +109,8 @@ function listRoleDescriptors(appConfig = {}) {
|
|
|
109
109
|
}));
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function resolveRolePermissions(
|
|
113
|
-
const normalizedRoleId = normalizeRoleId(
|
|
112
|
+
function resolveRolePermissions(roleSid, appConfig = {}) {
|
|
113
|
+
const normalizedRoleId = normalizeRoleId(roleSid);
|
|
114
114
|
if (!normalizedRoleId) {
|
|
115
115
|
return [];
|
|
116
116
|
}
|
|
@@ -5,7 +5,7 @@ exports.up = async function up(knex) {
|
|
|
5
5
|
await knex.schema.createTable("users", (table) => {
|
|
6
6
|
table.increments("id").primary();
|
|
7
7
|
table.string("auth_provider", 64).notNullable();
|
|
8
|
-
table.string("
|
|
8
|
+
table.string("auth_provider_user_sid", 191).notNullable();
|
|
9
9
|
table.string("email", 255).notNullable();
|
|
10
10
|
table.string("username", 120).notNullable();
|
|
11
11
|
table.string("display_name", 160).notNullable();
|
|
@@ -13,7 +13,7 @@ exports.up = async function up(knex) {
|
|
|
13
13
|
table.string("avatar_version", 64).nullable();
|
|
14
14
|
table.timestamp("avatar_updated_at", { useTz: false }).nullable();
|
|
15
15
|
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
16
|
-
table.unique(["auth_provider", "
|
|
16
|
+
table.unique(["auth_provider", "auth_provider_user_sid"], "uq_users_identity");
|
|
17
17
|
table.unique(["email"], "uq_users_email");
|
|
18
18
|
table.unique(["username"], "uq_users_username");
|
|
19
19
|
});
|
|
@@ -35,7 +35,7 @@ exports.up = async function up(knex) {
|
|
|
35
35
|
table.increments("id").primary();
|
|
36
36
|
table.integer("workspace_id").unsigned().notNullable().references("id").inTable("workspaces").onDelete("CASCADE");
|
|
37
37
|
table.integer("user_id").unsigned().notNullable().references("id").inTable("users").onDelete("CASCADE");
|
|
38
|
-
table.string("
|
|
38
|
+
table.string("role_sid", 64).notNullable().defaultTo("member");
|
|
39
39
|
table.string("status", 32).notNullable().defaultTo("active");
|
|
40
40
|
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
41
41
|
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
@@ -63,7 +63,7 @@ exports.up = async function up(knex) {
|
|
|
63
63
|
table.increments("id").primary();
|
|
64
64
|
table.integer("workspace_id").unsigned().notNullable().references("id").inTable("workspaces").onDelete("CASCADE");
|
|
65
65
|
table.string("email", 255).notNullable();
|
|
66
|
-
table.string("
|
|
66
|
+
table.string("role_sid", 64).notNullable().defaultTo("member");
|
|
67
67
|
table.string("status", 32).notNullable().defaultTo("pending");
|
|
68
68
|
table.string("token_hash", 191).notNullable();
|
|
69
69
|
table.integer("invited_by_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL");
|
|
@@ -97,7 +97,7 @@ exports.up = async function up(knex) {
|
|
|
97
97
|
|
|
98
98
|
await knex.schema.createTable("console_settings", (table) => {
|
|
99
99
|
table.integer("id").primary();
|
|
100
|
-
table.integer("owner_user_id").unsigned().nullable();
|
|
100
|
+
table.integer("owner_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL");
|
|
101
101
|
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
102
102
|
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
103
103
|
});
|
|
@@ -17,7 +17,7 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
17
17
|
return {
|
|
18
18
|
id: 13,
|
|
19
19
|
authProvider: payload.authProvider,
|
|
20
|
-
|
|
20
|
+
authProviderUserSid: payload.authProviderUserSid,
|
|
21
21
|
email: payload.email,
|
|
22
22
|
displayName: payload.displayName
|
|
23
23
|
};
|
|
@@ -42,7 +42,7 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
42
42
|
|
|
43
43
|
const profile = await service.syncIdentityProfile({
|
|
44
44
|
authProvider: "supabase",
|
|
45
|
-
|
|
45
|
+
authProviderUserSid: "abc-1",
|
|
46
46
|
email: "tony@example.com",
|
|
47
47
|
displayName: "Tony"
|
|
48
48
|
});
|
|
@@ -69,7 +69,7 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
69
69
|
return {
|
|
70
70
|
id: 7,
|
|
71
71
|
authProvider: "supabase",
|
|
72
|
-
|
|
72
|
+
authProviderUserSid: "abc-7",
|
|
73
73
|
email: "tony@example.com",
|
|
74
74
|
displayName: "Tony"
|
|
75
75
|
};
|
|
@@ -96,7 +96,7 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
96
96
|
|
|
97
97
|
const profile = await service.syncIdentityProfile({
|
|
98
98
|
authProvider: "supabase",
|
|
99
|
-
|
|
99
|
+
authProviderUserSid: "abc-7",
|
|
100
100
|
email: "tony@example.com",
|
|
101
101
|
displayName: "Tony"
|
|
102
102
|
});
|
|
@@ -127,7 +127,7 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
|
|
|
127
127
|
|
|
128
128
|
await service.findByIdentity({
|
|
129
129
|
authProvider: " SUPABASE ",
|
|
130
|
-
|
|
130
|
+
authProviderUserSid: " user-1 "
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
assert.deepEqual(capturedIdentity, {
|
|
@@ -41,7 +41,7 @@ test("avatarService uploadForUser stores bytes and updates profile avatar fields
|
|
|
41
41
|
const repository = createRepositoryDouble({
|
|
42
42
|
id: 7,
|
|
43
43
|
authProvider: "local",
|
|
44
|
-
|
|
44
|
+
authProviderUserSid: "u-7",
|
|
45
45
|
email: "test@example.com",
|
|
46
46
|
displayName: "Tester",
|
|
47
47
|
avatarStorageKey: null,
|
|
@@ -65,7 +65,7 @@ test("avatarService uploadForUser stores bytes and updates profile avatar fields
|
|
|
65
65
|
|
|
66
66
|
const user = {
|
|
67
67
|
authProvider: "local",
|
|
68
|
-
|
|
68
|
+
authProviderUserSid: "u-7"
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
const result = await avatarService.uploadForUser(user, {
|
|
@@ -84,7 +84,7 @@ test("avatarService clearForUser removes stored avatar and clears profile fields
|
|
|
84
84
|
const repository = createRepositoryDouble({
|
|
85
85
|
id: 7,
|
|
86
86
|
authProvider: "local",
|
|
87
|
-
|
|
87
|
+
authProviderUserSid: "u-7",
|
|
88
88
|
email: "test@example.com",
|
|
89
89
|
displayName: "Tester",
|
|
90
90
|
avatarStorageKey: "users/avatars/7/avatar",
|
|
@@ -105,7 +105,7 @@ test("avatarService clearForUser removes stored avatar and clears profile fields
|
|
|
105
105
|
|
|
106
106
|
const profile = await avatarService.clearForUser({
|
|
107
107
|
authProvider: "local",
|
|
108
|
-
|
|
108
|
+
authProviderUserSid: "u-7"
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
assert.deepEqual(deletedKeys, ["users/avatars/7/avatar"]);
|
|
@@ -379,7 +379,7 @@ test("workspace invite and member handlers build action input from request.input
|
|
|
379
379
|
createActionRequest({
|
|
380
380
|
input: {
|
|
381
381
|
params: { workspaceSlug: "acme", memberUserId: "12" },
|
|
382
|
-
body: {
|
|
382
|
+
body: { roleSid: "admin" }
|
|
383
383
|
},
|
|
384
384
|
executeAction
|
|
385
385
|
}),
|
|
@@ -389,7 +389,7 @@ test("workspace invite and member handlers build action input from request.input
|
|
|
389
389
|
createActionRequest({
|
|
390
390
|
input: {
|
|
391
391
|
params: { workspaceSlug: "acme" },
|
|
392
|
-
body: { email: "user@example.com",
|
|
392
|
+
body: { email: "user@example.com", roleSid: "member" }
|
|
393
393
|
},
|
|
394
394
|
executeAction
|
|
395
395
|
}),
|
|
@@ -419,8 +419,8 @@ test("workspace invite and member handlers build action input from request.input
|
|
|
419
419
|
input: { name: "Operations", slug: "operations" }
|
|
420
420
|
});
|
|
421
421
|
assert.deepEqual(calls[1].input, { payload: { token: "token-1", decision: "accept" } });
|
|
422
|
-
assert.deepEqual(calls[2].input, { workspaceSlug: "acme", memberUserId: "12",
|
|
423
|
-
assert.deepEqual(calls[3].input, { workspaceSlug: "acme", email: "user@example.com",
|
|
422
|
+
assert.deepEqual(calls[2].input, { workspaceSlug: "acme", memberUserId: "12", roleSid: "admin" });
|
|
423
|
+
assert.deepEqual(calls[3].input, { workspaceSlug: "acme", email: "user@example.com", roleSid: "member" });
|
|
424
424
|
assert.deepEqual(calls[4].input, { workspaceSlug: "acme", memberUserId: "44" });
|
|
425
425
|
assert.deepEqual(calls[5].input, { workspaceSlug: "acme", inviteId: "55" });
|
|
426
426
|
});
|
|
@@ -19,7 +19,7 @@ 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
|
};
|
|
@@ -64,7 +64,7 @@ test("workspace action context contributor resolves workspace context for worksp
|
|
|
64
64
|
slug: "acme"
|
|
65
65
|
},
|
|
66
66
|
membership: {
|
|
67
|
-
|
|
67
|
+
roleSid: "owner"
|
|
68
68
|
},
|
|
69
69
|
permissions: ["workspace.settings.update"]
|
|
70
70
|
}
|
|
@@ -74,7 +74,7 @@ test("workspace action context contributor resolves workspace context for worksp
|
|
|
74
74
|
slug: "acme"
|
|
75
75
|
},
|
|
76
76
|
membership: {
|
|
77
|
-
|
|
77
|
+
roleSid: "owner"
|
|
78
78
|
},
|
|
79
79
|
permissions: ["workspace.settings.update"]
|
|
80
80
|
});
|
|
@@ -113,7 +113,7 @@ test("workspace action context contributor always resolves and stores resolved c
|
|
|
113
113
|
ownerUserId: 77
|
|
114
114
|
},
|
|
115
115
|
membership: {
|
|
116
|
-
|
|
116
|
+
roleSid: "owner"
|
|
117
117
|
},
|
|
118
118
|
permissions: ["workspace.settings.update"]
|
|
119
119
|
};
|
|
@@ -161,13 +161,13 @@ test("workspace action context contributor always resolves and stores resolved c
|
|
|
161
161
|
ownerUserId: 77
|
|
162
162
|
},
|
|
163
163
|
membership: {
|
|
164
|
-
|
|
164
|
+
roleSid: "owner"
|
|
165
165
|
},
|
|
166
166
|
permissions: ["workspace.settings.update"]
|
|
167
167
|
}
|
|
168
168
|
},
|
|
169
169
|
membership: {
|
|
170
|
-
|
|
170
|
+
roleSid: "owner"
|
|
171
171
|
},
|
|
172
172
|
permissions: ["workspace.settings.update"]
|
|
173
173
|
});
|
|
@@ -185,7 +185,7 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
185
185
|
slug: "acme"
|
|
186
186
|
},
|
|
187
187
|
membership: {
|
|
188
|
-
|
|
188
|
+
roleSid: "admin"
|
|
189
189
|
},
|
|
190
190
|
permissions: ["assistant.chat.use"]
|
|
191
191
|
};
|
|
@@ -234,7 +234,7 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
234
234
|
slug: "acme"
|
|
235
235
|
},
|
|
236
236
|
membership: {
|
|
237
|
-
|
|
237
|
+
roleSid: "admin"
|
|
238
238
|
},
|
|
239
239
|
permissions: ["assistant.chat.use"]
|
|
240
240
|
}
|
|
@@ -244,7 +244,7 @@ test("workspace action context contributor resolves context for workspace-visibl
|
|
|
244
244
|
slug: "acme"
|
|
245
245
|
},
|
|
246
246
|
membership: {
|
|
247
|
-
|
|
247
|
+
roleSid: "admin"
|
|
248
248
|
},
|
|
249
249
|
permissions: ["assistant.chat.use"]
|
|
250
250
|
});
|
|
@@ -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
|
});
|
|
@@ -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,
|