@adminforth/two-factors-auth 4.1.5 → 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 +8 -1
- package/endpoints/registerPasskeyEndpoints.ts +1 -1
- package/handlers/createPasskeyHandlers.ts +5 -1
- package/handlers/createTwoFaHandlers.ts +5 -0
- package/index.ts +8 -1
- 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;
|
|
@@ -109,6 +110,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
109
110
|
this.cookieService = new CookieService(this.adminforth, this.options);
|
|
110
111
|
this.totpService = new TotpService(this.options, this.userRepository);
|
|
111
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));
|
|
112
114
|
this.ctx = {
|
|
113
115
|
adminforth: this.adminforth,
|
|
114
116
|
options: this.options,
|
|
@@ -117,6 +119,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
117
119
|
totpService: this.totpService,
|
|
118
120
|
passkeyService: this.passkeyService,
|
|
119
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
|
+
}),
|
|
120
127
|
verifyMfaConfirmation: (confirmationResult, opts) => verifyMfaConfirmation(this.ctx, confirmationResult, opts),
|
|
121
128
|
checkIfSkipSetupAllowSkipVerify: this.checkIfSkipSetupAllowSkipVerify.bind(this),
|
|
122
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) {
|
|
@@ -127,6 +128,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
127
128
|
this.cookieService = new CookieService(this.adminforth, this.options);
|
|
128
129
|
this.totpService = new TotpService(this.options, this.userRepository);
|
|
129
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));
|
|
130
132
|
this.ctx = {
|
|
131
133
|
adminforth: this.adminforth,
|
|
132
134
|
options: this.options,
|
|
@@ -135,6 +137,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
135
137
|
totpService: this.totpService,
|
|
136
138
|
passkeyService: this.passkeyService,
|
|
137
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
|
+
},
|
|
138
145
|
verifyMfaConfirmation: (confirmationResult, opts) => verifyMfaConfirmation(this.ctx, confirmationResult, opts),
|
|
139
146
|
checkIfSkipSetupAllowSkipVerify: this.checkIfSkipSetupAllowSkipVerify.bind(this),
|
|
140
147
|
autoVerify: {
|