@adminforth/two-factors-auth 4.4.0 → 4.4.2

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.
@@ -1,3 +1,5 @@
1
+ import { HttpStatus, respondWithStatus } from "../utils/errors.js";
2
+
1
3
  export function createTwoFaHandlers(ctx: any) {
2
4
  return {
3
5
  initSetup: async ({ cookies }) => {
@@ -11,17 +13,16 @@ export function createTwoFaHandlers(ctx: any) {
11
13
 
12
14
  confirmLogin: async ({ body, response, cookies, headers }) => {
13
15
  if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
14
- response.setStatus(429);
15
- return { error: 'Too many login attempts, please try again later' };
16
+ return respondWithStatus({ error: 'Too many login attempts, please try again later' }, response, HttpStatus.TOO_MANY_REQUESTS);
16
17
  }
17
18
 
18
19
  const totpTemporaryJWT = ctx.cookieService.getTotpTemporary(cookies);
19
20
  if (!totpTemporaryJWT) {
20
- return { error: 'Login session expired. Please log in again.' }
21
+ return respondWithStatus({ error: 'Login session expired. Please log in again.' }, response, HttpStatus.FORBIDDEN)
21
22
  }
22
23
  const decoded = await ctx.cookieService.verifyTotpTemporary(cookies);
23
24
  if (!decoded) {
24
- return { error: 'Login session expired. Please log in again.' }
25
+ return respondWithStatus({ error: 'Login session expired. Please log in again.' }, response, HttpStatus.FORBIDDEN)
25
26
  }
26
27
 
27
28
  if (decoded.newSecret) {
@@ -34,7 +35,7 @@ export function createTwoFaHandlers(ctx: any) {
34
35
  ctx.cookieService.setAuthCookie({expireInDuration: decoded.sessionDuration, response, username:decoded.userName, pk:decoded.pk})
35
36
  return { status: 'ok', allowedLogin: true }
36
37
  } else {
37
- return {error: 'Wrong or expired OTP code'}
38
+ return respondWithStatus({ error: 'Wrong or expired OTP code' }, response, HttpStatus.FORBIDDEN)
38
39
  }
39
40
  }
40
41
 
@@ -43,7 +44,7 @@ export function createTwoFaHandlers(ctx: any) {
43
44
  if (body.usePasskey && ctx.options.passkeys) {
44
45
  const cookiesValidationResult = await ctx.passkeyService.validateLoginChallengeCookie(cookies);
45
46
  if (!cookiesValidationResult.ok) {
46
- return { error: cookiesValidationResult.error };
47
+ return respondWithStatus({ error: cookiesValidationResult.error }, response, HttpStatus.FORBIDDEN);
47
48
  }
48
49
  const res = await ctx.passkeyService.verifyPasskeyResponse(body.passkeyOptions, decoded.pk, cookiesValidationResult.decodedPasskeysCookies);
49
50
  if (res.ok && res.passkeyConfirmed) {
@@ -63,28 +64,27 @@ export function createTwoFaHandlers(ctx: any) {
63
64
  ctx.cookieService.setAuthCookie({expireInDuration: decoded.sessionDuration, response, username:decoded.userName, pk:decoded.pk})
64
65
  return { status: 'ok', allowedLogin: true }
65
66
  } else {
66
- return {error: verificationError}
67
+ return respondWithStatus({ error: verificationError }, response, HttpStatus.FORBIDDEN)
67
68
  }
68
69
  },
69
70
 
70
71
  confirmLoginWithPasskey: async ({ body, response, cookies, headers, requestUrl, query }) => {
71
72
  if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
72
- response.setStatus(429);
73
- return { error: 'Too many login attempts, please try again later' };
73
+ return respondWithStatus({ error: 'Too many login attempts, please try again later' }, response, HttpStatus.TOO_MANY_REQUESTS);
74
74
  }
75
75
 
76
76
  if (!ctx.options.passkeys || ctx.options.passkeys.allowLoginWithPasskeys === false) {
77
- return { error: 'Login with passkeys is not allowed' };
77
+ return respondWithStatus({ error: 'Login with passkeys is not allowed' }, response, HttpStatus.FORBIDDEN);
78
78
  }
79
79
 
80
80
  const passkeyResponse = body.passkeyResponse;
81
81
  if (!passkeyResponse) {
82
- return { error: 'Passkey response is required' };
82
+ return respondWithStatus({ error: 'Passkey response is required' }, response, HttpStatus.BAD_REQUEST);
83
83
  }
84
84
 
85
85
  const passkeyLoginResult = await ctx.passkeyService.getLoginUserByPasskeyResponse(passkeyResponse, cookies);
86
86
  if (!passkeyLoginResult.ok) {
87
- return { error: passkeyLoginResult.error };
87
+ return respondWithStatus({ error: passkeyLoginResult.error }, response, HttpStatus.FORBIDDEN);
88
88
  }
89
89
  const user = passkeyLoginResult.user;
90
90
  const username = user[ctx.adminforth.config.auth.usernameField];
@@ -134,10 +134,10 @@ export function createTwoFaHandlers(ctx: any) {
134
134
  return toReturn;
135
135
  },
136
136
 
137
- skipAllow: async ({ cookies }) => {
137
+ skipAllow: async ({ cookies, response }) => {
138
138
  const decoded = await ctx.cookieService.verifyTotpTemporary(cookies);
139
139
  if (!decoded) {
140
- return { status: "error", message: "Invalid token" };
140
+ return respondWithStatus({ status: "error", message: "Invalid token" }, response, HttpStatus.FORBIDDEN);
141
141
  }
142
142
  if (!decoded.newSecret) {
143
143
  return { status: "ok", skipAllowed: false };
@@ -173,10 +173,14 @@ export function createTwoFaHandlers(ctx: any) {
173
173
  return { skipAllowed: false };
174
174
  },
175
175
 
176
- verifyTotp: async ({ adminUser, body }) => {
177
- if (!body?.code) return { error: 'Code is required' };
176
+ verifyTotp: async ({ adminUser, body, response }) => {
177
+ if (!body?.code) return respondWithStatus({ error: 'Code is required' }, response, HttpStatus.BAD_REQUEST);
178
178
 
179
- return ctx.totpService.verifyAdminUserCode(adminUser, body.code);
179
+ const verificationResult = await ctx.totpService.verifyAdminUserCode(adminUser, body.code);
180
+ if (!verificationResult.ok) {
181
+ return respondWithStatus(verificationResult, response, HttpStatus.FORBIDDEN);
182
+ }
183
+ return verificationResult;
180
184
  },
181
185
  };
182
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "4.4.0",
3
+ "version": "4.4.2",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -170,18 +170,18 @@ export class PasskeyService {
170
170
  public async finishRegistration(body: PasskeyRegistrationPayload, adminUser: AdminUser, cookies: CookieList) {
171
171
  const passkeysCookies = this.cookieService.getRegisterPasskeyTemporary(cookies);
172
172
  if (!passkeysCookies) {
173
- return { error: 'Passkey token is required' };
173
+ return { ok: false, error: 'Passkey token is required' };
174
174
  }
175
175
  const decodedPasskeysCookies = await this.cookieService.verifyRegisterPasskeyTemporary(cookies);
176
176
  if (!decodedPasskeysCookies) {
177
- return { error: 'Invalid passkey token' };
177
+ return { ok: false, error: 'Invalid passkey token' };
178
178
  }
179
179
  if (decodedPasskeysCookies.user_id !== adminUser.pk) {
180
- return { error: 'Invalid user' };
180
+ return { ok: false, error: 'Invalid user' };
181
181
  }
182
182
  const secret = this.userRepository.getSecret(adminUser.dbUser)
183
183
  if (!secret || secret.length === 0) {
184
- return { error: 'TOTP must be set up before registering a passkey' };
184
+ return { ok: false, error: 'TOTP must be set up before registering a passkey' };
185
185
  }
186
186
  const settingsOrigin = this.options.passkeys?.settings.expectedOrigin;
187
187
  const expectedOrigin = body.origin;
@@ -235,7 +235,7 @@ export class PasskeyService {
235
235
  }, adminUser);
236
236
  } catch (error) {
237
237
  console.error(error);
238
- return { error: 'Error registering passkey: ' + errorMessage(error) };
238
+ return { ok: false, error: 'Error registering passkey: ' + errorMessage(error) };
239
239
  }
240
240
  return { ok: true };
241
241
  }
package/utils/errors.ts CHANGED
@@ -1,5 +1,19 @@
1
+ import type { IAdminForthHttpResponse } from "adminforth";
1
2
  import type { ErrorResponse } from "./types.js";
2
3
 
4
+ export enum HttpStatus {
5
+ BAD_REQUEST = 400,
6
+ FORBIDDEN = 403,
7
+ NOT_FOUND = 404,
8
+ TOO_MANY_REQUESTS = 429,
9
+ INTERNAL_SERVER_ERROR = 500,
10
+ }
11
+
12
+ export function respondWithStatus<T>(body: T, response: IAdminForthHttpResponse, status: HttpStatus): T {
13
+ response.setStatus(status);
14
+ return body;
15
+ }
16
+
3
17
  export function errorMessage(error: unknown): string {
4
18
  return error instanceof Error ? error.message : String(error);
5
19
  }