@adminforth/two-factors-auth 4.4.0 → 4.4.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.
@@ -23,8 +23,8 @@ export function registerPasskeyEndpoints(server, handlers) {
23
23
  path: `/plugin/passkeys/finishRegistration`,
24
24
  noAuth: false,
25
25
  request_schema: finishRegistrationBodySchema,
26
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, cookies }) {
27
- return handlers.finishRegistration({ body, adminUser, cookies });
26
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, cookies, response }) {
27
+ return handlers.finishRegistration({ body, adminUser, cookies, response });
28
28
  }),
29
29
  });
30
30
  server.endpoint({
@@ -37,15 +37,15 @@ export function registerPasskeyEndpoints(server, handlers) {
37
37
  method: 'GET',
38
38
  path: `/plugin/passkeys/getPasskeys`,
39
39
  noAuth: false,
40
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) { return handlers.getPasskeys({ adminUser }); }),
40
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, response }) { return handlers.getPasskeys({ adminUser, response }); }),
41
41
  });
42
42
  server.endpoint({
43
43
  method: 'DELETE',
44
44
  path: `/plugin/passkeys/deletePasskey`,
45
45
  noAuth: false,
46
46
  request_schema: deletePasskeyBodySchema,
47
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
48
- return handlers.deletePasskey({ body, adminUser });
47
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
48
+ return handlers.deletePasskey({ body, adminUser, response });
49
49
  }),
50
50
  });
51
51
  server.endpoint({
@@ -53,15 +53,15 @@ export function registerPasskeyEndpoints(server, handlers) {
53
53
  path: `/plugin/passkeys/renamePasskey`,
54
54
  noAuth: false,
55
55
  request_schema: renamePasskeyBodySchema,
56
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
57
- return handlers.renamePasskey({ body, adminUser });
56
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
57
+ return handlers.renamePasskey({ body, adminUser, response });
58
58
  }),
59
59
  });
60
60
  server.endpoint({
61
61
  method: 'POST',
62
62
  path: `/plugin/passkeys/checkIfUserHasPasskeys`,
63
63
  noAuth: true,
64
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) { return handlers.checkIfUserHasPasskeys({ cookies }); }),
64
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies, response }) { return handlers.checkIfUserHasPasskeys({ cookies, response }); }),
65
65
  });
66
66
  server.endpoint({
67
67
  method: 'POST',
@@ -13,7 +13,7 @@ export function registerTwoFaEndpoints(server, handlers) {
13
13
  method: 'POST',
14
14
  path: `/plugin/twofa/initSetup`,
15
15
  noAuth: true,
16
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) { return handlers.initSetup({ cookies }); }),
16
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies, response }) { return handlers.initSetup({ cookies, response }); }),
17
17
  });
18
18
  server.endpoint({
19
19
  method: 'POST',
@@ -37,20 +37,20 @@ export function registerTwoFaEndpoints(server, handlers) {
37
37
  method: "GET",
38
38
  path: "/plugin/twofa/skip-allow",
39
39
  noAuth: true,
40
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) { return handlers.skipAllow({ cookies }); }),
40
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies, response }) { return handlers.skipAllow({ cookies, response }); }),
41
41
  });
42
42
  server.endpoint({
43
43
  method: "GET",
44
44
  path: "/plugin/twofa/skip-allow-modal",
45
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, headers, cookies }) { return handlers.skipAllowModal({ adminUser, headers, cookies }); }),
45
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, headers, cookies, response }) { return handlers.skipAllowModal({ adminUser, headers, cookies, response }); }),
46
46
  });
47
47
  server.endpoint({
48
48
  method: 'POST',
49
49
  path: `/plugin/twofa/verify`,
50
50
  noAuth: false,
51
51
  request_schema: verifyTotpBodySchema,
52
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body }) {
53
- return handlers.verifyTotp({ adminUser, body });
52
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body, response }) {
53
+ return handlers.verifyTotp({ adminUser, body, response });
54
54
  }),
55
55
  });
56
56
  }
@@ -6,10 +6,11 @@ export declare function createPasskeyHandlers(ctx: any): {
6
6
  cookies: any;
7
7
  headers: any;
8
8
  }) => Promise<any>;
