@adminforth/two-factors-auth 4.1.4 → 4.2.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/TwoFactorsConfirmation.vue +3 -2
- package/custom/utils.js +8 -3
- package/dist/custom/TwoFactorsConfirmation.vue +3 -2
- package/dist/custom/utils.js +8 -3
- package/dist/endpoints/registerPasskeyEndpoints.js +1 -1
- package/dist/handlers/createPasskeyHandlers.d.ts +2 -1
- package/dist/handlers/createPasskeyHandlers.js +5 -1
- package/dist/handlers/createTwoFaHandlers.js +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +12 -2
- package/endpoints/registerPasskeyEndpoints.ts +1 -1
- package/handlers/createPasskeyHandlers.ts +5 -1
- package/handlers/createTwoFaHandlers.ts +5 -0
- package/index.ts +12 -2
- package/package.json +1 -1
package/build.log
CHANGED
|
@@ -17,5 +17,5 @@ custom/tsconfig.json
|
|
|
17
17
|
custom/use2faApi.ts
|
|
18
18
|
custom/utils.js
|
|
19
19
|
|
|
20
|
-
sent 288,
|
|
21
|
-
total size is 287,
|
|
20
|
+
sent 288,176 bytes received 248 bytes 576,848.00 bytes/sec
|
|
21
|
+
total size is 287,203 speedup is 1.00
|
|
@@ -361,8 +361,9 @@
|
|
|
361
361
|
if (response.ok === true) {
|
|
362
362
|
return { _options: response.data, challengeId: response.challengeId };
|
|
363
363
|
} else {
|
|
364
|
-
|
|
365
|
-
|
|
364
|
+
const message = response.error || t('Error creating sign-in request.');
|
|
365
|
+
adminforth.alert({message, variant: 'danger'});
|
|
366
|
+
codeError.value = message;
|
|
366
367
|
}
|
|
367
368
|
}
|
|
368
369
|
|
package/custom/utils.js
CHANGED
|
@@ -57,7 +57,11 @@
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export async function getPasskey() {
|
|
60
|
-
const
|
|
60
|
+
const signIn = await createLoginOptions();
|
|
61
|
+
if (!signIn) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const { _options } = signIn;
|
|
61
65
|
let options;
|
|
62
66
|
try {
|
|
63
67
|
options = PublicKeyCredential.parseRequestOptionsFromJSON(_options);
|
|
@@ -93,8 +97,9 @@
|
|
|
93
97
|
if (response.ok === true) {
|
|
94
98
|
return { _options: response.data, challengeId: response.challengeId };
|
|
95
99
|
} else {
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
const message = response.error || 'Error creating sign-in request.';
|
|
101
|
+
adminforth.alert({message, variant: 'warning'});
|
|
102
|
+
codeError.value = message;
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -361,8 +361,9 @@
|
|
|
361
361
|
if (response.ok === true) {
|
|
362
362
|
return { _options: response.data, challengeId: response.challengeId };
|
|
363
363
|
} else {
|
|
364
|
-
|
|
365
|
-
|
|
364
|
+
const message = response.error || t('Error creating sign-in request.');
|
|
365
|
+
adminforth.alert({message, variant: 'danger'});
|
|
366
|
+
codeError.value = message;
|
|
366
367
|
}
|
|
367
368
|
}
|
|
368
369
|
|
package/dist/custom/utils.js
CHANGED
|
@@ -57,7 +57,11 @@
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export async function getPasskey() {
|
|
60
|
-
const
|
|
60
|
+
const signIn = await createLoginOptions();
|
|
61
|
+
if (!signIn) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const { _options } = signIn;
|
|
61
65
|
let options;
|
|
62
66
|
try {
|
|
63
67
|
options = PublicKeyCredential.parseRequestOptionsFromJSON(_options);
|
|
@@ -93,8 +97,9 @@
|
|
|
93
97
|
if (response.ok === true) {
|
|
94
98
|
return { _options: response.data, challengeId: response.challengeId };
|
|
95
99
|
} else {
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
const message = response.error || 'Error creating sign-in request.';
|
|
101
|
+
adminforth.alert({message, variant: 'warning'});
|
|
102
|
+
codeError.value = message;
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -24,7 +24,7 @@ export function registerPasskeyEndpoints(server, handlers) {
|
|
|
24
24
|
method: 'POST',
|
|
25
25
|
path: `/plugin/passkeys/loginOptions`,
|
|
26
26
|
noAuth: true,
|
|
27
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ response }) { return handlers.createLoginOptions({ response }); }),
|
|
27
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ response, headers }) { return handlers.createLoginOptions({ response, headers }); }),
|
|
28
28
|
});
|
|
29
29
|
server.endpoint({
|
|
30
30
|
method: 'GET',
|
|
@@ -11,8 +11,9 @@ export declare function createPasskeyHandlers(ctx: any): {
|
|
|
11
11
|
adminUser: any;
|
|
12
12
|
cookies: any;
|
|
13
13
|
}) => Promise<any>;
|
|
14
|
-
createLoginOptions: ({ response }: {
|
|
14
|
+
createLoginOptions: ({ response, headers }: {
|
|
15
15
|
response: any;
|
|
16
|
+
headers: any;
|
|
16
17
|
}) => Promise<any>;
|
|
17
18
|
getPasskeys: ({ adminUser }: {
|
|
18
19
|
adminUser: any;
|
|
@@ -30,7 +30,11 @@ export function createPasskeyHandlers(ctx) {
|
|
|
30
30
|
finishRegistration: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, cookies }) {
|
|
31
31
|
return ctx.passkeyService.finishRegistration(body, adminUser, cookies);
|
|
32
32
|
}),
|
|
33
|
-
createLoginOptions: (_a) => __awaiter(this, [_a], void 0, function* ({ response }) {
|
|
33
|
+
createLoginOptions: (_a) => __awaiter(this, [_a], void 0, function* ({ response, headers }) {
|
|
34
|
+
if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
|
|
35
|
+
response.setStatus(429);
|
|
36
|
+
return { error: 'Too many login attempts, please try again later' };
|
|
37
|
+
}
|
|
34
38
|
return ctx.passkeyService.createLoginOptions(response);
|
|
35
39
|
}),
|
|
36
40
|
getPasskeys: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
@@ -74,6 +74,10 @@ export function createTwoFaHandlers(ctx) {
|
|
|
74
74
|
}),
|
|
75
75
|
confirmLoginWithPasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers, requestUrl, query }) {
|
|
76
76
|
var _b;
|
|
77
|
+
if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
|
|
78
|
+
response.setStatus(429);
|
|
79
|
+
return { error: 'Too many login attempts, please try again later' };
|
|
80
|
+
}
|
|
77
81
|
if (!ctx.options.passkeys || ctx.options.passkeys.allowLoginWithPasskeys === false) {
|
|
78
82
|
return { error: 'Login with passkeys is not allowed' };
|
|
79
83
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdminForthPlugin } from "adminforth";
|
|
1
|
+
import { AdminForthPlugin, RateLimiter } from "adminforth";
|
|
2
2
|
import type { AdminForthResource, AdminUser, IAdminForth, IHttpServer, IAdminForthAuth } from "adminforth";
|
|
3
3
|
import { PluginOptions } from "./types.js";
|
|
4
4
|
import { PasskeyService } from "./services/passkeyService.js";
|
|
@@ -15,6 +15,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
15
15
|
totpService: TotpService;
|
|
16
16
|
cookieService: CookieService;
|
|
17
17
|
userRepository: UserRepository;
|
|
18
|
+
passkeyLoginRateLimiters: RateLimiter[];
|
|
18
19
|
private ctx;
|
|
19
20
|
constructor(options: PluginOptions);
|
|
20
21
|
instanceUniqueRepresentation(pluginOptions: any): string;
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthPlugin, suggestIfTypo } from "adminforth";
|
|
10
|
+
import { AdminForthPlugin, suggestIfTypo, RateLimiter } from "adminforth";
|
|
11
11
|
import * as AdminForthRuntime from "adminforth";
|
|
12
12
|
import { PasskeyService } from "./services/passkeyService.js";
|
|
13
13
|
import { TotpService } from "./services/totpService.js";
|
|
@@ -24,6 +24,7 @@ const getRequestWebsocketClientId = AdminForthRuntime.getRequestWebsocketClientI
|
|
|
24
24
|
export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
25
25
|
constructor(options) {
|
|
26
26
|
super(options, import.meta.url);
|
|
27
|
+
this.passkeyLoginRateLimiters = [];
|
|
27
28
|
this.pending = new Map();
|
|
28
29
|
this.options = options;
|
|
29
30
|
this.shouldHaveSingleInstancePerWholeApp = () => true;
|
|
@@ -44,7 +45,10 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
44
45
|
//check if user has 2FA set up
|
|
45
46
|
const users2FASecret = this.userRepository.getSecret(userRecord);
|
|
46
47
|
//check if user has any passkeys registered
|
|
47
|
-
|
|
48
|
+
let userHasPasskeys = false;
|
|
49
|
+
if (this.options.passkeys) {
|
|
50
|
+
userHasPasskeys = yield this.passkeyService.hasPasskeysForUser(adminUser.dbUser[userPkFieldName]);
|
|
51
|
+
}
|
|
48
52
|
// If user has either 2FA secret or any passkeys, they cannot skip
|
|
49
53
|
if (users2FASecret || userHasPasskeys) {
|
|
50
54
|
return { skipAllowed: false };
|
|
@@ -106,6 +110,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
106
110
|
this.cookieService = new CookieService(this.adminforth, this.options);
|
|
107
111
|
this.totpService = new TotpService(this.options, this.userRepository);
|
|
108
112
|
this.passkeyService = new PasskeyService(this.options, this.adminforth, this.userRepository, undefined, this.cookieService);
|
|
113
|
+
this.passkeyLoginRateLimiters = adminforth.config.auth.rateLimit.map((rate) => new RateLimiter(rate));
|
|
109
114
|
this.ctx = {
|
|
110
115
|
adminforth: this.adminforth,
|
|
111
116
|
options: this.options,
|
|
@@ -114,6 +119,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
114
119
|
totpService: this.totpService,
|
|
115
120
|
passkeyService: this.passkeyService,
|
|
116
121
|
cookieService: this.cookieService,
|
|
122
|
+
checkPasskeyLoginRateLimit: (headers) => __awaiter(this, void 0, void 0, function* () {
|
|
123
|
+
const passkeyLoginRateLimitKey = this.adminforth.auth.getClientIp(headers) || 'unknown';
|
|
124
|
+
const rateLimitResults = yield Promise.all(this.passkeyLoginRateLimiters.map((limiter) => limiter.consume(passkeyLoginRateLimitKey)));
|
|
125
|
+
return rateLimitResults.every(Boolean);
|
|
126
|
+
}),
|
|
117
127
|
verifyMfaConfirmation: (confirmationResult, opts) => verifyMfaConfirmation(this.ctx, confirmationResult, opts),
|
|
118
128
|
checkIfSkipSetupAllowSkipVerify: this.checkIfSkipSetupAllowSkipVerify.bind(this),
|
|
119
129
|
autoVerify: {
|
|
@@ -20,7 +20,7 @@ export function registerPasskeyEndpoints(server: IHttpServer, handlers: any): vo
|
|
|
20
20
|
method: 'POST',
|
|
21
21
|
path: `/plugin/passkeys/loginOptions`,
|
|
22
22
|
noAuth: true,
|
|
23
|
-
handler: async ({ response }) => handlers.createLoginOptions({ response }),
|
|
23
|
+
handler: async ({ response, headers }) => handlers.createLoginOptions({ response, headers }),
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
server.endpoint({
|
|
@@ -27,7 +27,11 @@ export function createPasskeyHandlers(ctx: any) {
|
|
|
27
27
|
return ctx.passkeyService.finishRegistration(body, adminUser, cookies);
|
|
28
28
|
},
|
|
29
29
|
|
|
30
|
-
createLoginOptions: async ({ response }) => {
|
|
30
|
+
createLoginOptions: async ({ response, headers }) => {
|
|
31
|
+
if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
|
|
32
|
+
response.setStatus(429);
|
|
33
|
+
return { error: 'Too many login attempts, please try again later' };
|
|
34
|
+
}
|
|
31
35
|
return ctx.passkeyService.createLoginOptions(response);
|
|
32
36
|
},
|
|
33
37
|
|
|
@@ -63,6 +63,11 @@ export function createTwoFaHandlers(ctx: any) {
|
|
|
63
63
|
},
|
|
64
64
|
|
|
65
65
|
confirmLoginWithPasskey: async ({ body, response, cookies, headers, requestUrl, query }) => {
|
|
66
|
+
if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
|
|
67
|
+
response.setStatus(429);
|
|
68
|
+
return { error: 'Too many login attempts, please try again later' };
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
if (!ctx.options.passkeys || ctx.options.passkeys.allowLoginWithPasskeys === false) {
|
|
67
72
|
return { error: 'Login with passkeys is not allowed' };
|
|
68
73
|
}
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdminForthPlugin, suggestIfTypo, HttpExtra } from "adminforth";
|
|
1
|
+
import { AdminForthPlugin, suggestIfTypo, HttpExtra, RateLimiter } from "adminforth";
|
|
2
2
|
import * as AdminForthRuntime from "adminforth";
|
|
3
3
|
import type { AdminForthResource, AdminUser, IAdminForth, IHttpServer, IAdminForthAuth, IAdminForthHttpResponse } from "adminforth";
|
|
4
4
|
import { PluginOptions } from "./types.js"
|
|
@@ -27,6 +27,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
27
27
|
totpService: TotpService;
|
|
28
28
|
cookieService: CookieService;
|
|
29
29
|
userRepository: UserRepository;
|
|
30
|
+
passkeyLoginRateLimiters: RateLimiter[] = [];
|
|
30
31
|
private ctx: any;
|
|
31
32
|
|
|
32
33
|
constructor(options: PluginOptions) {
|
|
@@ -53,7 +54,10 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
53
54
|
//check if user has 2FA set up
|
|
54
55
|
const users2FASecret = this.userRepository.getSecret(userRecord);
|
|
55
56
|
//check if user has any passkeys registered
|
|
56
|
-
|
|
57
|
+
let userHasPasskeys = false;
|
|
58
|
+
if(this.options.passkeys) {
|
|
59
|
+
userHasPasskeys = await this.passkeyService.hasPasskeysForUser(adminUser.dbUser[userPkFieldName]);
|
|
60
|
+
}
|
|
57
61
|
|
|
58
62
|
// If user has either 2FA secret or any passkeys, they cannot skip
|
|
59
63
|
if (users2FASecret || userHasPasskeys) {
|
|
@@ -124,6 +128,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
124
128
|
this.cookieService = new CookieService(this.adminforth, this.options);
|
|
125
129
|
this.totpService = new TotpService(this.options, this.userRepository);
|
|
126
130
|
this.passkeyService = new PasskeyService(this.options, this.adminforth, this.userRepository, undefined, this.cookieService);
|
|
131
|
+
this.passkeyLoginRateLimiters = (adminforth.config.auth as any).rateLimit.map((rate) => new RateLimiter(rate));
|
|
127
132
|
this.ctx = {
|
|
128
133
|
adminforth: this.adminforth,
|
|
129
134
|
options: this.options,
|
|
@@ -132,6 +137,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
132
137
|
totpService: this.totpService,
|
|
133
138
|
passkeyService: this.passkeyService,
|
|
134
139
|
cookieService: this.cookieService,
|
|
140
|
+
checkPasskeyLoginRateLimit: async (headers) => {
|
|
141
|
+
const passkeyLoginRateLimitKey = this.adminforth.auth.getClientIp(headers) || 'unknown';
|
|
142
|
+
const rateLimitResults = await Promise.all(this.passkeyLoginRateLimiters.map((limiter) => limiter.consume(passkeyLoginRateLimitKey)));
|
|
143
|
+
return rateLimitResults.every(Boolean);
|
|
144
|
+
},
|
|
135
145
|
verifyMfaConfirmation: (confirmationResult, opts) => verifyMfaConfirmation(this.ctx, confirmationResult, opts),
|
|
136
146
|
checkIfSkipSetupAllowSkipVerify: this.checkIfSkipSetupAllowSkipVerify.bind(this),
|
|
137
147
|
autoVerify: {
|