@jskit-ai/auth-provider-local-core 0.1.34 → 0.1.36
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
"packageVersion": 1,
|
|
3
3
|
"packageId": "@jskit-ai/auth-provider-local-core",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.36",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"description": "Local auth provider with a file backend default and no database requirement.",
|
|
7
7
|
"dependsOn": [
|
|
@@ -64,8 +64,8 @@ export default Object.freeze({
|
|
|
64
64
|
"mutations": {
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"runtime": {
|
|
67
|
-
"@jskit-ai/auth-core": "0.1.
|
|
68
|
-
"@jskit-ai/kernel": "0.1.
|
|
67
|
+
"@jskit-ai/auth-core": "0.1.132",
|
|
68
|
+
"@jskit-ai/kernel": "0.1.134",
|
|
69
69
|
"nodemailer": "^9.0.3",
|
|
70
70
|
"dotenv": "^16.4.5"
|
|
71
71
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/auth-provider-local-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"./server/lib/index": "./src/server/lib/index.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@jskit-ai/auth-core": "0.1.
|
|
15
|
-
"@jskit-ai/kernel": "0.1.
|
|
14
|
+
"@jskit-ai/auth-core": "0.1.132",
|
|
15
|
+
"@jskit-ai/kernel": "0.1.134",
|
|
16
16
|
"nodemailer": "^9.0.3"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -154,6 +154,23 @@ async function findExistingAppProfile(user, profileProjector) {
|
|
|
154
154
|
return profile ? requireProfileResult(profile, "findByIdentity") : null;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
async function findExistingAppProfileByLogin(login, profileProjector) {
|
|
158
|
+
if (!profileProjector || typeof profileProjector.findByLogin !== "function") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const profile = await profileProjector.findByLogin(login);
|
|
162
|
+
return profile ? requireProfileResult(profile, "findByLogin") : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function appProfileLocalUserId(profile = null) {
|
|
166
|
+
if (
|
|
167
|
+
String(profile?.authProvider || "").trim().toLowerCase() !== PROVIDER_ID
|
|
168
|
+
) {
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
return String(profile?.authProviderUserSid || "").trim();
|
|
172
|
+
}
|
|
173
|
+
|
|
157
174
|
async function buildSessionAuthPayload({ devAuth, profileProjector, request, session, user }) {
|
|
158
175
|
if (!isDevAuthSession(session)) {
|
|
159
176
|
return buildAuthPayload({ user, session: null, profileProjector });
|
|
@@ -230,17 +247,18 @@ function validateEmailInput(email) {
|
|
|
230
247
|
|
|
231
248
|
function devLoginAsValidationError(fieldErrors = {}) {
|
|
232
249
|
const messages = Object.values(fieldErrors).filter(Boolean);
|
|
233
|
-
return new AppError(400, messages[0] || "Provide a user id or
|
|
250
|
+
return new AppError(400, messages[0] || "Provide a user id, email, or login name.", {
|
|
234
251
|
details: {
|
|
235
252
|
fieldErrors
|
|
236
253
|
}
|
|
237
254
|
});
|
|
238
255
|
}
|
|
239
256
|
|
|
240
|
-
function devLoginAsUserNotFound({ email = "", userId = "" } = {}) {
|
|
257
|
+
function devLoginAsUserNotFound({ email = "", login = "", userId = "" } = {}) {
|
|
241
258
|
return devLoginAsValidationError({
|
|
242
259
|
...(userId ? { userId: "User not found." } : {}),
|
|
243
|
-
...(email ? { email: "User not found." } : {})
|
|
260
|
+
...(email ? { email: "User not found." } : {}),
|
|
261
|
+
...(login ? { login: "User not found." } : {})
|
|
244
262
|
});
|
|
245
263
|
}
|
|
246
264
|
|
|
@@ -458,10 +476,12 @@ function createLocalAuthService({
|
|
|
458
476
|
ensureDevAuthExchangeAvailable(devAuth, request);
|
|
459
477
|
const userId = String(input?.userId || "").trim();
|
|
460
478
|
const email = normalizeEmail(input?.email || "");
|
|
461
|
-
|
|
479
|
+
const login = String(input?.login || "").trim();
|
|
480
|
+
if (!userId && !email && !login) {
|
|
462
481
|
throw devLoginAsValidationError({
|
|
463
|
-
userId: "Provide a user id or
|
|
464
|
-
email: "Provide a user id or
|
|
482
|
+
userId: "Provide a user id, email, or login name.",
|
|
483
|
+
email: "Provide a user id, email, or login name.",
|
|
484
|
+
login: "Provide a user id, email, or login name."
|
|
465
485
|
});
|
|
466
486
|
}
|
|
467
487
|
|
|
@@ -470,12 +490,18 @@ function createLocalAuthService({
|
|
|
470
490
|
if (!user && email) {
|
|
471
491
|
user = await tx.users.findByEmail(email);
|
|
472
492
|
}
|
|
493
|
+
let appProfile = null;
|
|
494
|
+
if (!user && login) {
|
|
495
|
+
appProfile = await findExistingAppProfileByLogin(login, profileProjector);
|
|
496
|
+
const localUserId = appProfileLocalUserId(appProfile);
|
|
497
|
+
user = localUserId ? await tx.users.findById(localUserId) : null;
|
|
498
|
+
}
|
|
473
499
|
if (!user || user.disabled) {
|
|
474
|
-
throw devLoginAsUserNotFound({ email, userId });
|
|
500
|
+
throw devLoginAsUserNotFound({ email, login, userId });
|
|
475
501
|
}
|
|
476
|
-
|
|
502
|
+
appProfile ||= await findExistingAppProfile(user, profileProjector);
|
|
477
503
|
if (profileProjector && !appProfile) {
|
|
478
|
-
throw devLoginAsUserNotFound({ email, userId });
|
|
504
|
+
throw devLoginAsUserNotFound({ email, login, userId });
|
|
479
505
|
}
|
|
480
506
|
const session = await createSessionForUser(tx, user, {
|
|
481
507
|
purpose: DEV_AUTH_SESSION_PURPOSE
|
|
@@ -158,6 +158,9 @@ function createLazyProfileProjector(scope) {
|
|
|
158
158
|
findByIdentity(identity, options = {}) {
|
|
159
159
|
return resolveMethod("findByIdentity")(identity, options);
|
|
160
160
|
},
|
|
161
|
+
findByLogin(login, options = {}) {
|
|
162
|
+
return resolveMethod("findByLogin")(login, options);
|
|
163
|
+
},
|
|
161
164
|
syncIdentityProfile(profile, options = {}) {
|
|
162
165
|
return resolveMethod("syncIdentityProfile")(profile, options);
|
|
163
166
|
}
|
|
@@ -234,6 +234,109 @@ test("local file auth login-as issues native cookies for an existing user", asyn
|
|
|
234
234
|
assert.equal(authenticated.sessionPurpose, "dev-auth");
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
+
test("local login-as resolves an application login through its existing auth profile link", async () => {
|
|
238
|
+
const projectedProfile = {
|
|
239
|
+
id: "app-user-ada",
|
|
240
|
+
authProvider: "local",
|
|
241
|
+
authProviderUserSid: "",
|
|
242
|
+
displayName: "Ada",
|
|
243
|
+
email: "ada@example.com",
|
|
244
|
+
username: "merc"
|
|
245
|
+
};
|
|
246
|
+
let syncCalls = 0;
|
|
247
|
+
const { app } = await createStartedApp({
|
|
248
|
+
env: devAuthEnv(),
|
|
249
|
+
profileProjector: {
|
|
250
|
+
async findByIdentity() {
|
|
251
|
+
return projectedProfile;
|
|
252
|
+
},
|
|
253
|
+
async findByLogin(login) {
|
|
254
|
+
return login === "merc" ? projectedProfile : null;
|
|
255
|
+
},
|
|
256
|
+
async syncIdentityProfile(profile) {
|
|
257
|
+
syncCalls += 1;
|
|
258
|
+
return {
|
|
259
|
+
...projectedProfile,
|
|
260
|
+
authProviderUserSid: profile.authProviderUserSid
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
const authService = app.make("authService");
|
|
266
|
+
const registered = await authService.register({
|
|
267
|
+
displayName: "Ada",
|
|
268
|
+
email: "ada@example.com",
|
|
269
|
+
password: "correct horse battery staple"
|
|
270
|
+
});
|
|
271
|
+
projectedProfile.authProviderUserSid = registered.actor.providerUserId;
|
|
272
|
+
syncCalls = 0;
|
|
273
|
+
|
|
274
|
+
const impersonated = await authService.devLoginAs(createDevAuthExchangeRequest(), {
|
|
275
|
+
login: "merc"
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
assert.equal(impersonated.profile.id, "app-user-ada");
|
|
279
|
+
assert.equal(impersonated.actor.providerUserId, registered.actor.providerUserId);
|
|
280
|
+
assert.equal(impersonated.session.purpose, "dev-auth");
|
|
281
|
+
assert.equal(syncCalls, 0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("local login-as rejects unknown, disabled, and non-local application logins", async () => {
|
|
285
|
+
const disabledProfile = {
|
|
286
|
+
id: "app-user-disabled",
|
|
287
|
+
authProvider: "local",
|
|
288
|
+
authProviderUserSid: "local-user-disabled",
|
|
289
|
+
displayName: "Disabled",
|
|
290
|
+
email: "disabled-login@example.com",
|
|
291
|
+
username: "disabled"
|
|
292
|
+
};
|
|
293
|
+
const { app } = await createStartedApp({
|
|
294
|
+
env: devAuthEnv(),
|
|
295
|
+
profileProjector: {
|
|
296
|
+
async findByIdentity() {
|
|
297
|
+
return null;
|
|
298
|
+
},
|
|
299
|
+
async findByLogin(login) {
|
|
300
|
+
if (login === "disabled") {
|
|
301
|
+
return disabledProfile;
|
|
302
|
+
}
|
|
303
|
+
if (login === "external") {
|
|
304
|
+
return {
|
|
305
|
+
id: "app-user-external",
|
|
306
|
+
authProvider: "external",
|
|
307
|
+
authProviderUserSid: "external-user-1",
|
|
308
|
+
displayName: "External",
|
|
309
|
+
email: "external@example.com",
|
|
310
|
+
username: login
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
},
|
|
315
|
+
async syncIdentityProfile() {
|
|
316
|
+
throw new Error("preview login must not synchronize profiles");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
const backend = app.make("auth.local.backend");
|
|
321
|
+
await backend.withTransaction(async (tx) => {
|
|
322
|
+
await tx.users.create({
|
|
323
|
+
disabled: true,
|
|
324
|
+
displayName: "Disabled",
|
|
325
|
+
email: disabledProfile.email,
|
|
326
|
+
id: disabledProfile.authProviderUserSid,
|
|
327
|
+
password: await hashPassword("stored password value")
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
const authService = app.make("authService");
|
|
331
|
+
|
|
332
|
+
for (const login of ["missing", "disabled", "external"]) {
|
|
333
|
+
await assert.rejects(
|
|
334
|
+
() => authService.devLoginAs(createDevAuthExchangeRequest(), { login }),
|
|
335
|
+
/User not found/
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
237
340
|
test("local login-as never invokes the mutating profile projector", async () => {
|
|
238
341
|
const projectedProfile = {
|
|
239
342
|
id: "app-user-ada",
|