9
- finishRegistration: ({ body, adminUser, cookies }: {
9
+ finishRegistration: ({ body, adminUser, cookies, response }: {
10
10
  body: any;
11
11
  adminUser: any;
12
12
  cookies: any;
13
+ response: any;
13
14
  }) => Promise<any>;
14
15
  createLoginOptions: ({ response, headers }: {
15
16
  response: any;
@@ -18,16 +19,19 @@ export declare function createPasskeyHandlers(ctx: any): {
18
19
  getPasskeys: ({ adminUser }: {
19
20
  adminUser: any;
20
21
  }) => Promise<any>;
21
- deletePasskey: ({ body, adminUser }: {
22
+ deletePasskey: ({ body, adminUser, response }: {
22
23
  body: any;
23
24
  adminUser: any;
25
+ response: any;
24
26
  }) => Promise<any>;
25
- renamePasskey: ({ body, adminUser }: {
27
+ renamePasskey: ({ body, adminUser, response }: {
26
28
  body: any;
27
29
  adminUser: any;
30
+ response: any;
28
31
  }) => Promise<any>;
29
- checkIfUserHasPasskeys: ({ cookies }: {
32
+ checkIfUserHasPasskeys: ({ cookies, response }: {
30
33
  cookies: any;
34
+ response: any;
31
35
  }) => Promise<any>;
32
36
  resolveVerifyAuto: ({ body, adminUser, response, cookies, headers }: {
33
37
  body: any;
@@ -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 { errorMessage } from "../utils/errors.js";
10
+ import { HttpStatus, errorMessage, respondWithStatus } from "../utils/errors.js";
11
11
  export function createPasskeyHandlers(ctx) {
12
12
  return {
13
13
  createRegistrationOptions: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies, headers }) {
@@ -23,31 +23,54 @@ export function createPasskeyHandlers(ctx) {
23
23
  }
24
24
  });
25
25
  if (!verificationResult || !('ok' in verificationResult)) {
26
- return { ok: false, error: 'error' in verificationResult ? verificationResult.error : 'Verification failed' };
26
+ return respondWithStatus({ ok: false, error: 'error' in verificationResult ? verificationResult.error : 'Verification failed' }, response, HttpStatus.FORBIDDEN);
27
27
  }
28
- return ctx.passkeyService.createRegistrationOptions(mode, adminUser, response);
28
+ const registrationOptions = yield ctx.passkeyService.createRegistrationOptions(mode, adminUser, response);
29
+ if (!registrationOptions.ok) {
30
+ return respondWithStatus(registrationOptions, response, HttpStatus.NOT_FOUND);
31
+ }
32
+ return registrationOptions;
29
33
  }),
30
- finishRegistration: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, cookies }) {
31
- return ctx.passkeyService.finishRegistration(body, adminUser, cookies);
34
+ finishRegistration: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, cookies, response }) {
35
+ const registrationResult = yield ctx.passkeyService.finishRegistration(body, adminUser, cookies);
36
+ if (!registrationResult.ok) {
37
+ return respondWithStatus(registrationResult, response, HttpStatus.FORBIDDEN);
38
+ }
39
+ return registrationResult;
32
40
  }),
33
41
  createLoginOptions: (_a) => __awaiter(this, [_a], void 0, function* ({ response, headers }) {
34
42
  if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
35
- response.setStatus(429);
36
- return { error: 'Too many login attempts, please try again later' };
43
+ return respondWithStatus({ error: 'Too many login attempts, please try again later' }, response, HttpStatus.TOO_MANY_REQUESTS);
44
+ }
45
+ const loginOptions = yield ctx.passkeyService.createLoginOptions(response);
46
+ if (!loginOptions.ok) {
47
+ return respondWithStatus(loginOptions, response, HttpStatus.INTERNAL_SERVER_ERROR);
37
48
  }
38
- return ctx.passkeyService.createLoginOptions(response);
49
+ return loginOptions;
39
50
  }),
40
51
  getPasskeys: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
41
52
  return ctx.passkeyService.getPasskeys(adminUser);
42
53
  }),
43
- deletePasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
44
- return ctx.passkeyService.deletePasskey(body.passkeyId, adminUser);
54
+ deletePasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
55
+ const deleteResult = yield ctx.passkeyService.deletePasskey(body.passkeyId, adminUser);
56
+ if (!deleteResult.ok) {
57
+ return respondWithStatus(deleteResult, response, HttpStatus.NOT_FOUND);
58
+ }
59
+ return deleteResult;
45
60
  }),
46
- renamePasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
47
- return ctx.passkeyService.renamePasskey(body.passkeyId, body.newName, adminUser);
61
+ renamePasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
62
+ const renameResult = yield ctx.passkeyService.renamePasskey(body.passkeyId, body.newName, adminUser);
63
+ if (!renameResult.ok) {
64
+ return respondWithStatus(renameResult, response, HttpStatus.NOT_FOUND);
65
+ }
66
+ return renameResult;
48
67
  }),
