mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-30 03:32:26 +08:00
feat: 🎸 move totp services to encryption logic
This commit is contained in:
parent
687d506ca5
commit
5742d3068e
@ -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
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user