mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	feat: 🎸 move totp services to encryption logic
This commit is contained in:
		| @@ -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<OAuthStatus>("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<OAuthStatus>("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<TOTPStatus>("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)); | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -8,7 +8,7 @@ const TOTP_OPTIONS: Record<string, OptionNames> = { | ||||
|     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, ""); | ||||
|   | ||||
| @@ -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 }, | ||||
|   | ||||
| @@ -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() { | ||||
|     if (config.MultiFactorAuthentication.totpEnabled && config.MultiFactorAuthentication.totpSecret === "") { | ||||
|         throw new MFAError("TOTP secret is not set!"); | ||||
|     } | ||||
|     return config.MultiFactorAuthentication.totpEnabled; | ||||
| function isTotpEnabled(): boolean { | ||||
|     return options.getOption('mfaEnabled') === "true" && options.getOption('mfaMethod') === "totp"; | ||||
| } | ||||
|  | ||||
| function getTotpSecret() { | ||||
|     return config.MultiFactorAuthentication.totpSecret; | ||||
| 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' | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function checkForTotSecret() { | ||||
|     return config.MultiFactorAuthentication.totpSecret === "" ? false : true; | ||||
| function getTotpSecret(): string | null { | ||||
|     return totpEncryptionService.getTotpSecret(); | ||||
| } | ||||
|  | ||||
| function validateTOTP(submittedPasscode: string) { | ||||
|     if (config.MultiFactorAuthentication.totpSecret === "") return false; | ||||
| function checkForTotpSecret(): boolean { | ||||
|     return totpEncryptionService.isTotpSecretSet(); | ||||
| } | ||||
|  | ||||
| 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 | ||||
|     checkForTotpSecret, | ||||
|     validateTOTP, | ||||
|     resetTotp | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user