49
- checkIfUserHasPasskeys: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) {
50
- return ctx.passkeyService.checkIfUserHasPasskeys(cookies);
68
+ checkIfUserHasPasskeys: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies, response }) {
69
+ const checkResult = yield ctx.passkeyService.checkIfUserHasPasskeys(cookies);
70
+ if (checkResult.error) {
71
+ return respondWithStatus(checkResult, response, HttpStatus.FORBIDDEN);
72
+ }
73
+ return checkResult;
51
74
  }),
52
75
  resolveVerifyAuto: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies, headers }) {
53
76
  var _b;
@@ -63,15 +86,15 @@ export function createPasskeyHandlers(ctx) {
63
86
  };
64
87
  try {
65
88
  if (!idsToResolve.length || !confirmationResult) {
66
- return (resolveAllIdsAsFailed('Confirmation window was closed', 'VERIFICATION_CANCELLED'));
89
+ return respondWithStatus(resolveAllIdsAsFailed('Confirmation window was closed', 'VERIFICATION_CANCELLED'), response, HttpStatus.BAD_REQUEST);
67
90
  }
68
91
  for (const id of idsToResolve) {
69
92
  const validationResult = yield ctx.adminforth.auth.verify(id, 'auto2FA', false);
70
93
  if (!validationResult) {
71
- return (resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
94
+ return respondWithStatus(resolveAllIdsAsFailed('Invalid session ID or confirmation result'), response, HttpStatus.FORBIDDEN);
72
95
  }
73
96
  if (validationResult.adminUserPk !== adminUser.pk) {
74
- return (resolveAllIdsAsFailed('Session does not belong to the authenticated user'));
97
+ return respondWithStatus(resolveAllIdsAsFailed('Session does not belong to the authenticated user'), response, HttpStatus.FORBIDDEN);
75
98
  }
76
99
  }
77
100
  const verificationResult = yield ctx.verifyMfaConfirmation(confirmationResult, {
@@ -84,7 +107,7 @@ export function createPasskeyHandlers(ctx) {
84
107
  }
85
108
  });
86
109
  if (!verificationResult || !('ok' in verificationResult)) {
87
- return (resolveAllIdsAsFailed((_b = verificationResult === null || verificationResult === void 0 ? void 0 : verificationResult.error) !== null && _b !== void 0 ? _b : 'Verification failed'));
110
+ return respondWithStatus(resolveAllIdsAsFailed((_b = verificationResult === null || verificationResult === void 0 ? void 0 : verificationResult.error) !== null && _b !== void 0 ? _b : 'Verification failed'), response, HttpStatus.FORBIDDEN);
88
111
  }
89
112
  if ('ok' in verificationResult && verificationResult.ok) {
90
113
  for (const id of idsToResolve) {
@@ -92,11 +115,11 @@ export function createPasskeyHandlers(ctx) {
92
115
  }
93
116
  return { ok: true };
94
117
  }
95
- return (resolveAllIdsAsFailed('Verification failed'));
118
+ return respondWithStatus(resolveAllIdsAsFailed('Verification failed'), response, HttpStatus.FORBIDDEN);
96
119
  }
97
120
  catch (error) {
98
121
  console.error('[AdminForth 2FA] Error resolving automatic 2FA verification', error);
99
- return (resolveAllIdsAsFailed(errorMessage(error)));
122
+ return respondWithStatus(resolveAllIdsAsFailed(errorMessage(error)), response, HttpStatus.INTERNAL_SERVER_ERROR);
100
123
  }
101
124
  }),
102
125
  };
@@ -11,13 +11,10 @@ export declare function createTwoFaHandlers(ctx: any): {
11
11
  cookies: any;
12
12
  headers: any;
13
13
  }) => Promise<{
14
+ error: any;
15
+ } | {
14
16
  status: string;
15
17
  allowedLogin: boolean;
16
- error?: undefined;
17
- } | {
18
- error: any;
19
- status?: undefined;
20
- allowedLogin?: undefined;
21
18
  }>;
