@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.
- 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
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
});
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/errors.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
33
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
}
|