From 5742d3068e8efa4422408778851e143d42f54c0b Mon Sep 17 00:00:00 2001 From: Jin <22962980+JYC333@users.noreply.github.com> Date: Fri, 28 Mar 2025 02:15:25 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20move=20totp=20services?= =?UTF-8?q?=20to=20encryption=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../options/multi_factor_authentication.ts | 35 ++++------- src/routes/api/totp.ts | 13 ++-- src/services/encryption/totp_encryption.ts | 9 +-- src/services/options_init.ts | 4 +- src/services/totp.ts | 62 +++++++++++++------ 5 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/multi_factor_authentication.ts b/src/public/app/widgets/type_widgets/options/multi_factor_authentication.ts index 3061becf6..8ed38c2e8 100644 --- a/src/public/app/widgets/type_widgets/options/multi_factor_authentication.ts +++ b/src/public/app/widgets/type_widgets/options/multi_factor_authentication.ts @@ -111,7 +111,7 @@ interface OAuthStatus { } interface TOTPStatus { - enabled: boolean; + set: boolean; message: boolean; } @@ -278,30 +278,21 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget { this.$oauthOptions.hide(); } - server.get("oauth/status").then((result) => { - if (result.enabled) { - if (result.name) this.$UserAccountName.text(result.name); - if (result.email) this.$UserAccountEmail.text(result.email); + // server.get("oauth/status").then((result) => { + // if (result.enabled) { + // if (result.name) this.$UserAccountName.text(result.name); + // if (result.email) this.$UserAccountEmail.text(result.email); - this.$envEnabledOAuth.hide(); - } else { - this.$envEnabledOAuth.text(t("multi_factor_authentication.oauth_enable_description")); - this.$envEnabledOAuth.show(); - } - }); + // this.$envEnabledOAuth.hide(); + // } else { + // this.$envEnabledOAuth.text(t("multi_factor_authentication.oauth_enable_description")); + // this.$envEnabledOAuth.show(); + // } + // }); server.get("totp/status").then((result) => { - if (result.enabled) { - this.$generateTotpButton.prop("disabled", !result.message); - this.$generateRecoveryCodeButton.prop("disabled", !result.message); - - this.$envEnabledTOTP.hide(); - } else { - this.$generateTotpButton.prop("disabled", true); - this.$generateRecoveryCodeButton.prop("disabled", true); - - this.$envEnabledTOTP.text(t("multi_factor_authentication.totp_enable_description")); - this.$envEnabledTOTP.show(); + if (result.set) { + this.$generateTotpButton.text(t("multi_factor_authentication.totp_secret_regenerate")); } }); this.$protectedSessionTimeout.val(Number(options.protectedSessionTimeout)); diff --git a/src/routes/api/totp.ts b/src/routes/api/totp.ts index 7315b664e..ece982960 100644 --- a/src/routes/api/totp.ts +++ b/src/routes/api/totp.ts @@ -1,20 +1,15 @@ -import { generateSecret } from 'time2fa'; -import config from '../../services/config.js'; +import totpService from '../../services/totp.js'; function generateTOTPSecret() { - return { success: true, message: generateSecret() }; -} - -function getTotpEnabled() { - return config.MultiFactorAuthentication.totpEnabled; + return totpService.createSecret(); } function getTOTPStatus() { - return { success: true, message: getTotpEnabled(), enabled: getTotpEnabled() }; + return { success: true, message: totpService.isTotpEnabled(), set: totpService.checkForTotpSecret() }; } function getSecret() { - return config.MultiFactorAuthentication.totpSecret; + return totpService.getTotpSecret(); } export default { diff --git a/src/services/encryption/totp_encryption.ts b/src/services/encryption/totp_encryption.ts index d3db7ad67..bf079cc9d 100644 --- a/src/services/encryption/totp_encryption.ts +++ b/src/services/encryption/totp_encryption.ts @@ -8,7 +8,7 @@ const TOTP_OPTIONS: Record = { SALT: "totpEncryptionSalt", ENCRYPTED_SECRET: "totpEncryptedSecret", VERIFICATION_HASH: "totpVerificationHash" -} as const; +}; function verifyTotpSecret(secret: string): boolean { const givenSecretHash = toBase64(myScryptService.getVerificationHash(secret)); @@ -21,20 +21,17 @@ function verifyTotpSecret(secret: string): boolean { return givenSecretHash === dbSecretHash; } -function setTotpSecret(secret: string): void { +function setTotpSecret(secret: string) { if (!secret) { throw new Error("TOTP secret cannot be empty"); } - // 生成新的加密盐值 const encryptionSalt = randomSecureToken(32); optionService.setOption(TOTP_OPTIONS.SALT, encryptionSalt); - // 使用 scrypt 生成验证哈希 const verificationHash = toBase64(myScryptService.getVerificationHash(secret)); optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, verificationHash); - // 使用数据加密密钥加密 TOTP secret const encryptedSecret = dataEncryptionService.encrypt( Buffer.from(encryptionSalt), secret @@ -67,7 +64,7 @@ function getTotpSecret(): string | null { } } -function resetTotpSecret(): void { +function resetTotpSecret() { optionService.setOption(TOTP_OPTIONS.SALT, ""); optionService.setOption(TOTP_OPTIONS.ENCRYPTED_SECRET, ""); optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, ""); diff --git a/src/services/options_init.ts b/src/services/options_init.ts index 90f26a272..8b64d0119 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -131,10 +131,10 @@ const defaultOptions: DefaultOption[] = [ { name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true }, { name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true }, { name: "editedNotesOpenInRibbon", value: "true", isSynced: true }, - { name: 'totpEnabled', value: 'false', isSynced: true }, + { name: "mfaEnabled", value: "false", isSynced: false }, + { name: 'mfaMethod', value: 'totp', isSynced: false }, { name: 'encryptedRecoveryCodes', value: 'false', isSynced: true }, { name: 'userSubjectIdentifierSaved', value: 'false', isSynced: true }, - { name: 'oAuthEnabled', value: 'false', isSynced: true }, // Appearance { name: "splitEditorOrientation", value: "horizontal", isSynced: true }, diff --git a/src/services/totp.ts b/src/services/totp.ts index e1d1b1a5e..94ab6c713 100644 --- a/src/services/totp.ts +++ b/src/services/totp.ts @@ -1,40 +1,64 @@ -import { Totp } from 'time2fa'; -import config from './config.js'; -import MFAError from '../errors/mfa_error.js'; +import { Totp, generateSecret } from 'time2fa'; +import options from './options.js'; +import totpEncryptionService from './encryption/totp_encryption.js'; +function isTotpEnabled(): boolean { + return options.getOption('mfaEnabled') === "true" && options.getOption('mfaMethod') === "totp"; +} -function isTotpEnabled() { - if (config.MultiFactorAuthentication.totpEnabled && config.MultiFactorAuthentication.totpSecret === "") { - throw new MFAError("TOTP secret is not set!"); +function createSecret(): { success: boolean; message?: string } { + try { + const secret = generateSecret(); + + totpEncryptionService.setTotpSecret(secret); + + return { + success: true, + message: secret + }; + } catch (e) { + console.error('Failed to create TOTP secret:', e); + return { + success: false, + message: e instanceof Error ? e.message : 'Unknown error occurred' + }; } - return config.MultiFactorAuthentication.totpEnabled; } -function getTotpSecret() { - return config.MultiFactorAuthentication.totpSecret; +function getTotpSecret(): string | null { + return totpEncryptionService.getTotpSecret(); } -function checkForTotSecret() { - return config.MultiFactorAuthentication.totpSecret === "" ? false : true; +function checkForTotpSecret(): boolean { + return totpEncryptionService.isTotpSecretSet(); } -function validateTOTP(submittedPasscode: string) { - if (config.MultiFactorAuthentication.totpSecret === "") return false; +function validateTOTP(submittedPasscode: string): boolean { + const secret = getTotpSecret(); + if (!secret) return false; try { - const valid = Totp.validate({ + return Totp.validate({ passcode: submittedPasscode, - secret: config.MultiFactorAuthentication.totpSecret.trim() + secret: secret.trim() }); - return valid; } catch (e) { + console.error('Failed to validate TOTP:', e); return false; } } +function resetTotp(): void { + totpEncryptionService.resetTotpSecret(); + options.setOption('mfaEnabled', 'false'); + options.setOption('mfaMethod', ''); +} + export default { isTotpEnabled, + createSecret, getTotpSecret, - checkForTotSecret, - validateTOTP -}; \ No newline at end of file + checkForTotpSecret, + validateTOTP, + resetTotp +};