22
19
  confirmLoginWithPasskey: ({ body, response, cookies, headers, requestUrl, query }: {
23
20
  body: any;
@@ -27,21 +24,17 @@ export declare function createTwoFaHandlers(ctx: any): {
27
24
  requestUrl: any;
28
25
  query: any;
29
26
  }) => Promise<{
30
- allowedLogin: boolean;
31
- error: string;
32
- } | {
33
27
  error: any;
34
28
  }>;
35
- skipAllow: ({ cookies }: {
29
+ skipAllow: ({ cookies, response }: {
36
30
  cookies: any;
31
+ response: any;
37
32
  }) => Promise<{
38
33
  status: string;
39
34
  message: string;
40
- skipAllowed?: undefined;
41
35
  } | {
42
36
  status: string;
43
37
  skipAllowed: any;
44
- message?: undefined;
45
38
  }>;
46
39
  skipAllowModal: ({ adminUser, headers, cookies }: {
47
40
  adminUser: any;
@@ -50,8 +43,9 @@ export declare function createTwoFaHandlers(ctx: any): {
50
43
  }) => Promise<{
51
44
  skipAllowed: boolean;
52
45
  }>;
53
- verifyTotp: ({ adminUser, body }: {
46
+ verifyTotp: ({ adminUser, body, response }: {
54
47
  adminUser: any;
55
48
  body: any;
49
+ response: any;
56
50
  }) => Promise<any>;
57
51
  };
@@ -7,6 +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 { HttpStatus, respondWithStatus } from "../utils/errors.js";
10
11
  export function createTwoFaHandlers(ctx) {
11
12
  return {
12
13
  initSetup: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) {
@@ -20,16 +21,15 @@ export function createTwoFaHandlers(ctx) {
20
21
  confirmLogin: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers }) {
21
22
  var _b, _c;
22
23
  if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
23
- response.setStatus(429);
24
- return { error: 'Too many login attempts, please try again later' };
24
+ return respondWithStatus({ error: 'Too many login attempts, please try again later' }, response, HttpStatus.TOO_MANY_REQUESTS);
25
25
  }
26
26
  const totpTemporaryJWT = ctx.cookieService.getTotpTemporary(cookies);
27
27
  if (!totpTemporaryJWT) {
28
- return { error: 'Login session expired. Please log in again.' };
28
+ return respondWithStatus({ error: 'Login session expired. Please log in again.' }, response, HttpStatus.FORBIDDEN);
29
29
  }
30
30
  const decoded = yield ctx.cookieService.verifyTotpTemporary(cookies);
31
31
  if (!decoded) {
32
- return { error: 'Login session expired. Please log in again.' };
32
+ return respondWithStatus({ error: 'Login session expired. Please log in again.' }, response, HttpStatus.FORBIDDEN);
33
33
  }
34
34
  if (decoded.newSecret) {
35
35
  const verified = body.skip && decoded.userCanSkipSetup ? true : ctx.totpService.verifySecret(decoded.newSecret, body.code);
@@ -42,7 +42,7 @@ export function createTwoFaHandlers(ctx) {
42
42
  return { status: 'ok', allowedLogin: true };
43
43
  }
44
44
  else {
45
- return { error: 'Wrong or expired OTP code' };
45
+ return respondWithStatus({ error: 'Wrong or expired OTP code' }, response, HttpStatus.FORBIDDEN);
46
46
  }
47
47
  }
48
48
  let verified = false;
@@ -50,7 +50,7 @@ export function createTwoFaHandlers(ctx) {
50
50
  if (body.usePasskey && ctx.options.passkeys) {
51
51
  const cookiesValidationResult = yield ctx.passkeyService.validateLoginChallengeCookie(cookies);
52
52
  if (!cookiesValidationResult.ok) {
53
- return { error: cookiesValidationResult.error };
53
+ return respondWithStatus({ error: cookiesValidationResult.error }, response, HttpStatus.FORBIDDEN);
54
54
  }
55
55
  const res = yield ctx.passkeyService.verifyPasskeyResponse(body.passkeyOptions, decoded.pk, cookiesValidationResult.decodedPasskeysCookies);
56
56
  if (res.ok && res.passkeyConfirmed) {
@@ -73,25 +73,24 @@ export function createTwoFaHandlers(ctx) {
73
73
  return { status: 'ok', allowedLogin: true };
74
74
  }
75
75
  else {
76
- return { error: verificationError };
76
+ return respondWithStatus({ error: verificationError }, response, HttpStatus.FORBIDDEN);
77
77
  }
78
78
  }),
79
79
  confirmLoginWithPasskey: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers, requestUrl, query }) {
80
80
  var _b;
81
81
  if (!(yield ctx.checkPasskeyLoginRateLimit(headers))) {
82
- response.setStatus(429);
83
- return { error: 'Too many login attempts, please try again later' };
82
+ return respondWithStatus({ error: 'Too many login attempts, please try again later' }, response, HttpStatus.TOO_MANY_REQUESTS);
84
83
  }
85
84
  if (!ctx.options.passkeys || ctx.options.passkeys.allowLoginWithPasskeys === false) {
86
- return { error: 'Login with passkeys is not allowed' };
85
+ return respondWithStatus({ error: 'Login with passkeys is not allowed' }, response, HttpStatus.FORBIDDEN);
87
86
  }
88
87
  const passkeyResponse = body.passkeyResponse;
89
88
  if (!passkeyResponse) {
90
- return { error: 'Passkey response is required' };
89
+ return respondWithStatus({ error: 'Passkey response is required' }, response, HttpStatus.BAD_REQUEST);
91
90
  }
92
91
  const passkeyLoginResult = yield ctx.passkeyService.getLoginUserByPasskeyResponse(passkeyResponse, cookies);
93
92
  if (!passkeyLoginResult.ok) {
94
- return { error: passkeyLoginResult.error };
93
+ return respondWithStatus({ error: passkeyLoginResult.error }, response, HttpStatus.FORBIDDEN);
95
94
  }
96
95
  const user = passkeyLoginResult.user;
97
96
  const username = user[ctx.adminforth.config.auth.usernameField];
@@ -130,10 +129,10 @@ export function createTwoFaHandlers(ctx) {
130
129
  }
131
130
  return toReturn;
132
131
  }),
133
- skipAllow: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) {
132
+ skipAllow: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies, response }) {
134
133
  const decoded = yield ctx.cookieService.verifyTotpTemporary(cookies);
135
134
  if (!decoded) {
136
- return { status: "error", message: "Invalid token" };
135
+ return respondWithStatus({ status: "error", message: "Invalid token" }, response, HttpStatus.FORBIDDEN);
137
136
  }
138
137
  if (!decoded.newSecret) {
139
138
  return { status: "ok", skipAllowed: false };
@@ -166,10 +165,14 @@ export function createTwoFaHandlers(ctx) {
166
165
  }
167
166
  return { skipAllowed: false };
168
167
  }),
169
- verifyTotp: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body }) {
168
+ verifyTotp: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body, response }) {
170
169
  if (!(body === null || body === void 0 ? void 0 : body.code))
171
- return { error: 'Code is required' };
172
- return ctx.totpService.verifyAdminUserCode(adminUser, body.code);
170
+ return respondWithStatus({ error: 'Code is required' }, response, HttpStatus.BAD_REQUEST);
171
+ const verificationResult = yield ctx.totpService.verifyAdminUserCode(adminUser, body.code);
172
+ if (!verificationResult.ok) {
173
+ return respondWithStatus(verificationResult, response, HttpStatus.FORBIDDEN);
174
+ }
175
+ return verificationResult;
173
176
  }),
