@adminforth/two-factors-auth 3.3.9 → 4.0.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 +4 -4
- package/custom/TwoFactorsConfirmation.vue +9 -10
- package/custom/TwoFactorsPasskeysSettings.vue +10 -10
- package/custom/utils.js +7 -8
- package/dist/custom/TwoFAModal.vue +4 -4
- package/dist/custom/TwoFactorsConfirmation.vue +9 -10
- package/dist/custom/TwoFactorsPasskeysSettings.vue +10 -10
- package/dist/custom/utils.js +7 -8
- package/dist/endpoints/registerPasskeyEndpoints.d.ts +2 -0
- package/dist/endpoints/registerPasskeyEndpoints.js +59 -0
- package/dist/endpoints/registerTwoFaEndpoints.d.ts +2 -0
- package/dist/endpoints/registerTwoFaEndpoints.js +46 -0
- package/dist/handlers/createPasskeyHandlers.d.ts +48 -0
- package/dist/handlers/createPasskeyHandlers.js +99 -0
- package/dist/handlers/createTwoFaHandlers.d.ts +56 -0
- package/dist/handlers/createTwoFaHandlers.js +167 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +51 -858
- package/dist/repositories/passkeyRepository.d.ts +18 -0
- package/dist/repositories/passkeyRepository.js +89 -0
- package/dist/repositories/userRepository.d.ts +14 -0
- package/dist/repositories/userRepository.js +45 -0
- package/dist/services/cookieService.d.ts +52 -0
- package/dist/services/cookieService.js +144 -0
- package/dist/services/passkeyService.d.ts +84 -0
- package/dist/services/passkeyService.js +369 -0
- package/dist/services/totpService.d.ts +21 -0
- package/dist/services/totpService.js +43 -0
- package/dist/services/verifyMfaConfirmation.d.ts +30 -0
- package/dist/services/verifyMfaConfirmation.js +67 -0
- package/dist/types.d.ts +116 -0
- package/dist/utils/errors.d.ts +4 -0
- package/dist/utils/errors.js +9 -0
- package/dist/utils/parsePeriod.d.ts +1 -0
- package/dist/utils/parsePeriod.js +17 -0
- package/dist/utils/types.d.ts +13 -0
- package/dist/utils/types.js +1 -0
- package/endpoints/registerPasskeyEndpoints.ts +61 -0
- package/endpoints/registerTwoFaEndpoints.ts +45 -0
- package/handlers/createPasskeyHandlers.ts +103 -0
- package/handlers/createTwoFaHandlers.ts +172 -0
- package/index.ts +57 -886
- package/package.json +1 -1
- package/repositories/passkeyRepository.ts +78 -0
- package/repositories/userRepository.ts +42 -0
- package/services/cookieService.ts +165 -0
- package/services/passkeyService.ts +375 -0
- package/services/totpService.ts +37 -0
- package/services/verifyMfaConfirmation.ts +92 -0
- package/tsconfig.json +1 -1
- package/utils/errors.ts +13 -0
- package/utils/parsePeriod.ts +19 -0
- package/utils/types.ts +17 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AdminUser } from "adminforth";
|
|
2
|
+
import twofactor from 'node-2fa';
|
|
3
|
+
import type { PluginOptions } from "../types.js";
|
|
4
|
+
import { UserRepository } from "../repositories/userRepository.js";
|
|
5
|
+
|
|
6
|
+
export class TotpService {
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly options: PluginOptions,
|
|
9
|
+
private readonly userRepository: UserRepository,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
public generateSecret(issuerName: string, userName: string): string {
|
|
13
|
+
return twofactor.generateSecret({ name: issuerName, account: userName }).secret;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public verifySecret(secret: string, code: string): boolean {
|
|
17
|
+
return Boolean(twofactor.verifyToken(secret, code, this.options.timeStepWindow));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async verifyUserCode(userPk: string, code: string): Promise<{ ok: true } | { error: string }> {
|
|
21
|
+
const user = await this.userRepository.getAuthUser(userPk);
|
|
22
|
+
if (!user) return { error: "User not found" };
|
|
23
|
+
|
|
24
|
+
const secret = this.userRepository.getSecret(user);
|
|
25
|
+
if (!secret) return { error: "2FA is not set up for this user" };
|
|
26
|
+
|
|
27
|
+
return this.verifySecret(secret, code) ? { ok: true } : { error: "Wrong or expired OTP code" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async verifyAdminUserCode(adminUser: AdminUser, code: string): Promise<{ ok: true } | { error: string }> {
|
|
31
|
+
return this.verifyUserCode(this.userRepository.getUserPk(adminUser), code);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async saveSecret(userPk: string, secret: string): Promise<void> {
|
|
35
|
+
await this.userRepository.updateSecret(userPk, secret);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { AdminUser, HttpExtra, IAdminForthHttpResponse } from "adminforth";
|
|
2
|
+
import type { CookieList } from "../utils/types.js";
|
|
3
|
+
|
|
4
|
+
type TotpConfirmationResult = {
|
|
5
|
+
mode: "totp";
|
|
6
|
+
result: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type PasskeyConfirmationResult = {
|
|
10
|
+
mode: "passkey";
|
|
11
|
+
result: {
|
|
12
|
+
response: string;
|
|
13
|
+
origin: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type GraceConfirmationResult = {
|
|
18
|
+
mode?: undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type MfaConfirmationResult = TotpConfirmationResult | PasskeyConfirmationResult | GraceConfirmationResult;
|
|
22
|
+
|
|
23
|
+
export type VerifyOptions = {
|
|
24
|
+
adminUser: AdminUser;
|
|
25
|
+
userPk: string;
|
|
26
|
+
cookies?: CookieList;
|
|
27
|
+
response?: IAdminForthHttpResponse;
|
|
28
|
+
extra?: HttpExtra;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export async function verifyMfaConfirmation(
|
|
32
|
+
ctx: any,
|
|
33
|
+
confirmationResult: MfaConfirmationResult,
|
|
34
|
+
opts: VerifyOptions,
|
|
35
|
+
): Promise<{ ok: true } | { error: string }> {
|
|
36
|
+
if (!confirmationResult) return { error: "Confirmation result is required" };
|
|
37
|
+
|
|
38
|
+
const cookies = opts.extra?.cookies || opts.cookies;
|
|
39
|
+
const response = opts.extra?.response || opts.response;
|
|
40
|
+
|
|
41
|
+
if (ctx.options.usersFilterToApply) {
|
|
42
|
+
const res = ctx.options.usersFilterToApply(opts.adminUser);
|
|
43
|
+
if ( res === false ) {
|
|
44
|
+
return { ok: true };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (ctx.options.usersFilterToAllowSkipSetup) {
|
|
48
|
+
const res = await ctx.checkIfSkipSetupAllowSkipVerify(opts.adminUser);
|
|
49
|
+
if ( res.skipAllowed === true ) {
|
|
50
|
+
return { ok: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if ( ctx.options.stepUpMfaGracePeriodSeconds && opts.extra?.headers && !confirmationResult.mode) {
|
|
54
|
+
const verificationResult = await ctx.cookieService.isMfaGraceValid(opts.extra.headers, cookies);
|
|
55
|
+
if ( verificationResult === true ) {
|
|
56
|
+
return { ok: true };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const mode = confirmationResult.mode;
|
|
60
|
+
if (!mode) {
|
|
61
|
+
return { error: "Unsupported confirmation mode" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (mode === "totp") {
|
|
65
|
+
const { result: code } = confirmationResult as TotpConfirmationResult;
|
|
66
|
+
const verificationResult = await ctx.totpService.verifyUserCode(opts.userPk, code);
|
|
67
|
+
|
|
68
|
+
if ( 'ok' in verificationResult && verificationResult.ok ) {
|
|
69
|
+
if (ctx.options.stepUpMfaGracePeriodSeconds) {
|
|
70
|
+
ctx.cookieService.issueMfaGrace({ headers: opts.extra?.headers, response }, cookies);
|
|
71
|
+
}
|
|
72
|
+
return { ok: true }
|
|
73
|
+
}
|
|
74
|
+
return verificationResult;
|
|
75
|
+
} else if (mode === "passkey") {
|
|
76
|
+
const { result } = confirmationResult as PasskeyConfirmationResult;
|
|
77
|
+
const cookiesValidationResult = await ctx.passkeyService.validateLoginChallengeCookie(cookies);
|
|
78
|
+
if (!cookiesValidationResult.ok) {
|
|
79
|
+
return { error: cookiesValidationResult.error };
|
|
80
|
+
}
|
|
81
|
+
const verificationResult = await ctx.passkeyService.verifyPasskeyResponse(result, opts.userPk, cookiesValidationResult.decodedPasskeysCookies );
|
|
82
|
+
|
|
83
|
+
if (verificationResult.ok && verificationResult.passkeyConfirmed) {
|
|
84
|
+
if (ctx.options.stepUpMfaGracePeriodSeconds) {
|
|
85
|
+
ctx.cookieService.issueMfaGrace({ headers: opts.extra?.headers, response }, cookies);
|
|
86
|
+
}
|
|
87
|
+
return { ok: true }
|
|
88
|
+
}
|
|
89
|
+
return { error: "Invalid passkey" }
|
|
90
|
+
}
|
|
91
|
+
return { error: "Unsupported confirmation mode" };
|
|
92
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
|
51
51
|
|
|
52
52
|
/* Emit */
|
|
53
|
-
|
|
53
|
+
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
|
54
54
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
|
55
55
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
|
56
56
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
package/utils/errors.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ErrorResponse } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function errorMessage(error: unknown): string {
|
|
4
|
+
return error instanceof Error ? error.message : String(error);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function errorResult(message: string): ErrorResponse {
|
|
8
|
+
return { ok: false, error: message };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function prefixedErrorResult(prefix: string, error: unknown): ErrorResponse {
|
|
12
|
+
return errorResult(`${prefix}${errorMessage(error)}`);
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const PERIOD_RE = /^(\d+)([dhms])$/;
|
|
2
|
+
|
|
3
|
+
export function parsePeriod(period?: string): number {
|
|
4
|
+
if (!period) return 0;
|
|
5
|
+
|
|
6
|
+
const match = PERIOD_RE.exec(period.trim());
|
|
7
|
+
if (!match) throw new Error(`Invalid suggestionPeriod format: ${period}`);
|
|
8
|
+
|
|
9
|
+
const value = parseInt(match[1], 10);
|
|
10
|
+
const unit = match[2];
|
|
11
|
+
|
|
12
|
+
switch (unit) {
|
|
13
|
+
case 'd': return value * 24 * 60 * 60 * 1000;
|
|
14
|
+
case 'h': return value * 60 * 60 * 1000;
|
|
15
|
+
case 'm': return value * 60 * 1000;
|
|
16
|
+
case 's': return value * 1000;
|
|
17
|
+
default: return value;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/utils/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HttpExtra } from "adminforth";
|
|
2
|
+
|
|
3
|
+
export type AnyRecord = Record<string, any>;
|
|
4
|
+
export type CookieList = HttpExtra["cookies"];
|
|
5
|
+
export type HttpHeaders = HttpExtra["headers"];
|
|
6
|
+
|
|
7
|
+
export type ErrorResponse = {
|
|
8
|
+
ok: false;
|
|
9
|
+
code?: string;
|
|
10
|
+
error: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type OkResponse<T extends AnyRecord = AnyRecord> = {
|
|
14
|
+
ok: true;
|
|
15
|
+
} & T;
|
|
16
|
+
|
|
17
|
+
export type PluginResponse<T extends AnyRecord = AnyRecord> = OkResponse<T> | ErrorResponse;
|