@adminforth/two-factors-auth 4.1.5 → 4.2.1

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 CHANGED
@@ -17,5 +17,5 @@ custom/tsconfig.json
17
17
  custom/use2faApi.ts
18
18
  custom/utils.js
19
19
 
20
- sent 288,082 bytes received 248 bytes 576,660.00 bytes/sec
21
- total size is 287,109 speedup is 1.00
20
+ sent 288,163 bytes received 248 bytes 576,822.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
- adminforth.alert({message: t('Error creating sign-in request.'), variant: 'danger'});
365
- codeError.value = 'Error creating sign-in request.';
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 { _options } = await createLoginOptions();
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
- adminforth.alert({message: 'Error creating sign-in request.', variant: 'warning'});
97
- codeError.value = 'Error creating sign-in request.';
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
- adminforth.alert({message: t('Error creating sign-in request.'), variant: 'danger'});
365
- codeError.value = 'Error creating sign-in request.';
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
 
@@ -57,7 +57,11 @@
57
57
  }
58
58
 
59
59
  export async function getPasskey() {
60
- const { _options } = await createLoginOptions();
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
- adminforth.alert({message: 'Error creating sign-in request.', variant: 'warning'});
97
- codeError.value = 'Error creating sign-in request.';
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',
@@ -18,7 +18,7 @@ export function registerTwoFaEndpoints(server, handlers) {
18
18
  method: 'POST',
19
19
  path: `/plugin/twofa/confirmLogin`,
20
20
  noAuth: true,
21
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies }) { return handlers.confirmLogin({ body, response, cookies }); }),
21
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers }) { return handlers.confirmLogin({ body, response, cookies, headers }); }),
22
22
  });
23
23
  server.endpoint({
24
24
  method: 'POST',
@@ -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 }) {
@@ -5,10 +5,11 @@ export declare function createTwoFaHandlers(ctx: any): {
5
5
  totpJWT: any;
6
6
  status: string;
7
7
  }>;
8
- confirmLogin: ({ body, response, cookies }: {
8
+ confirmLogin: ({ body, response, cookies, headers }: {
9
9
  body: any;
10
10
  response: any;
11
11
  cookies: any;
12
+ headers: any;
12
13
  }) => Promise<{
13
14
  status: string;
14
15
  allowedLogin: boolean;
@@ -17,8 +17,12 @@ export function createTwoFaHandlers(ctx) {
17
17
  }
18
18
  return toReturn;
19
19
  }),
20
- confirmLogin: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies }) {
20
+ confirmLogin: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers }) {
21
21
  var _b, _c;
22
+ if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
23
+ response.setStatus(429);
24
+ return { error: 'Too many login attempts, please try again later' };
25
+ }
22
26
  const totpTemporaryJWT = ctx.cookieService.getTotpTemporary(cookies);
23
27
  if (!totpTemporaryJWT) {
24
28
  return { error: 'Login session expired. Please log in again.' };
@@ -74,6 +78,10 @@ export function createTwoFaHandlers(ctx) {
74
78
  }),
75
79
  confirmLoginWithPasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers, requestUrl, query }) {
76
80
  var _b;
81
+ if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
82
+ response.setStatus(429);
83
+ return { error: 'Too many login attempts, please try again later' };
84
+ }
77
85
  if (!ctx.options.passkeys || ctx.options.passkeys.allowLoginWithPasskeys === false) {
78
86
  return { error: 'Login with passkeys is not allowed' };
79
87
  }
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({
@@ -12,7 +12,7 @@ export function registerTwoFaEndpoints(server: IHttpServer, handlers: any): void
12
12
  method: 'POST',
13
13
  path: `/plugin/twofa/confirmLogin`,
14
14
  noAuth: true,
15
- handler: async ({ body, response, cookies }) => handlers.confirmLogin({ body, response, cookies }),
15
+ handler: async ({ body, response, cookies, headers }) => handlers.confirmLogin({ body, response, cookies, headers }),
16
16
  });
17
17
 
18
18
  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
 
@@ -9,7 +9,12 @@ export function createTwoFaHandlers(ctx: any) {
9
9
  return toReturn
10
10
  },
11
11
 
12
- confirmLogin: async ({ body, response, cookies }) => {
12
+ confirmLogin: async ({ body, response, cookies, headers }) => {
13
+ if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
14
+ response.setStatus(429);
15
+ return { error: 'Too many login attempts, please try again later' };
16
+ }
17
+
13
18
  const totpTemporaryJWT = ctx.cookieService.getTotpTemporary(cookies);
14
19
  if (!totpTemporaryJWT) {
15
20
  return { error: 'Login session expired. Please log in again.' }
@@ -63,6 +68,11 @@ export function createTwoFaHandlers(ctx: any) {
63
68
  },
64
69
 
65
70
  confirmLoginWithPasskey: async ({ body, response, cookies, headers, requestUrl, query }) => {
71
+ if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
72
+ response.setStatus(429);
73
+ return { error: 'Too many login attempts, please try again later' };
74
+ }
75
+
66
76
  if (!ctx.options.passkeys || ctx.options.passkeys.allowLoginWithPasskeys === false) {
67
77
  return { error: 'Login with passkeys is not allowed' };
68
78
  }
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "4.1.5",
3
+ "version": "4.2.1",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",