feat: 🎸 move totp services to encryption logic

This commit is contained in:
Jin 2025-03-28 02:15:25 +01:00
parent 687d506ca5
commit 5742d3068e
5 changed files with 65 additions and 58 deletions

View File

@ -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));

View File

@ -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 {

View File

@ -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, "");

View File

@ -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 },

View File

@ -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
};
checkForTotpSecret,
validateTOTP,
resetTotp
};