174
177
  };
175
178
  }
@@ -40,8 +40,8 @@ export declare class PasskeyService {
40
40
  data: import("@simplewebauthn/server").PublicKeyCredentialCreationOptionsJSON;
41
41
  }>;
42
42
  finishRegistration(body: PasskeyRegistrationPayload, adminUser: AdminUser, cookies: CookieList): Promise<{
43
+ ok: boolean;
43
44
  error: string;
44
- ok?: undefined;
45
45
  } | {
46
46
  ok: boolean;
47
47
  error?: undefined;
@@ -161,18 +161,18 @@ export class PasskeyService {
161
161
  var _a, _b, _c, _d, _e, _f;
162
162
  const passkeysCookies = this.cookieService.getRegisterPasskeyTemporary(cookies);
163
163
  if (!passkeysCookies) {
164
- return { error: 'Passkey token is required' };
164
+ return { ok: false, error: 'Passkey token is required' };
165
165
  }
166
166
  const decodedPasskeysCookies = yield this.cookieService.verifyRegisterPasskeyTemporary(cookies);
167
167
  if (!decodedPasskeysCookies) {
168
- return { error: 'Invalid passkey token' };
168
+ return { ok: false, error: 'Invalid passkey token' };
169
169
  }
170
170
  if (decodedPasskeysCookies.user_id !== adminUser.pk) {
171
- return { error: 'Invalid user' };
171
+ return { ok: false, error: 'Invalid user' };
172
172
  }
173
173
  const secret = this.userRepository.getSecret(adminUser.dbUser);
174
174
  if (!secret || secret.length === 0) {
175
- return { error: 'TOTP must be set up before registering a passkey' };
175
+ return { ok: false, error: 'TOTP must be set up before registering a passkey' };
176
176
  }
177
177
  const settingsOrigin = (_a = this.options.passkeys) === null || _a === void 0 ? void 0 : _a.settings.expectedOrigin;
178
178
  const expectedOrigin = body.origin;
@@ -220,7 +220,7 @@ export class PasskeyService {
220
220
  }
221
221
  catch (error) {
222
222
  console.error(error);
223
- return { error: 'Error registering passkey: ' + errorMessage(error) };
223
+ return { ok: false, error: 'Error registering passkey: ' + errorMessage(error) };
224
224
  }
225
225
  return { ok: true };
226
226
  });
