mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-10 10:22:29 +08:00
feat: 🎸 add totp encryption
This commit is contained in:
parent
04cbe9d3d1
commit
18a417addd
86
src/services/encryption/totp_encryption.ts
Normal file
86
src/services/encryption/totp_encryption.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import optionService from "../options.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import { randomSecureToken, toBase64 } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
import type { OptionNames } from "../options_interface.js";
|
||||
|
||||
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));
|
||||
const dbSecretHash = optionService.getOptionOrNull(TOTP_OPTIONS.VERIFICATION_HASH);
|
||||
|
||||
if (!dbSecretHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return givenSecretHash === dbSecretHash;
|
||||
}
|
||||
|
||||
function setTotpSecret(secret: string): void {
|
||||
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
|
||||
);
|
||||
optionService.setOption(TOTP_OPTIONS.ENCRYPTED_SECRET, encryptedSecret);
|
||||
}
|
||||
|
||||
function getTotpSecret(): string | null {
|
||||
const encryptionSalt = optionService.getOptionOrNull(TOTP_OPTIONS.SALT);
|
||||
const encryptedSecret = optionService.getOptionOrNull(TOTP_OPTIONS.ENCRYPTED_SECRET);
|
||||
|
||||
if (!encryptionSalt || !encryptedSecret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const decryptedSecret = dataEncryptionService.decrypt(
|
||||
Buffer.from(encryptionSalt),
|
||||
encryptedSecret
|
||||
);
|
||||
|
||||
if (!decryptedSecret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return decryptedSecret.toString();
|
||||
} catch (e) {
|
||||
console.error("Failed to decrypt TOTP secret:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetTotpSecret(): void {
|
||||
optionService.setOption(TOTP_OPTIONS.SALT, "");
|
||||
optionService.setOption(TOTP_OPTIONS.ENCRYPTED_SECRET, "");
|
||||
optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, "");
|
||||
}
|
||||
|
||||
function isTotpSecretSet(): boolean {
|
||||
return !!optionService.getOptionOrNull(TOTP_OPTIONS.VERIFICATION_HASH);
|
||||
}
|
||||
|
||||
export default {
|
||||
verifyTotpSecret,
|
||||
setTotpSecret,
|
||||
getTotpSecret,
|
||||
resetTotpSecret,
|
||||
isTotpSecretSet
|
||||
};
|
@ -51,6 +51,9 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
||||
// Multi-Factor Authentication
|
||||
mfaEnabled: boolean;
|
||||
mfaMethod: string;
|
||||
totpEncryptionSalt: string;
|
||||
totpEncryptedSecret: string;
|
||||
totpVerificationHash: string;
|
||||
encryptedRecoveryCodes: boolean;
|
||||
userSubjectIdentifierSaved: boolean;
|
||||
recoveryCodeInitialVector: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user