@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.
- package/build.log +2 -2
- package/custom/TwoFactorsSetup.vue +15 -11
- package/custom/package-lock.json +418 -0
- package/custom/package.json +3 -0
- package/dist/custom/TwoFactorsSetup.vue +15 -11
- package/dist/custom/package-lock.json +418 -0
- package/dist/custom/package.json +3 -0
- package/dist/endpoints/registerPasskeyEndpoints.js +8 -8
- package/dist/endpoints/registerTwoFaEndpoints.js +5 -5
- package/dist/handlers/createPasskeyHandlers.d.ts +8 -4
- package/dist/handlers/createPasskeyHandlers.js +43 -20
- package/dist/handlers/createTwoFaHandlers.d.ts +6 -12
- package/dist/handlers/createTwoFaHandlers.js +20 -17
- package/dist/services/passkeyService.d.ts +1 -1
- package/dist/services/passkeyService.js +5 -5
- package/dist/utils/errors.d.ts +9 -0
- package/dist/utils/errors.js +12 -0
- package/endpoints/registerPasskeyEndpoints.ts +8 -8
- package/endpoints/registerTwoFaEndpoints.ts +5 -5
- package/handlers/createPasskeyHandlers.ts +51 -20
- package/handlers/createTwoFaHandlers.ts +21 -17
- package/package.json +1 -1
- package/services/passkeyService.ts +5 -5
- package/utils/errors.ts +14 -0
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
@@ -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
|
}
|