@adminforth/two-factors-auth 4.4.9 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.log +2 -2
- package/custom/TwoFAModal.vue +30 -19
- package/custom/TwoFactorsPasskeysSettings.vue +2 -4
- package/custom/use2faApi.ts +1 -0
- package/dist/custom/TwoFAModal.vue +30 -19
- package/dist/custom/TwoFactorsPasskeysSettings.vue +2 -4
- package/dist/custom/use2faApi.ts +1 -0
- package/dist/endpoints/registerPasskeyEndpoints.js +24 -0
- package/dist/endpoints/registerTwoFaEndpoints.js +18 -0
- package/dist/handlers/createPasskeyHandlers.d.ts +3 -6
- package/dist/handlers/createPasskeyHandlers.js +11 -15
- package/dist/index.d.ts +11 -7
- package/dist/index.js +23 -5
- package/dist/services/mfaRequestContext.d.ts +13 -0
- package/dist/services/mfaRequestContext.js +50 -0
- package/dist/services/totpService.d.ts +9 -10
- package/dist/services/totpService.js +27 -5
- package/dist/services/verifyMfaConfirmation.d.ts +7 -7
- package/dist/services/verifyMfaConfirmation.js +23 -20
- package/dist/utils/errors.d.ts +13 -0
- package/dist/utils/errors.js +14 -0
- package/dist/utils/types.d.ts +8 -0
- package/endpoints/registerPasskeyEndpoints.ts +24 -0
- package/endpoints/registerTwoFaEndpoints.ts +18 -0
- package/handlers/createPasskeyHandlers.ts +30 -16
- package/index.ts +28 -7
- package/package.json +3 -3
- package/services/mfaRequestContext.ts +51 -0
- package/services/totpService.ts +30 -7
- package/services/verifyMfaConfirmation.ts +30 -24
- package/tests/mfaRequestContext.test.mjs +79 -0
- package/tests/totpService.test.mjs +38 -0
- package/utils/errors.ts +14 -0
- package/utils/types.ts +4 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { AdminUser, HttpExtra } from 'adminforth';
|
|
3
|
+
import type { MfaConfirmationResult } from './verifyMfaConfirmation.js';
|
|
4
|
+
import type { VerificationFailure, VerificationResult } from '../utils/types.js';
|
|
5
|
+
|
|
6
|
+
type Context = {
|
|
7
|
+
confirmationResult: MfaConfirmationResult | undefined;
|
|
8
|
+
extra: HttpExtra;
|
|
9
|
+
results: Map<string, Promise<VerificationResult>>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ContextualVerificationFailure = VerificationFailure & { actionDescription?: string };
|
|
13
|
+
|
|
14
|
+
class MfaVerificationError extends Error {
|
|
15
|
+
constructor(readonly result: ContextualVerificationFailure) {
|
|
16
|
+
super(result.error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Keeps verification within one external request, including every CRUD hook it invokes. */
|
|
21
|
+
export class MfaRequestContext {
|
|
22
|
+
private readonly storage = new AsyncLocalStorage<Context>();
|
|
23
|
+
|
|
24
|
+
run<T>(confirmationResult: MfaConfirmationResult | undefined, extra: HttpExtra, callback: () => Promise<T>): Promise<T | ContextualVerificationFailure> {
|
|
25
|
+
return this.storage.run({ confirmationResult, extra, results: new Map() }, async () => {
|
|
26
|
+
try {
|
|
27
|
+
return await callback();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof MfaVerificationError) return error.result;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async verify(
|
|
36
|
+
adminUser: AdminUser,
|
|
37
|
+
actionDescription: string | undefined,
|
|
38
|
+
verifier: (confirmationResult: MfaConfirmationResult | undefined, extra: HttpExtra) => Promise<VerificationResult>,
|
|
39
|
+
): Promise<VerificationResult | undefined> {
|
|
40
|
+
const context = this.storage.getStore();
|
|
41
|
+
if (!context) return undefined;
|
|
42
|
+
let result = context.results.get(adminUser.pk!);
|
|
43
|
+
if (!result) {
|
|
44
|
+
result = verifier(context.confirmationResult, context.extra);
|
|
45
|
+
context.results.set(adminUser.pk!, result);
|
|
46
|
+
}
|
|
47
|
+
const verified = await result;
|
|
48
|
+
if ('error' in verified) throw new MfaVerificationError({ ...verified, actionDescription });
|
|
49
|
+
return verified;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/services/totpService.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { RateLimiter, type AdminUser, type IAdminForthAuth } from "adminforth";
|
|
2
2
|
import twofactor from 'node-2fa';
|
|
3
3
|
import type { PluginOptions } from "../types.js";
|
|
4
4
|
import { UserRepository } from "../repositories/userRepository.js";
|
|
5
|
+
import { TwoFactorErrorCode } from "../utils/errors.js";
|
|
6
|
+
import type { VerificationFailure, VerificationResult } from "../utils/types.js";
|
|
7
|
+
|
|
8
|
+
const CODE_ATTEMPT_RATE_LIMITS = ['30/1m', '300/1h', '1000/1d'];
|
|
9
|
+
const VERIFIED_CODE_JWT_TYPE = 'verifiedTotpCode';
|
|
10
|
+
const VERIFIED_CODE_TOKEN_TTL = '1m';
|
|
5
11
|
|
|
6
12
|
export class TotpService {
|
|
13
|
+
private readonly codeAttemptLimiters = CODE_ATTEMPT_RATE_LIMITS.map((rate) => new RateLimiter(rate));
|
|
14
|
+
|
|
7
15
|
constructor(
|
|
8
16
|
private readonly options: PluginOptions,
|
|
9
17
|
private readonly userRepository: UserRepository,
|
|
18
|
+
private readonly auth: IAdminForthAuth,
|
|
10
19
|
) {}
|
|
11
20
|
|
|
12
21
|
public generateSecret(issuerName: string, userName: string): string {
|
|
@@ -17,18 +26,32 @@ export class TotpService {
|
|
|
17
26
|
return Boolean(twofactor.verifyToken(secret, code, this.options.timeStepWindow));
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
public async verifyUserCode(userPk: string, code: string): Promise<
|
|
29
|
+
public async verifyUserCode(userPk: string, code: string): Promise<VerificationResult> {
|
|
30
|
+
const attemptsAllowed = await Promise.all(this.codeAttemptLimiters.map((limiter) => limiter.consume(userPk)));
|
|
31
|
+
if (!attemptsAllowed.every(Boolean)) {
|
|
32
|
+
return { error: "Too many attempts to enter the code. Try again later.", code: TwoFactorErrorCode.RATE_LIMITED };
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
const user = await this.userRepository.getAuthUser(userPk);
|
|
22
|
-
if (!user) return { error: "User not found" };
|
|
36
|
+
if (!user) return { error: "User not found", code: TwoFactorErrorCode.USER_NOT_FOUND };
|
|
23
37
|
|
|
24
38
|
const secret = this.userRepository.getSecret(user);
|
|
25
|
-
if (!secret) return { error: "2FA is not set up for this user" };
|
|
39
|
+
if (!secret) return { error: "2FA is not set up for this user", code: TwoFactorErrorCode.NOT_SET_UP };
|
|
40
|
+
|
|
41
|
+
return this.verifySecret(secret, code)
|
|
42
|
+
? { ok: true }
|
|
43
|
+
: { error: "Wrong or expired OTP code", code: TwoFactorErrorCode.INVALID };
|
|
44
|
+
}
|
|
26
45
|
|
|
27
|
-
|
|
46
|
+
public async verifyAdminUserCode(adminUser: AdminUser, code: string): Promise<{ ok: true; verifiedCodeToken: string } | VerificationFailure> {
|
|
47
|
+
const result = await this.verifyUserCode(this.userRepository.getUserPk(adminUser), code);
|
|
48
|
+
if ('error' in result) return result;
|
|
49
|
+
return { ok: true, verifiedCodeToken: this.auth.issueJWT({ pk: adminUser.pk }, VERIFIED_CODE_JWT_TYPE, VERIFIED_CODE_TOKEN_TTL) };
|
|
28
50
|
}
|
|
29
51
|
|
|
30
|
-
public async
|
|
31
|
-
|
|
52
|
+
public async isVerifiedCodeToken(token: string, userPk: string): Promise<boolean> {
|
|
53
|
+
const payload = await this.auth.verify(token, VERIFIED_CODE_JWT_TYPE, false);
|
|
54
|
+
return payload?.pk === userPk;
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
public async saveSecret(userPk: string, secret: string): Promise<void> {
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { AdminUser, HttpExtra, IAdminForthHttpResponse } from "adminforth";
|
|
2
|
-
import
|
|
2
|
+
import { TwoFactorErrorCode } from "../utils/errors.js";
|
|
3
|
+
import type { CookieList, VerificationResult } from "../utils/types.js";
|
|
3
4
|
|
|
4
5
|
type TotpConfirmationResult = {
|
|
5
6
|
mode: "totp";
|
|
6
7
|
result: string;
|
|
7
8
|
};
|
|
8
9
|
|
|
10
|
+
type VerifiedTotpConfirmationResult = {
|
|
11
|
+
mode: "verifiedTotp";
|
|
12
|
+
result: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
9
15
|
type PasskeyConfirmationResult = {
|
|
10
16
|
mode: "passkey";
|
|
11
17
|
result: {
|
|
@@ -18,7 +24,7 @@ type GraceConfirmationResult = {
|
|
|
18
24
|
mode?: undefined;
|
|
19
25
|
};
|
|
20
26
|
|
|
21
|
-
export type MfaConfirmationResult = TotpConfirmationResult | PasskeyConfirmationResult | GraceConfirmationResult;
|
|
27
|
+
export type MfaConfirmationResult = TotpConfirmationResult | VerifiedTotpConfirmationResult | PasskeyConfirmationResult | GraceConfirmationResult;
|
|
22
28
|
|
|
23
29
|
export type VerifyOptions = {
|
|
24
30
|
adminUser: AdminUser;
|
|
@@ -30,11 +36,10 @@ export type VerifyOptions = {
|
|
|
30
36
|
|
|
31
37
|
export async function verifyMfaConfirmation(
|
|
32
38
|
ctx: any,
|
|
33
|
-
confirmationResult: MfaConfirmationResult,
|
|
39
|
+
confirmationResult: MfaConfirmationResult | undefined,
|
|
34
40
|
opts: VerifyOptions,
|
|
35
|
-
): Promise<
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
): Promise<VerificationResult> {
|
|
42
|
+
const mode = confirmationResult?.mode;
|
|
38
43
|
const cookies = opts.extra?.cookies || opts.cookies;
|
|
39
44
|
const response = opts.extra?.response || opts.response;
|
|
40
45
|
|
|
@@ -50,43 +55,44 @@ export async function verifyMfaConfirmation(
|
|
|
50
55
|
return { ok: true };
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
|
-
if ( ctx.options.stepUpMfaGracePeriodSeconds && opts.extra?.headers && !
|
|
58
|
+
if ( ctx.options.stepUpMfaGracePeriodSeconds && opts.extra?.headers && !mode) {
|
|
54
59
|
const verificationResult = await ctx.cookieService.isMfaGraceValid(opts.extra.headers, cookies);
|
|
55
60
|
if ( verificationResult === true ) {
|
|
56
61
|
return { ok: true };
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
|
-
const mode = confirmationResult.mode;
|
|
60
64
|
if (!mode) {
|
|
61
|
-
return { error: "
|
|
65
|
+
return { error: "Confirmation result is required", code: TwoFactorErrorCode.REQUIRED };
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
const confirmed = (): VerificationResult => {
|
|
69
|
+
if (ctx.options.stepUpMfaGracePeriodSeconds) {
|
|
70
|
+
ctx.cookieService.issueMfaGrace({ headers: opts.extra?.headers, response }, cookies);
|
|
71
|
+
}
|
|
72
|
+
return { ok: true };
|
|
73
|
+
};
|
|
74
|
+
|
|
64
75
|
if (mode === "totp") {
|
|
65
76
|
const { result: code } = confirmationResult as TotpConfirmationResult;
|
|
66
77
|
const verificationResult = await ctx.totpService.verifyUserCode(opts.userPk, code);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
return verificationResult;
|
|
78
|
+
return 'error' in verificationResult ? verificationResult : confirmed();
|
|
79
|
+
} else if (mode === "verifiedTotp") {
|
|
80
|
+
const { result: token } = confirmationResult as VerifiedTotpConfirmationResult;
|
|
81
|
+
return await ctx.totpService.isVerifiedCodeToken(token, opts.userPk)
|
|
82
|
+
? confirmed()
|
|
83
|
+
: { error: "The code confirmation expired. Enter the code again.", code: TwoFactorErrorCode.INVALID };
|
|
75
84
|
} else if (mode === "passkey") {
|
|
76
85
|
const { result } = confirmationResult as PasskeyConfirmationResult;
|
|
77
86
|
const cookiesValidationResult = await ctx.passkeyService.validateLoginChallengeCookie(cookies);
|
|
78
87
|
if (!cookiesValidationResult.ok) {
|
|
79
|
-
return { error: cookiesValidationResult.error };
|
|
88
|
+
return { error: cookiesValidationResult.error, code: TwoFactorErrorCode.INVALID };
|
|
80
89
|
}
|
|
81
90
|
const verificationResult = await ctx.passkeyService.verifyPasskeyResponse(result, opts.userPk, cookiesValidationResult.decodedPasskeysCookies );
|
|
82
91
|
|
|
83
92
|
if (verificationResult.ok && verificationResult.passkeyConfirmed) {
|
|
84
|
-
|
|
85
|
-
ctx.cookieService.issueMfaGrace({ headers: opts.extra?.headers, response }, cookies);
|
|
86
|
-
}
|
|
87
|
-
return { ok: true }
|
|
93
|
+
return confirmed();
|
|
88
94
|
}
|
|
89
|
-
return { error: "Invalid passkey" }
|
|
95
|
+
return { error: "Invalid passkey", code: TwoFactorErrorCode.INVALID }
|
|
90
96
|
}
|
|
91
|
-
return { error: "Unsupported confirmation mode" };
|
|
97
|
+
return { error: "Unsupported confirmation mode", code: TwoFactorErrorCode.UNSUPPORTED_MODE };
|
|
92
98
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { MfaRequestContext } from '../dist/services/mfaRequestContext.js';
|
|
4
|
+
const user = { pk: 'user-1' };
|
|
5
|
+
|
|
6
|
+
test('without external context normal browser verification is unaffected', async () => {
|
|
7
|
+
const context = new MfaRequestContext();
|
|
8
|
+
assert.equal(await context.verify(user, undefined, () => assert.fail('verification must not run')), undefined);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('missing verification stops a CRUD hook before it can strip the error code', async () => {
|
|
12
|
+
const context = new MfaRequestContext();
|
|
13
|
+
let written = false;
|
|
14
|
+
const result = await context.run(undefined, {}, async () => {
|
|
15
|
+
await context.verify(user, undefined, async () => ({ error: 'Required', code: 'TWO_FACTOR_REQUIRED' }));
|
|
16
|
+
written = true;
|
|
17
|
+
return { ok: true };
|
|
18
|
+
});
|
|
19
|
+
assert.equal(written, false);
|
|
20
|
+
assert.equal(result.code, 'TWO_FACTOR_REQUIRED');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('a failed verification carries the action description for the confirmation page', async () => {
|
|
24
|
+
const context = new MfaRequestContext();
|
|
25
|
+
const result = await context.run(undefined, {}, async () => {
|
|
26
|
+
await context.verify(user, 'Approve $100,000 bonus', async () => ({ error: 'Required', code: 'TWO_FACTOR_REQUIRED' }));
|
|
27
|
+
});
|
|
28
|
+
assert.deepEqual(result, { error: 'Required', code: 'TWO_FACTOR_REQUIRED', actionDescription: 'Approve $100,000 bonus' });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('invalid TOTP or passkey cannot reach the mutation', async () => {
|
|
32
|
+
for (const mode of ['totp', 'passkey']) {
|
|
33
|
+
const context = new MfaRequestContext();
|
|
34
|
+
const result = await context.run({ mode, result: 'invalid' }, {}, async () => {
|
|
35
|
+
await context.verify(user, undefined, async () => ({ error: 'Invalid', code: 'TWO_FACTOR_INVALID' }));
|
|
36
|
+
assert.fail('mutation must not execute');
|
|
37
|
+
});
|
|
38
|
+
assert.equal(result.code, 'TWO_FACTOR_INVALID');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('valid verification is reused only within the same request and user', async () => {
|
|
43
|
+
const context = new MfaRequestContext();
|
|
44
|
+
const token = { mode: 'totp', result: '123456' };
|
|
45
|
+
const extra = { cookies: [{ key: 'session', value: 'browser' }] };
|
|
46
|
+
let checks = 0;
|
|
47
|
+
const verify = async (result, passedExtra) => { checks++; assert.equal(result, token); assert.equal(passedExtra, extra); return { ok: true }; };
|
|
48
|
+
const run = () => context.run(token, extra, async () => {
|
|
49
|
+
await context.verify(user, undefined, verify);
|
|
50
|
+
await context.verify(user, undefined, verify);
|
|
51
|
+
await context.verify({ pk: 'other-user' }, undefined, verify);
|
|
52
|
+
return { ok: true };
|
|
53
|
+
});
|
|
54
|
+
await run();
|
|
55
|
+
assert.equal(checks, 2);
|
|
56
|
+
await run();
|
|
57
|
+
assert.equal(checks, 4);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('concurrent requests do not share verification or cookies', async () => {
|
|
61
|
+
const context = new MfaRequestContext();
|
|
62
|
+
const run = (token, cookie, code) => context.run(token, { cookie }, async () => {
|
|
63
|
+
await context.verify(user, undefined, async (received, extra) => {
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
65
|
+
assert.equal(received, token);
|
|
66
|
+
assert.equal(extra.cookie, cookie);
|
|
67
|
+
return code ? { error: 'Required', code } : { ok: true };
|
|
68
|
+
});
|
|
69
|
+
return { ok: true };
|
|
70
|
+
});
|
|
71
|
+
const [accepted, rejected] = await Promise.all([run('valid', 'a'), run(undefined, 'b', 'TWO_FACTOR_REQUIRED')]);
|
|
72
|
+
assert.equal(accepted.ok, true);
|
|
73
|
+
assert.equal(rejected.code, 'TWO_FACTOR_REQUIRED');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('unrelated execution errors propagate to MCP', async () => {
|
|
77
|
+
const context = new MfaRequestContext();
|
|
78
|
+
await assert.rejects(context.run(undefined, {}, async () => { throw new Error('DB error'); }), /DB error/);
|
|
79
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import twofactor from 'node-2fa';
|
|
4
|
+
import { TotpService } from '../dist/services/totpService.js';
|
|
5
|
+
import { TwoFactorErrorCode } from '../dist/utils/errors.js';
|
|
6
|
+
|
|
7
|
+
const SECRET = 'JBSWY3DPEHPK3PXP';
|
|
8
|
+
const userRepository = { getAuthUser: async (pk) => ({ pk }), getSecret: () => SECRET, getUserPk: (adminUser) => adminUser.pk };
|
|
9
|
+
|
|
10
|
+
function createService() {
|
|
11
|
+
const jwts = new Map();
|
|
12
|
+
const auth = {
|
|
13
|
+
issueJWT: (payload, type) => {
|
|
14
|
+
const token = `jwt-${jwts.size}`;
|
|
15
|
+
jwts.set(token, { ...payload, type });
|
|
16
|
+
return token;
|
|
17
|
+
},
|
|
18
|
+
verify: async (token, type) => (jwts.get(token)?.type === type ? jwts.get(token) : null),
|
|
19
|
+
};
|
|
20
|
+
return new TotpService({ timeStepWindow: 1 }, userRepository, auth);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('limits code attempts of each user to stop brute force of a stolen session', async () => {
|
|
24
|
+
const service = createService();
|
|
25
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
26
|
+
assert.equal((await service.verifyUserCode('user-1', 'wrong')).code, TwoFactorErrorCode.INVALID);
|
|
27
|
+
}
|
|
28
|
+
assert.equal((await service.verifyUserCode('user-1', 'wrong')).code, TwoFactorErrorCode.RATE_LIMITED);
|
|
29
|
+
assert.equal((await service.verifyUserCode('user-2', 'wrong')).code, TwoFactorErrorCode.INVALID);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('a correct code gives a token that confirms the code only for its user', async () => {
|
|
33
|
+
const service = createService();
|
|
34
|
+
const { verifiedCodeToken } = await service.verifyAdminUserCode({ pk: 'user-1' }, twofactor.generateToken(SECRET).token);
|
|
35
|
+
assert.equal(await service.isVerifiedCodeToken(verifiedCodeToken, 'user-1'), true);
|
|
36
|
+
assert.equal(await service.isVerifiedCodeToken(verifiedCodeToken, 'user-2'), false);
|
|
37
|
+
assert.equal(await service.isVerifiedCodeToken('forged', 'user-1'), false);
|
|
38
|
+
});
|
package/utils/errors.ts
CHANGED
|
@@ -9,6 +9,20 @@ export enum HttpStatus {
|
|
|
9
9
|
INTERNAL_SERVER_ERROR = 500,
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export enum TwoFactorErrorCode {
|
|
13
|
+
REQUIRED = 'TWO_FACTOR_REQUIRED',
|
|
14
|
+
INVALID = 'TWO_FACTOR_INVALID',
|
|
15
|
+
RATE_LIMITED = 'TWO_FACTOR_RATE_LIMITED',
|
|
16
|
+
NOT_SET_UP = 'TWO_FACTOR_NOT_SET_UP',
|
|
17
|
+
USER_NOT_FOUND = 'USER_NOT_FOUND',
|
|
18
|
+
UNSUPPORTED_MODE = 'UNSUPPORTED_CONFIRMATION_MODE',
|
|
19
|
+
BROWSER_CONTEXT_REQUIRED = 'BROWSER_CONTEXT_REQUIRED',
|
|
20
|
+
CANCELLED = 'VERIFICATION_CANCELLED',
|
|
21
|
+
TIMEOUT = 'VERIFICATION_TIMEOUT',
|
|
22
|
+
INVALID_SESSION = 'INVALID_VERIFICATION_SESSION',
|
|
23
|
+
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
export function respondWithStatus<T>(body: T, response: IAdminForthHttpResponse, status: HttpStatus): T {
|
|
13
27
|
response.setStatus(status);
|
|
14
28
|
return body;
|
package/utils/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HttpExtra } from "adminforth";
|
|
2
|
+
import type { TwoFactorErrorCode } from "./errors.js";
|
|
2
3
|
|
|
3
4
|
export type AnyRecord = Record<string, any>;
|
|
4
5
|
export type CookieList = HttpExtra["cookies"];
|
|
@@ -15,3 +16,6 @@ export type OkResponse<T extends AnyRecord = AnyRecord> = {
|
|
|
15
16
|
} & T;
|
|
16
17
|
|
|
17
18
|
export type PluginResponse<T extends AnyRecord = AnyRecord> = OkResponse<T> | ErrorResponse;
|
|
19
|
+
|
|
20
|
+
export type VerificationFailure = { error: string; code: TwoFactorErrorCode };
|
|
21
|
+
export type VerificationResult = { ok: true } | VerificationFailure;
|