@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 CHANGED
@@ -17,5 +17,5 @@ custom/tsconfig.json
17
17
  custom/use2faApi.ts
18
18
  custom/utils.js
19
19
 
20
- sent 288,092 bytes received 248 bytes 576,680.00 bytes/sec
21
- total size is 287,109 speedup is 1.00
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
- 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',
@@ -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
- const userHasPasskeys = yield this.passkeyService.hasPasskeysForUser(adminUser.dbUser[userPkFieldName]);
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
- const userHasPasskeys = await this.passkeyService.hasPasskeysForUser(adminUser.dbUser[userPkFieldName]);
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "4.1.4",
3
+ "version": "4.2.0",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",