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 { | interface TOTPStatus { | ||||||
|     enabled: boolean; |     set: boolean; | ||||||
|     message: boolean; |     message: boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -278,30 +278,21 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget { | |||||||
|                 this.$oauthOptions.hide(); |                 this.$oauthOptions.hide(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             server.get<OAuthStatus>("oauth/status").then((result) => { |             // server.get<OAuthStatus>("oauth/status").then((result) => { | ||||||
|                 if (result.enabled) { |             //     if (result.enabled) { | ||||||
|                     if (result.name) this.$UserAccountName.text(result.name); |             //         if (result.name) this.$UserAccountName.text(result.name); | ||||||
|                     if (result.email) this.$UserAccountEmail.text(result.email); |             //         if (result.email) this.$UserAccountEmail.text(result.email); | ||||||
|  |  | ||||||
|                     this.$envEnabledOAuth.hide(); |             //         this.$envEnabledOAuth.hide(); | ||||||
|                 } else { |             //     } else { | ||||||
|                     this.$envEnabledOAuth.text(t("multi_factor_authentication.oauth_enable_description")); |             //         this.$envEnabledOAuth.text(t("multi_factor_authentication.oauth_enable_description")); | ||||||
|                     this.$envEnabledOAuth.show(); |             //         this.$envEnabledOAuth.show(); | ||||||
|                 } |             //     } | ||||||
|             }); |             // }); | ||||||
|  |  | ||||||
|             server.get<TOTPStatus>("totp/status").then((result) => { |             server.get<TOTPStatus>("totp/status").then((result) => { | ||||||
|                 if (result.enabled) { |                 if (result.set) { | ||||||
|                     this.$generateTotpButton.prop("disabled", !result.message); |                     this.$generateTotpButton.text(t("multi_factor_authentication.totp_secret_regenerate")); | ||||||
|                     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(); |  | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|             this.$protectedSessionTimeout.val(Number(options.protectedSessionTimeout)); |             this.$protectedSessionTimeout.val(Number(options.protectedSessionTimeout)); | ||||||
|   | |||||||
| @@ -1,20 +1,15 @@ | |||||||
| import { generateSecret } from 'time2fa'; | import totpService from '../../services/totp.js'; | ||||||
| import config from '../../services/config.js'; |  | ||||||
|  |  | ||||||
| function generateTOTPSecret() { | function generateTOTPSecret() { | ||||||
|     return { success: true, message: generateSecret() }; |     return totpService.createSecret(); | ||||||
| } |  | ||||||
|  |  | ||||||
| function getTotpEnabled() { |  | ||||||
|     return config.MultiFactorAuthentication.totpEnabled; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function getTOTPStatus() { | function getTOTPStatus() { | ||||||
|     return { success: true, message: getTotpEnabled(), enabled: getTotpEnabled() }; |     return { success: true, message: totpService.isTotpEnabled(), set: totpService.checkForTotpSecret() }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function getSecret() { | function getSecret() { | ||||||
|     return config.MultiFactorAuthentication.totpSecret; |     return totpService.getTotpSecret(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ const TOTP_OPTIONS: Record<string, OptionNames> = { | |||||||
|     SALT: "totpEncryptionSalt", |     SALT: "totpEncryptionSalt", | ||||||
|     ENCRYPTED_SECRET: "totpEncryptedSecret", |     ENCRYPTED_SECRET: "totpEncryptedSecret", | ||||||
|     VERIFICATION_HASH: "totpVerificationHash" |     VERIFICATION_HASH: "totpVerificationHash" | ||||||
| } as const; | }; | ||||||
|  |  | ||||||
| function verifyTotpSecret(secret: string): boolean { | function verifyTotpSecret(secret: string): boolean { | ||||||
|     const givenSecretHash = toBase64(myScryptService.getVerificationHash(secret)); |     const givenSecretHash = toBase64(myScryptService.getVerificationHash(secret)); | ||||||
| @@ -21,20 +21,17 @@ function verifyTotpSecret(secret: string): boolean { | |||||||
|     return givenSecretHash === dbSecretHash; |     return givenSecretHash === dbSecretHash; | ||||||
| } | } | ||||||
|  |  | ||||||
| function setTotpSecret(secret: string): void { | function setTotpSecret(secret: string) { | ||||||
|     if (!secret) { |     if (!secret) { | ||||||
|         throw new Error("TOTP secret cannot be empty"); |         throw new Error("TOTP secret cannot be empty"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 生成新的加密盐值 |  | ||||||
|     const encryptionSalt = randomSecureToken(32); |     const encryptionSalt = randomSecureToken(32); | ||||||
|     optionService.setOption(TOTP_OPTIONS.SALT, encryptionSalt); |     optionService.setOption(TOTP_OPTIONS.SALT, encryptionSalt); | ||||||
|  |  | ||||||
|     // 使用 scrypt 生成验证哈希 |  | ||||||
|     const verificationHash = toBase64(myScryptService.getVerificationHash(secret)); |     const verificationHash = toBase64(myScryptService.getVerificationHash(secret)); | ||||||
|     optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, verificationHash); |     optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, verificationHash); | ||||||
|  |  | ||||||
|     // 使用数据加密密钥加密 TOTP secret |  | ||||||
|     const encryptedSecret = dataEncryptionService.encrypt( |     const encryptedSecret = dataEncryptionService.encrypt( | ||||||
|         Buffer.from(encryptionSalt), |         Buffer.from(encryptionSalt), | ||||||
|         secret |         secret | ||||||
| @@ -67,7 +64,7 @@ function getTotpSecret(): string | null { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function resetTotpSecret(): void { | function resetTotpSecret() { | ||||||
|     optionService.setOption(TOTP_OPTIONS.SALT, ""); |     optionService.setOption(TOTP_OPTIONS.SALT, ""); | ||||||
|     optionService.setOption(TOTP_OPTIONS.ENCRYPTED_SECRET, ""); |     optionService.setOption(TOTP_OPTIONS.ENCRYPTED_SECRET, ""); | ||||||
|     optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, ""); |     optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, ""); | ||||||
|   | |||||||
| @@ -131,10 +131,10 @@ const defaultOptions: DefaultOption[] = [ | |||||||
|     { name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true }, |     { name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true }, | ||||||
|     { name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true }, |     { name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true }, | ||||||
|     { name: "editedNotesOpenInRibbon", 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: 'encryptedRecoveryCodes', value: 'false', isSynced: true }, | ||||||
|     { name: 'userSubjectIdentifierSaved', value: 'false', isSynced: true }, |     { name: 'userSubjectIdentifierSaved', value: 'false', isSynced: true }, | ||||||
|     { name: 'oAuthEnabled', value: 'false', isSynced: true }, |  | ||||||
|  |  | ||||||
|     // Appearance |     // Appearance | ||||||
|     { name: "splitEditorOrientation", value: "horizontal", isSynced: true }, |     { name: "splitEditorOrientation", value: "horizontal", isSynced: true }, | ||||||
|   | |||||||
| @@ -1,40 +1,64 @@ | |||||||
| import { Totp } from 'time2fa'; | import { Totp, generateSecret } from 'time2fa'; | ||||||
| import config from './config.js'; | import options from './options.js'; | ||||||
| import MFAError from '../errors/mfa_error.js'; | import totpEncryptionService from './encryption/totp_encryption.js'; | ||||||
|  |  | ||||||
|  | function isTotpEnabled(): boolean { | ||||||
|  |     return options.getOption('mfaEnabled') === "true" && options.getOption('mfaMethod') === "totp"; | ||||||
|  | } | ||||||
|  |  | ||||||
| function isTotpEnabled() { | function createSecret(): { success: boolean; message?: string } { | ||||||
|     if (config.MultiFactorAuthentication.totpEnabled && config.MultiFactorAuthentication.totpSecret === "") { |     try { | ||||||
|         throw new MFAError("TOTP secret is not set!"); |         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() { | function getTotpSecret(): string | null { | ||||||
|     return config.MultiFactorAuthentication.totpSecret; |     return totpEncryptionService.getTotpSecret(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function checkForTotSecret() { | function checkForTotpSecret(): boolean { | ||||||
|     return config.MultiFactorAuthentication.totpSecret === "" ? false : true; |     return totpEncryptionService.isTotpSecretSet(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function validateTOTP(submittedPasscode: string) { | function validateTOTP(submittedPasscode: string): boolean { | ||||||
|     if (config.MultiFactorAuthentication.totpSecret === "") return false; |     const secret = getTotpSecret(); | ||||||
|  |     if (!secret) return false; | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|         const valid = Totp.validate({ |         return Totp.validate({ | ||||||
|             passcode: submittedPasscode, |             passcode: submittedPasscode, | ||||||
|             secret: config.MultiFactorAuthentication.totpSecret.trim() |             secret: secret.trim() | ||||||
|         }); |         }); | ||||||
|         return valid; |  | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|  |         console.error('Failed to validate TOTP:', e); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function resetTotp(): void { | ||||||
|  |     totpEncryptionService.resetTotpSecret(); | ||||||
|  |     options.setOption('mfaEnabled', 'false'); | ||||||
|  |     options.setOption('mfaMethod', ''); | ||||||
|  | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     isTotpEnabled, |     isTotpEnabled, | ||||||
|  |     createSecret, | ||||||
|     getTotpSecret, |     getTotpSecret, | ||||||
|     checkForTotSecret, |     checkForTotpSecret, | ||||||
|     validateTOTP |     validateTOTP, | ||||||
| }; |     resetTotp | ||||||
|  | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user