@@ -1,4 +1,13 @@
1
+ import type { IAdminForthHttpResponse } from "adminforth";
1
2
  import type { ErrorResponse } from "./types.js";
3
+ export declare enum HttpStatus {
4
+ BAD_REQUEST = 400,
5
+ FORBIDDEN = 403,
6
+ NOT_FOUND = 404,
7
+ TOO_MANY_REQUESTS = 429,
8
+ INTERNAL_SERVER_ERROR = 500
9
+ }
10
+ export declare function respondWithStatus<T>(body: T, response: IAdminForthHttpResponse, status: HttpStatus): T;
2
11
  export declare function errorMessage(error: unknown): string;
3
12
  export declare function errorResult(message: string): ErrorResponse;
4
13
  export declare function prefixedErrorResult(prefix: string, error: unknown): ErrorResponse;
@@ -1,3 +1,15 @@
1
+ export var HttpStatus;
2
+ (function (HttpStatus) {
3
+ HttpStatus[HttpStatus["BAD_REQUEST"] = 400] = "BAD_REQUEST";
4
+ HttpStatus[HttpStatus["FORBIDDEN"] = 403] = "FORBIDDEN";
5
+ HttpStatus[HttpStatus["NOT_FOUND"] = 404] = "NOT_FOUND";
6
+ HttpStatus[HttpStatus["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
7
+ HttpStatus[HttpStatus["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
8
+ })(HttpStatus || (HttpStatus = {}));
9
+ export function respondWithStatus(body, response, status) {
10
+ response.setStatus(status);
11
+ return body;
12
+ }
1
13
  export function errorMessage(error) {
2
14
  return error instanceof Error ? error.message : String(error);
3
15
  }
@@ -23,8 +23,8 @@ export function registerPasskeyEndpoints(server: IHttpServer, handlers: any): vo
23
23
  path: `/plugin/passkeys/finishRegistration`,
24
24
  noAuth: false,
25
25
  request_schema: finishRegistrationBodySchema,
26
- handler: async ({ body, adminUser, cookies }) => {
27
- return handlers.finishRegistration({ body, adminUser, cookies });
26
+ handler: async ({ body, adminUser, cookies, response }) => {
27
+ return handlers.finishRegistration({ body, adminUser, cookies, response });
28
28
  },
29
29
  });
30
30
 
@@ -39,7 +39,7 @@ export function registerPasskeyEndpoints(server: IHttpServer, handlers: any): vo
39
39
  method: 'GET',
40
40
  path: `/plugin/passkeys/getPasskeys`,
41
41
  noAuth: false,
42
- handler: async ({ adminUser }) => handlers.getPasskeys({ adminUser }),
42
+ handler: async ({ adminUser, response }) => handlers.getPasskeys({ adminUser, response }),
43
43
  });
44
44
 
45
45
  server.endpoint({
@@ -47,8 +47,8 @@ export function registerPasskeyEndpoints(server: IHttpServer, handlers: any): vo
47
47
  path: `/plugin/passkeys/deletePasskey`,
48
48
  noAuth: false,
49
49
  request_schema: deletePasskeyBodySchema,
50
- handler: async ({ body, adminUser }) => {
51
- return handlers.deletePasskey({ body, adminUser });
50
+ handler: async ({ body, adminUser, response }) => {
51
+ return handlers.deletePasskey({ body, adminUser, response });
52
52
  },
53
53
  });
54
54
 
@@ -57,8 +57,8 @@ export function registerPasskeyEndpoints(server: IHttpServer, handlers: any): vo
57
57
  path: `/plugin/passkeys/renamePasskey`,
58
58
  noAuth: false,
59
59
  request_schema: renamePasskeyBodySchema,
60
- handler: async ({ body, adminUser }) => {
61
- return handlers.renamePasskey({ body, adminUser });
60
+ handler: async ({ body, adminUser, response }) => {
61
+ return handlers.renamePasskey({ body, adminUser, response });
62
62
  },
63
63
  });
64
64
 
@@ -66,7 +66,7 @@ export function registerPasskeyEndpoints(server: IHttpServer, handlers: any): vo
66
66
  method: 'POST',
67
67
  path: `/plugin/passkeys/checkIfUserHasPasskeys`,
68
68
  noAuth: true,
69
- handler: async ({ cookies }) => handlers.checkIfUserHasPasskeys({ cookies }),
69
+ handler: async ({ cookies, response }) => handlers.checkIfUserHasPasskeys({ cookies, response }),
70
70
  });
71
71
 
