@adminforth/two-factors-auth 1.10.7 → 1.11.0
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/build.log +2 -2
- package/custom/TwoFAModal.vue +113 -19
- package/custom/TwoFactorsConfirmation.vue +12 -3
- package/custom/TwoFactorsPasskeysSettings.vue +102 -25
- package/custom/utils.js +53 -2
- package/dist/custom/TwoFAModal.vue +113 -19
- package/dist/custom/TwoFactorsConfirmation.vue +12 -3
- package/dist/custom/TwoFactorsPasskeysSettings.vue +102 -25
- package/dist/custom/utils.js +53 -2
- package/dist/index.js +52 -25
- package/index.ts +50 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,30 +19,48 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
19
19
|
instanceUniqueRepresentation(pluginOptions) {
|
|
20
20
|
return `single`;
|
|
21
21
|
}
|
|
22
|
-
verify(
|
|
22
|
+
verify(confirmationResult, opts) {
|
|
23
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
24
|
var _a, _b, _c, _d;
|
|
25
|
-
if (!
|
|
26
|
-
return { error: "
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
if (!confirmationResult)
|
|
26
|
+
return { error: "Confirmation result is required" };
|
|
27
|
+
if (confirmationResult.mode === "totp") {
|
|
28
|
+
const code = confirmationResult.result;
|
|
29
|
+
const authRes = this.adminforth.config.resources
|
|
30
|
+
.find(r => r.resourceId === this.adminforth.config.auth.usersResourceId);
|
|
31
|
+
if (!authRes)
|
|
32
|
+
return { error: "Auth resource not found" };
|
|
33
|
+
const connector = this.adminforth.connectors[authRes.dataSource];
|
|
34
|
+
const pkName = (_a = authRes.columns.find(c => c.primaryKey)) === null || _a === void 0 ? void 0 : _a.name;
|
|
35
|
+
if (!pkName)
|
|
36
|
+
return { error: "Primary key not found on auth resource" };
|
|
37
|
+
const pk = (_b = opts === null || opts === void 0 ? void 0 : opts.userPk) !== null && _b !== void 0 ? _b : (_d = (_c = opts === null || opts === void 0 ? void 0 : opts.adminUser) === null || _c === void 0 ? void 0 : _c.dbUser) === null || _d === void 0 ? void 0 : _d[pkName];
|
|
38
|
+
if (!pk)
|
|
39
|
+
return { error: "User PK is required" };
|
|
40
|
+
const user = yield connector.getRecordByPrimaryKey(authRes, pk);
|
|
41
|
+
if (!user)
|
|
42
|
+
return { error: "User not found" };
|
|
43
|
+
const secret = user[this.options.twoFaSecretFieldName];
|
|
44
|
+
if (!secret)
|
|
45
|
+
return { error: "2FA is not set up for this user" };
|
|
46
|
+
const verified = twofactor.verifyToken(secret, code, this.options.timeStepWindow);
|
|
47
|
+
return verified ? { ok: true } : { error: "Wrong or expired OTP code" };
|
|
48
|
+
}
|
|
49
|
+
else if (confirmationResult.mode === "passkey") {
|
|
50
|
+
const passkeysCookies = this.adminforth.auth.getCustomCookie({ cookies: opts.cookies, name: `passkeyLoginTemporaryJWT` });
|
|
51
|
+
if (!passkeysCookies) {
|
|
52
|
+
return { error: 'Passkey token is required' };
|
|
53
|
+
}
|
|
54
|
+
const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, 'tempLoginPasskeyChallenge', false);
|
|
55
|
+
if (!decodedPasskeysCookies) {
|
|
56
|
+
return { error: 'Invalid passkey' };
|
|
57
|
+
}
|
|
58
|
+
const verificationResult = yield this.verifyPasskeyResponse(confirmationResult.result, opts.userPk, decodedPasskeysCookies);
|
|
59
|
+
if (verificationResult.ok && verificationResult.passkeyConfirmed) {
|
|
60
|
+
return { ok: true };
|
|
61
|
+
}
|
|
62
|
+
return { error: "Invalid passkey" };
|
|
63
|
+
}
|
|
46
64
|
});
|
|
47
65
|
}
|
|
48
66
|
parsePeriod(period) {
|
|
@@ -326,7 +344,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
326
344
|
if (!passkeysCookies) {
|
|
327
345
|
return { error: 'Passkey token is required' };
|
|
328
346
|
}
|
|
329
|
-
const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, '
|
|
347
|
+
const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, 'tempLoginPasskeyChallenge', false);
|
|
330
348
|
if (!decodedPasskeysCookies) {
|
|
331
349
|
return { error: 'Invalid passkey' };
|
|
332
350
|
}
|
|
@@ -400,9 +418,18 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
400
418
|
method: 'POST',
|
|
401
419
|
path: `/plugin/passkeys/registerPasskeyRequest`,
|
|
402
420
|
noAuth: false,
|
|
403
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
421
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies }) {
|
|
404
422
|
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
405
423
|
const mode = body === null || body === void 0 ? void 0 : body.mode;
|
|
424
|
+
const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
|
|
425
|
+
const verificationResult = yield this.verify(confirmationResult, {
|
|
426
|
+
adminUser: adminUser,
|
|
427
|
+
userPk: adminUser.pk,
|
|
428
|
+
cookies: cookies
|
|
429
|
+
});
|
|
430
|
+
if (!verificationResult || !('ok' in verificationResult)) {
|
|
431
|
+
return { ok: false, error: 'Verification failed' };
|
|
432
|
+
}
|
|
406
433
|
const settingsOrigin = (_b = this.options.passkeys) === null || _b === void 0 ? void 0 : _b.settings.expectedOrigin;
|
|
407
434
|
const rp = {
|
|
408
435
|
name: ((_c = this.options.passkeys) === null || _c === void 0 ? void 0 : _c.settings.rp.name) || this.adminforth.config.customization.brandName,
|
|
@@ -530,7 +557,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
530
557
|
rpID: (_b = this.options.passkeys) === null || _b === void 0 ? void 0 : _b.settings.rp.id,
|
|
531
558
|
userVerification: ((_c = this.options.passkeys) === null || _c === void 0 ? void 0 : _c.settings.authenticatorSelection.userVerification) || "required"
|
|
532
559
|
});
|
|
533
|
-
const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge }, '
|
|
560
|
+
const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge }, 'tempLoginPasskeyChallenge', '10m');
|
|
534
561
|
this.adminforth.auth.setCustomCookie({ response, payload: { name: `passkeyLoginTemporaryJWT`, value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true } });
|
|
535
562
|
return { ok: true, data: options };
|
|
536
563
|
}
|
package/index.ts
CHANGED
|
@@ -27,31 +27,50 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
public async verify(
|
|
30
|
-
|
|
31
|
-
opts?: { adminUser?: AdminUser; userPk?: string }
|
|
30
|
+
confirmationResult: Record<string, any>,
|
|
31
|
+
opts?: { adminUser?: AdminUser; userPk?: string; cookies?: any }
|
|
32
32
|
): Promise<{ ok: true } | { error: string }> {
|
|
33
|
-
if (!
|
|
33
|
+
if (!confirmationResult) return { error: "Confirmation result is required" };
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if (confirmationResult.mode === "totp") {
|
|
36
|
+
const code = confirmationResult.result;
|
|
37
|
+
const authRes = this.adminforth.config.resources
|
|
38
|
+
.find(r => r.resourceId === this.adminforth.config.auth.usersResourceId);
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
if (!authRes) return { error: "Auth resource not found" };
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const connector = this.adminforth.connectors[authRes.dataSource];
|
|
43
|
+
const pkName = authRes.columns.find(c => c.primaryKey)?.name;
|
|
44
|
+
if (!pkName) return { error: "Primary key not found on auth resource" };
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const pk = opts?.userPk ?? opts?.adminUser?.dbUser?.[pkName];
|
|
47
|
+
if (!pk) return { error: "User PK is required" };
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
const user = await connector.getRecordByPrimaryKey(authRes, pk);
|
|
50
|
+
if (!user) return { error: "User not found" };
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const secret = user[this.options.twoFaSecretFieldName];
|
|
53
|
+
if (!secret) return { error: "2FA is not set up for this user" };
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const verified = twofactor.verifyToken(secret, code, this.options.timeStepWindow);
|
|
56
|
+
return verified ? { ok: true } : { error: "Wrong or expired OTP code" };
|
|
57
|
+
} else if (confirmationResult.mode === "passkey") {
|
|
58
|
+
const passkeysCookies = this.adminforth.auth.getCustomCookie({cookies: opts.cookies, name: `passkeyLoginTemporaryJWT`});
|
|
59
|
+
if (!passkeysCookies) {
|
|
60
|
+
return { error: 'Passkey token is required' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const decodedPasskeysCookies = await this.adminforth.auth.verify(passkeysCookies, 'tempLoginPasskeyChallenge', false);
|
|
64
|
+
if (!decodedPasskeysCookies) {
|
|
65
|
+
return { error: 'Invalid passkey' };
|
|
66
|
+
}
|
|
67
|
+
const verificationResult = await this.verifyPasskeyResponse(confirmationResult.result, opts.userPk, decodedPasskeysCookies );
|
|
68
|
+
|
|
69
|
+
if (verificationResult.ok && verificationResult.passkeyConfirmed) {
|
|
70
|
+
return { ok: true }
|
|
71
|
+
}
|
|
72
|
+
return { error: "Invalid passkey" }
|
|
73
|
+
}
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
public parsePeriod(period?: string): number {
|
|
@@ -365,7 +384,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
365
384
|
if (!passkeysCookies) {
|
|
366
385
|
return { error: 'Passkey token is required' };
|
|
367
386
|
}
|
|
368
|
-
const decodedPasskeysCookies = await this.adminforth.auth.verify(passkeysCookies, '
|
|
387
|
+
const decodedPasskeysCookies = await this.adminforth.auth.verify(passkeysCookies, 'tempLoginPasskeyChallenge', false);
|
|
369
388
|
if (!decodedPasskeysCookies) {
|
|
370
389
|
return { error: 'Invalid passkey' };
|
|
371
390
|
}
|
|
@@ -440,8 +459,19 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
440
459
|
method: 'POST',
|
|
441
460
|
path: `/plugin/passkeys/registerPasskeyRequest`,
|
|
442
461
|
noAuth: false,
|
|
443
|
-
handler: async ({ body, adminUser, response }) => {
|
|
462
|
+
handler: async ({ body, adminUser, response, cookies }) => {
|
|
444
463
|
const mode = body?.mode;
|
|
464
|
+
|
|
465
|
+
const confirmationResult = body?.confirmationResult;
|
|
466
|
+
const verificationResult = await this.verify(confirmationResult, {
|
|
467
|
+
adminUser: adminUser,
|
|
468
|
+
userPk: adminUser.pk,
|
|
469
|
+
cookies: cookies
|
|
470
|
+
});
|
|
471
|
+
if ( !verificationResult || !('ok' in verificationResult) ) {
|
|
472
|
+
return { ok: false, error: 'Verification failed' };
|
|
473
|
+
}
|
|
474
|
+
|
|
445
475
|
const settingsOrigin = this.options.passkeys?.settings.expectedOrigin;
|
|
446
476
|
const rp = {
|
|
447
477
|
name: this.options.passkeys?.settings.rp.name || this.adminforth.config.customization.brandName,
|
|
@@ -574,7 +604,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
574
604
|
rpID: this.options.passkeys?.settings.rp.id,
|
|
575
605
|
userVerification: this.options.passkeys?.settings.authenticatorSelection.userVerification || "required"
|
|
576
606
|
});
|
|
577
|
-
const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge }, '
|
|
607
|
+
const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge }, 'tempLoginPasskeyChallenge', '10m');
|
|
578
608
|
this.adminforth.auth.setCustomCookie({response, payload: {name: `passkeyLoginTemporaryJWT`, value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true}});
|
|
579
609
|
return { ok: true, data: options };
|
|
580
610
|
} catch (e) {
|