@jskit-ai/users-core 0.1.145 → 0.1.147
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 +10 -10
- package/package.json +9 -9
- package/src/server/accountProfile/bootAccountProfileRoutes.js +1 -1
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +2 -1
- package/src/server/common/repositories/userProfilesRepository.js +3 -21
- package/src/server/common/services/authProfileSyncService.js +0 -8
- package/test/authProfileSyncService.test.js +0 -48
- package/test/repositoryContracts.test.js +0 -58
- package/test/usersRouteRequestInputValidator.test.js +48 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/users-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.147",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Users/account runtime plus HTTP routes for account features.",
|
|
7
7
|
dependsOn: [
|
|
@@ -146,16 +146,16 @@ export default Object.freeze({
|
|
|
146
146
|
mutations: {
|
|
147
147
|
dependencies: {
|
|
148
148
|
runtime: {
|
|
149
|
-
"@jskit-ai/auth-core": "0.1.
|
|
150
|
-
"@jskit-ai/crud-core": "0.1.
|
|
151
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
152
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
153
|
-
"@jskit-ai/json-rest-api-core": "0.1.
|
|
154
|
-
"@jskit-ai/kernel": "0.1.
|
|
155
|
-
"@jskit-ai/resource-core": "0.1.
|
|
156
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
149
|
+
"@jskit-ai/auth-core": "0.1.133",
|
|
150
|
+
"@jskit-ai/crud-core": "0.1.144",
|
|
151
|
+
"@jskit-ai/database-runtime": "0.1.134",
|
|
152
|
+
"@jskit-ai/http-runtime": "0.1.133",
|
|
153
|
+
"@jskit-ai/json-rest-api-core": "0.1.79",
|
|
154
|
+
"@jskit-ai/kernel": "0.1.135",
|
|
155
|
+
"@jskit-ai/resource-core": "0.1.78",
|
|
156
|
+
"@jskit-ai/resource-crud-core": "0.1.78",
|
|
157
157
|
"@local/users": "file:packages/users",
|
|
158
|
-
"@jskit-ai/uploads-runtime": "0.1.
|
|
158
|
+
"@jskit-ai/uploads-runtime": "0.1.111"
|
|
159
159
|
},
|
|
160
160
|
dev: {}
|
|
161
161
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.147",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"./shared/resources/userSettingsResource": "./src/shared/resources/userSettingsResource.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@jskit-ai/auth-core": "0.1.
|
|
15
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
16
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
17
|
-
"@jskit-ai/json-rest-api-core": "0.1.
|
|
18
|
-
"@jskit-ai/kernel": "0.1.
|
|
19
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
20
|
-
"@jskit-ai/resource-core": "0.1.
|
|
21
|
-
"@jskit-ai/uploads-runtime": "0.1.
|
|
14
|
+
"@jskit-ai/auth-core": "0.1.133",
|
|
15
|
+
"@jskit-ai/database-runtime": "0.1.134",
|
|
16
|
+
"@jskit-ai/http-runtime": "0.1.133",
|
|
17
|
+
"@jskit-ai/json-rest-api-core": "0.1.79",
|
|
18
|
+
"@jskit-ai/kernel": "0.1.135",
|
|
19
|
+
"@jskit-ai/resource-crud-core": "0.1.78",
|
|
20
|
+
"@jskit-ai/resource-core": "0.1.78",
|
|
21
|
+
"@jskit-ai/uploads-runtime": "0.1.111",
|
|
22
22
|
"json-rest-schema": "1.x.x"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -116,11 +116,12 @@ function bootAccountSecurityRoutes(app) {
|
|
|
116
116
|
}
|
|
117
117
|
},
|
|
118
118
|
async function (request, reply) {
|
|
119
|
+
const query = request.input.query || {};
|
|
119
120
|
const result = await request.executeAction({
|
|
120
121
|
actionId: "settings.security.oauth.link.start",
|
|
121
122
|
input: {
|
|
122
123
|
provider: request.input.params.provider,
|
|
123
|
-
returnTo:
|
|
124
|
+
...(Object.hasOwn(query, "returnTo") ? { returnTo: query.returnTo } : {})
|
|
124
125
|
}
|
|
125
126
|
});
|
|
126
127
|
|
|
@@ -174,8 +174,8 @@ function createRepository({ api, knex } = {}) {
|
|
|
174
174
|
|
|
175
175
|
const withTransaction = createWithTransaction(knex);
|
|
176
176
|
|
|
177
|
-
async function
|
|
178
|
-
|
|
177
|
+
async function queryFirst(filters = {}, options = {}) {
|
|
178
|
+
const rows = extractJsonRestCollectionRows(
|
|
179
179
|
await api.resources.userProfiles.query(
|
|
180
180
|
{
|
|
181
181
|
queryParams: {
|
|
@@ -187,10 +187,8 @@ function createRepository({ api, knex } = {}) {
|
|
|
187
187
|
createJsonRestContext(options?.context || null)
|
|
188
188
|
)
|
|
189
189
|
);
|
|
190
|
-
}
|
|
191
190
|
|
|
192
|
-
|
|
193
|
-
return (await queryProfiles(filters, options))[0] || null;
|
|
191
|
+
return rows[0] || null;
|
|
194
192
|
}
|
|
195
193
|
|
|
196
194
|
async function findById(userId, options = {}) {
|
|
@@ -211,21 +209,6 @@ function createRepository({ api, knex } = {}) {
|
|
|
211
209
|
return normalizeProfileRecord(await queryFirst({ email: normalizedEmail }, options));
|
|
212
210
|
}
|
|
213
211
|
|
|
214
|
-
async function findByUsername(username, options = {}) {
|
|
215
|
-
const normalizedUsername = normalizeUsername(username);
|
|
216
|
-
if (!normalizedUsername) {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const rows = await queryProfiles({ username: normalizedUsername }, options);
|
|
221
|
-
if (rows.length > 1) {
|
|
222
|
-
const error = new Error("Application login matches more than one user profile.");
|
|
223
|
-
error.code = "USER_PROFILE_LOGIN_AMBIGUOUS";
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
return normalizeProfileRecord(rows[0] || null);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
212
|
async function findByIdentity(identityLike, options = {}) {
|
|
230
213
|
const identity = normalizeIdentity(identityLike);
|
|
231
214
|
if (!identity) {
|
|
@@ -433,7 +416,6 @@ function createRepository({ api, knex } = {}) {
|
|
|
433
416
|
findById,
|
|
434
417
|
findByEmail,
|
|
435
418
|
findByIdentity,
|
|
436
|
-
findByUsername,
|
|
437
419
|
updateDisplayNameById,
|
|
438
420
|
updateAvatarById,
|
|
439
421
|
clearAvatarById,
|
|
@@ -68,9 +68,6 @@ function createService({ userProfilesRepository, lifecycleContributors = [], use
|
|
|
68
68
|
if (!userProfilesRepository || typeof userProfilesRepository.findByIdentity !== "function") {
|
|
69
69
|
throw new Error("authProfileSyncService requires userProfilesRepository.findByIdentity().");
|
|
70
70
|
}
|
|
71
|
-
if (typeof userProfilesRepository.findByUsername !== "function") {
|
|
72
|
-
throw new Error("authProfileSyncService requires userProfilesRepository.findByUsername().");
|
|
73
|
-
}
|
|
74
71
|
if (typeof userProfilesRepository.upsert !== "function") {
|
|
75
72
|
throw new Error("authProfileSyncService requires userProfilesRepository.upsert().");
|
|
76
73
|
}
|
|
@@ -94,10 +91,6 @@ function createService({ userProfilesRepository, lifecycleContributors = [], use
|
|
|
94
91
|
);
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
async function findByLogin(login, options = {}) {
|
|
98
|
-
return userProfilesRepository.findByUsername(normalizeLowerText(login), options);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
94
|
async function upsertByIdentity(profileLike, options = {}) {
|
|
102
95
|
const normalized = buildNormalizedIdentityProfile(profileLike);
|
|
103
96
|
return userProfilesRepository.upsert(
|
|
@@ -156,7 +149,6 @@ function createService({ userProfilesRepository, lifecycleContributors = [], use
|
|
|
156
149
|
|
|
157
150
|
return Object.freeze({
|
|
158
151
|
findByIdentity,
|
|
159
|
-
findByLogin,
|
|
160
152
|
upsertByIdentity,
|
|
161
153
|
syncIdentityProfile
|
|
162
154
|
});
|
|
@@ -12,9 +12,6 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
12
12
|
calls.push({ step: "find", trx: options.trx || null });
|
|
13
13
|
return null;
|
|
14
14
|
},
|
|
15
|
-
async findByUsername() {
|
|
16
|
-
return null;
|
|
17
|
-
},
|
|
18
15
|
async upsert(payload, options = {}) {
|
|
19
16
|
calls.push({ step: "upsert", trx: options.trx || null });
|
|
20
17
|
return {
|
|
@@ -81,9 +78,6 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
81
78
|
displayName: "Tony"
|
|
82
79
|
};
|
|
83
80
|
},
|
|
84
|
-
async findByUsername() {
|
|
85
|
-
return null;
|
|
86
|
-
},
|
|
87
81
|
async upsert() {
|
|
88
82
|
upsertCalls += 1;
|
|
89
83
|
return null;
|
|
@@ -129,9 +123,6 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
|
|
|
129
123
|
capturedIdentity = identity;
|
|
130
124
|
return null;
|
|
131
125
|
},
|
|
132
|
-
async findByUsername() {
|
|
133
|
-
return null;
|
|
134
|
-
},
|
|
135
126
|
async upsert() {
|
|
136
127
|
return null;
|
|
137
128
|
},
|
|
@@ -156,42 +147,3 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
|
|
|
156
147
|
providerUserId: "user-1"
|
|
157
148
|
});
|
|
158
149
|
});
|
|
159
|
-
|
|
160
|
-
test("authProfileSyncService.findByLogin resolves the normalized application username", async () => {
|
|
161
|
-
let capturedUsername = null;
|
|
162
|
-
const expectedProfile = {
|
|
163
|
-
id: "7",
|
|
164
|
-
authProvider: "local",
|
|
165
|
-
authProviderUserSid: "local-user-7",
|
|
166
|
-
email: "ada@example.com",
|
|
167
|
-
username: "merc",
|
|
168
|
-
displayName: "Ada"
|
|
169
|
-
};
|
|
170
|
-
const service = createService({
|
|
171
|
-
userProfilesRepository: {
|
|
172
|
-
async findByIdentity() {
|
|
173
|
-
return null;
|
|
174
|
-
},
|
|
175
|
-
async findByUsername(username) {
|
|
176
|
-
capturedUsername = username;
|
|
177
|
-
return expectedProfile;
|
|
178
|
-
},
|
|
179
|
-
async upsert() {
|
|
180
|
-
return null;
|
|
181
|
-
},
|
|
182
|
-
async withTransaction(work) {
|
|
183
|
-
return work({ trxId: "tx-4" });
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
userSettingsRepository: {
|
|
187
|
-
async ensureForUserId() {
|
|
188
|
-
return { userId: "7" };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const profile = await service.findByLogin(" MERC ");
|
|
194
|
-
|
|
195
|
-
assert.equal(capturedUsername, "merc");
|
|
196
|
-
assert.equal(profile, expectedProfile);
|
|
197
|
-
});
|
|
@@ -328,64 +328,6 @@ test("userProfilesRepository.findByEmail normalizes email lookup", async () => {
|
|
|
328
328
|
assert.equal(profile?.displayName, "Ada Example");
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
-
test("userProfilesRepository.findByUsername normalizes login lookup", async () => {
|
|
332
|
-
const { api, calls } = createUserProfilesApiStub({
|
|
333
|
-
id: 7,
|
|
334
|
-
authProvider: "local",
|
|
335
|
-
authProviderUserSid: "local-user-7",
|
|
336
|
-
email: "ada@example.com",
|
|
337
|
-
username: "merc-user",
|
|
338
|
-
displayName: "Ada Example"
|
|
339
|
-
});
|
|
340
|
-
const repository = createUserProfilesRepository({
|
|
341
|
-
api,
|
|
342
|
-
knex: createKnexStub()
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const profile = await repository.findByUsername(" Merc User ");
|
|
346
|
-
|
|
347
|
-
assert.deepEqual(calls, [{ username: "merc-user" }]);
|
|
348
|
-
assert.equal(profile?.id, "7");
|
|
349
|
-
assert.equal(profile?.username, "merc-user");
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
test("userProfilesRepository.findByUsername rejects ambiguous login data", async () => {
|
|
353
|
-
const repository = createUserProfilesRepository({
|
|
354
|
-
knex: createKnexStub(),
|
|
355
|
-
api: {
|
|
356
|
-
resources: {
|
|
357
|
-
userProfiles: {
|
|
358
|
-
async query() {
|
|
359
|
-
return asCollectionDocument([
|
|
360
|
-
{
|
|
361
|
-
id: "7",
|
|
362
|
-
authProvider: "local",
|
|
363
|
-
authProviderUserSid: "local-user-7",
|
|
364
|
-
email: "ada@example.com",
|
|
365
|
-
username: "merc",
|
|
366
|
-
displayName: "Ada"
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
id: "8",
|
|
370
|
-
authProvider: "local",
|
|
371
|
-
authProviderUserSid: "local-user-8",
|
|
372
|
-
email: "grace@example.com",
|
|
373
|
-
username: "merc",
|
|
374
|
-
displayName: "Grace"
|
|
375
|
-
}
|
|
376
|
-
]);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
await assert.rejects(
|
|
384
|
-
() => repository.findByUsername("merc"),
|
|
385
|
-
(error) => error?.code === "USER_PROFILE_LOGIN_AMBIGUOUS"
|
|
386
|
-
);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
331
|
test("userProfilesRepository.findByIdentity reads existing profiles from collection documents", async () => {
|
|
390
332
|
const { api, calls } = createUserProfilesApiStub({
|
|
391
333
|
id: 7,
|
|
@@ -228,6 +228,54 @@ test("account route handlers build action input from request.input", async () =>
|
|
|
228
228
|
assert.equal(calls[7].actionId, "settings.security.sessions.logout_others");
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
+
test("account route handlers preserve omitted optional action fields", async () => {
|
|
232
|
+
const routes = await registerRoutes();
|
|
233
|
+
const calls = [];
|
|
234
|
+
const executeAction = async (payload) => {
|
|
235
|
+
calls.push(payload);
|
|
236
|
+
if (payload.actionId === "settings.security.oauth.link.start") {
|
|
237
|
+
return { url: "/oauth/link" };
|
|
238
|
+
}
|
|
239
|
+
return {};
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
await findRoute(routes, { method: "GET", path: "/api/settings/security/oauth/:provider/start" }).handler(
|
|
243
|
+
createActionRequest({
|
|
244
|
+
input: {
|
|
245
|
+
params: { provider: "github" },
|
|
246
|
+
query: {}
|
|
247
|
+
},
|
|
248
|
+
executeAction
|
|
249
|
+
}),
|
|
250
|
+
createReplyDouble()
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const avatarStream = {};
|
|
254
|
+
await findRoute(routes, { method: "POST", path: "/api/settings/profile/avatar" }).handler(
|
|
255
|
+
createActionRequest({
|
|
256
|
+
file: async () => ({
|
|
257
|
+
fieldname: "avatar",
|
|
258
|
+
filename: "avatar.png",
|
|
259
|
+
mimetype: "image/png",
|
|
260
|
+
encoding: "7bit",
|
|
261
|
+
file: avatarStream,
|
|
262
|
+
fields: {}
|
|
263
|
+
}),
|
|
264
|
+
executeAction
|
|
265
|
+
}),
|
|
266
|
+
createReplyDouble()
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
assert.deepEqual(calls[0].input, { provider: "github" });
|
|
270
|
+
assert.equal(Object.hasOwn(calls[0].input, "returnTo"), false);
|
|
271
|
+
assert.deepEqual(calls[1].input, {
|
|
272
|
+
stream: avatarStream,
|
|
273
|
+
mimeType: "image/png",
|
|
274
|
+
fileName: "avatar.png"
|
|
275
|
+
});
|
|
276
|
+
assert.equal(Object.hasOwn(calls[1].input, "uploadDimension"), false);
|
|
277
|
+
});
|
|
278
|
+
|
|
231
279
|
test("account settings jsonapi transport resolves response resource id from request user", async () => {
|
|
232
280
|
const routes = await registerRoutes();
|
|
233
281
|
const settingsRoute = findRoute(routes, { method: "GET", path: "/api/settings" });
|