72
72
  server.endpoint({
@@ -10,7 +10,7 @@ export function registerTwoFaEndpoints(server: IHttpServer, handlers: any): void
10
10
  method: 'POST',
11
11
  path: `/plugin/twofa/initSetup`,
12
12
  noAuth: true,
13
- handler: async ({ cookies }) => handlers.initSetup({ cookies }),
13
+ handler: async ({ cookies, response }) => handlers.initSetup({ cookies, response }),
14
14
  });
15
15
 
16
16
  server.endpoint({
@@ -37,13 +37,13 @@ export function registerTwoFaEndpoints(server: IHttpServer, handlers: any): void
37
37
  method: "GET",
38
38
  path: "/plugin/twofa/skip-allow",
39
39
  noAuth: true,
40
- handler: async ({ cookies }) => handlers.skipAllow({ cookies }),
40
+ handler: async ({ cookies, response }) => handlers.skipAllow({ cookies, response }),
41
41
  });
42
42
 
43
43
  server.endpoint({
44
44
  method: "GET",
45
45
  path: "/plugin/twofa/skip-allow-modal",
46
- handler: async ({ adminUser, headers, cookies }) => handlers.skipAllowModal({ adminUser, headers, cookies }),
46
+ handler: async ({ adminUser, headers, cookies, response }) => handlers.skipAllowModal({ adminUser, headers, cookies, response }),
47
47
  });
48
48
 
49
49
  server.endpoint({
@@ -51,8 +51,8 @@ export function registerTwoFaEndpoints(server: IHttpServer, handlers: any): void
51
51
  path: `/plugin/twofa/verify`,
52
52
  noAuth: false,
53
53
  request_schema: verifyTotpBodySchema,
54
- handler: async ({ adminUser, body }) => {
55
- return handlers.verifyTotp({ adminUser, body });
54
+ handler: async ({ adminUser, body, response }) => {
55
+ return handlers.verifyTotp({ adminUser, body, response });
56
56
  },
57
57
  });
58
58
  }
@@ -1,5 +1,5 @@
1
1
  import type { HttpExtra } from "adminforth";
2
- import { errorMessage } from "../utils/errors.js";
2
+ import { HttpStatus, errorMessage, respondWithStatus } from "../utils/errors.js";
3
3
 
4
4
  export function createPasskeyHandlers(ctx: any) {
5
5
  return {
@@ -17,38 +17,65 @@ export function createPasskeyHandlers(ctx: any) {
17
17
  } as HttpExtra
18
18
  });
19
19
  if (!verificationResult || !('ok' in verificationResult)) {
20
- return { ok: false, error: 'error' in verificationResult ? verificationResult.error : 'Verification failed' };
20
+ return respondWithStatus(
21
+ { ok: false, error: 'error' in verificationResult ? verificationResult.error : 'Verification failed' },
22
+ response,
23
+ HttpStatus.FORBIDDEN,
24
+ );
21
25
  }
22
26
 
23
- return ctx.passkeyService.createRegistrationOptions(mode, adminUser, response);
27
+ const registrationOptions = await ctx.passkeyService.createRegistrationOptions(mode, adminUser, response);
28
+ if (!registrationOptions.ok) {
29
+ return respondWithStatus(registrationOptions, response, HttpStatus.NOT_FOUND);
30
+ }
31
+ return registrationOptions;
24
32
  },
25
33
 
26
- finishRegistration: async ({ body, adminUser, cookies }) => {
27
- return ctx.passkeyService.finishRegistration(body, adminUser, cookies);
34
+ finishRegistration: async ({ body, adminUser, cookies, response }) => {
35
+ const registrationResult = await ctx.passkeyService.finishRegistration(body, adminUser, cookies);
36
+ if (!registrationResult.ok) {
37
+ return respondWithStatus(registrationResult, response, HttpStatus.FORBIDDEN);
38
+ }
39
+ return registrationResult;
28
40
  },
29
41
 
30
42
  createLoginOptions: async ({ response, headers }) => {
31
43
  if (!(await ctx.checkPasskeyLoginRateLimit(headers))) {
32
- response.setStatus(429);
33
- return { error: 'Too many login attempts, please try again later' };
44
+ return respondWithStatus({ error: 'Too many login attempts, please try again later' }, response, HttpStatus.TOO_MANY_REQUESTS);
45
+ }
46
+ const loginOptions = await ctx.passkeyService.createLoginOptions(response);
47
+ if (!loginOptions.ok) {
48
+ return respondWithStatus(loginOptions, response, HttpStatus.INTERNAL_SERVER_ERROR);
34
49
  }
35
- return ctx.passkeyService.createLoginOptions(response);
50
+ return loginOptions;
36
51
  },
37
52
 
38
53
  getPasskeys: async ({ adminUser }) => {
39
54
  return ctx.passkeyService.getPasskeys(adminUser);
40
55
  },
41
56
 
42
- deletePasskey: async ({ body, adminUser }) => {
43
- return ctx.passkeyService.deletePasskey(body.passkeyId, adminUser);
57
+ deletePasskey: async ({ body, adminUser, response }) => {
58
+ const deleteResult = await ctx.passkeyService.deletePasskey(body.passkeyId, adminUser);
59
+ if (!deleteResult.ok) {
60
+ return respondWithStatus(deleteResult, response, HttpStatus.NOT_FOUND);
61
+ }
62
+ return deleteResult;
44
63
  },
45
64
 
46
- renamePasskey: async ({ body, adminUser }) => {
47
- return ctx.passkeyService.renamePasskey(body.passkeyId, body.newName, adminUser);
65
+ renamePasskey: async ({ body, adminUser, response }) => {
66
+ const renameResult = await ctx.passkeyService.renamePasskey(body.passkeyId, body.newName, adminUser);
67
+ if (!renameResult.ok) {
68
+ return respondWithStatus(renameResult, response, HttpStatus.NOT_FOUND);
69
+ }
70
+ return renameResult;
48
71
  },
49
72
 
50
- checkIfUserHasPasskeys: async ({ cookies }) => {
51
- return ctx.passkeyService.checkIfUserHasPasskeys(cookies);
73
+ checkIfUserHasPasskeys: async ({ cookies, response }) => {
74
+ const checkResult = await ctx.passkeyService.checkIfUserHasPasskeys(cookies);
75
+ if (checkResult.error) {
76
+ return respondWithStatus(checkResult, response, HttpStatus.FORBIDDEN);
77
+ }
78
+ return checkResult;
52
79
  },
53
80
 
54
81
  resolveVerifyAuto: async ({ body, adminUser, response, cookies, headers }) => {
@@ -66,16 +93,20 @@ export function createPasskeyHandlers(ctx: any) {
66
93
 
67
94
  try {
68
95
  if (!idsToResolve.length || !confirmationResult) {
69
- return(resolveAllIdsAsFailed('Confirmation window was closed', 'VERIFICATION_CANCELLED'));
96
+ return respondWithStatus(
97
+ resolveAllIdsAsFailed('Confirmation window was closed', 'VERIFICATION_CANCELLED'),
98
+ response,
99
+ HttpStatus.BAD_REQUEST,
100
+ );
70
101
  }
71
102
 
72
103
  for (const id of idsToResolve) {
73
104
  const validationResult = await ctx.adminforth.auth.verify(id, 'auto2FA', false);
74
105
  if (!validationResult) {
75
- return(resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
106
+ return respondWithStatus(resolveAllIdsAsFailed('Invalid session ID or confirmation result'), response, HttpStatus.FORBIDDEN);
76
107
  }
77
108
  if (validationResult.adminUserPk !== adminUser.pk) {
78
- return(resolveAllIdsAsFailed('Session does not belong to the authenticated user'));
109
+ return respondWithStatus(resolveAllIdsAsFailed('Session does not belong to the authenticated user'), response, HttpStatus.FORBIDDEN);
79
110
  }
80
111
  }
81
112
 
@@ -89,7 +120,7 @@ export function createPasskeyHandlers(ctx: any) {
89
120
  } as HttpExtra
90
121
  });
91
122
  if ( !verificationResult || !('ok' in verificationResult) ) {
92
- return(resolveAllIdsAsFailed(verificationResult?.error ?? 'Verification failed'));
123
+ return respondWithStatus(resolveAllIdsAsFailed(verificationResult?.error ?? 'Verification failed'), response, HttpStatus.FORBIDDEN);
93
124
  }
94
125
  if ('ok' in verificationResult && verificationResult.ok){
95
126
  for (const id of idsToResolve) {
@@ -97,10 +128,10 @@ export function createPasskeyHandlers(ctx: any) {
97
128
  }
98
129
  return { ok: true };
99
130
  }
100
- return(resolveAllIdsAsFailed('Verification failed'));
131
+ return respondWithStatus(resolveAllIdsAsFailed('Verification failed'), response, HttpStatus.FORBIDDEN);
101
132
  } catch (error) {
102
133
  console.error('[AdminForth 2FA] Error resolving automatic 2FA verification', error);
103
- return(resolveAllIdsAsFailed(errorMessage(error)));
134
+ return respondWithStatus(resolveAllIdsAsFailed(errorMessage(error)), response, HttpStatus.INTERNAL_SERVER_ERROR);
104
135
  }
105
136
  },
106
137
  };
@@ -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.1",
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
  }