mirror of
https://github.com/zadam/trilium.git
synced 2025-11-03 11:56:01 +01:00
chore(nx/server): move source code
This commit is contained in:
114
apps/server/src/services/encryption/data_encryption.ts
Normal file
114
apps/server/src/services/encryption/data_encryption.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import crypto from "crypto";
|
||||
import log from "../log.js";
|
||||
|
||||
function arraysIdentical(a: any[] | Buffer, b: any[] | Buffer) {
|
||||
let i = a.length;
|
||||
if (i !== b.length) return false;
|
||||
while (i--) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function shaArray(content: crypto.BinaryLike) {
|
||||
// we use this as a simple checksum and don't rely on its security, so SHA-1 is good enough
|
||||
return crypto.createHash("sha1").update(content).digest();
|
||||
}
|
||||
|
||||
function pad(data: Buffer): Buffer {
|
||||
if (data.length > 16) {
|
||||
data = data.slice(0, 16);
|
||||
} else if (data.length < 16) {
|
||||
const zeros = Array(16 - data.length).fill(0);
|
||||
|
||||
data = Buffer.concat([data, Buffer.from(zeros)]);
|
||||
}
|
||||
|
||||
return Buffer.from(data);
|
||||
}
|
||||
|
||||
function encrypt(key: Buffer, plainText: Buffer | string) {
|
||||
if (!key) {
|
||||
throw new Error("No data key!");
|
||||
}
|
||||
|
||||
const plainTextBuffer = Buffer.isBuffer(plainText) ? plainText : Buffer.from(plainText);
|
||||
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv("aes-128-cbc", pad(key), pad(iv));
|
||||
|
||||
const digest = shaArray(plainTextBuffer).slice(0, 4);
|
||||
|
||||
const digestWithPayload = Buffer.concat([digest, plainTextBuffer]);
|
||||
|
||||
const encryptedData = Buffer.concat([cipher.update(digestWithPayload), cipher.final()]);
|
||||
|
||||
const encryptedDataWithIv = Buffer.concat([iv, encryptedData]);
|
||||
|
||||
return encryptedDataWithIv.toString("base64");
|
||||
}
|
||||
|
||||
function decrypt(key: Buffer, cipherText: string | Buffer): Buffer | false | null {
|
||||
if (cipherText === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
return Buffer.from("[protected]");
|
||||
}
|
||||
|
||||
try {
|
||||
const cipherTextBufferWithIv = Buffer.from(cipherText.toString(), "base64");
|
||||
|
||||
// old encrypted data can have IV of length 13, see some details here: https://github.com/zadam/trilium/issues/3017
|
||||
const ivLength = cipherTextBufferWithIv.length % 16 === 0 ? 16 : 13;
|
||||
|
||||
const iv = cipherTextBufferWithIv.slice(0, ivLength);
|
||||
|
||||
const cipherTextBuffer = cipherTextBufferWithIv.slice(ivLength);
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes-128-cbc", pad(key), pad(iv));
|
||||
|
||||
const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]);
|
||||
|
||||
const digest = decryptedBytes.slice(0, 4);
|
||||
const payload = decryptedBytes.slice(4);
|
||||
|
||||
const computedDigest = shaArray(payload).slice(0, 4);
|
||||
|
||||
if (!arraysIdentical(digest, computedDigest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
} catch (e: any) {
|
||||
// recovery from https://github.com/zadam/trilium/issues/510
|
||||
if (e.message?.includes("WRONG_FINAL_BLOCK_LENGTH") || e.message?.includes("wrong final block length")) {
|
||||
log.info("Caught WRONG_FINAL_BLOCK_LENGTH, returning cipherText instead");
|
||||
|
||||
return (Buffer.isBuffer(cipherText) ? cipherText : Buffer.from(cipherText));
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decryptString(dataKey: Buffer, cipherText: string) {
|
||||
const buffer = decrypt(dataKey, cipherText);
|
||||
|
||||
if (buffer === null) {
|
||||
return null;
|
||||
} else if (buffer === false) {
|
||||
log.error(`Could not decrypt string. Buffer: ${buffer}`);
|
||||
|
||||
throw new Error("Could not decrypt string.");
|
||||
}
|
||||
|
||||
return buffer.toString("utf-8");
|
||||
}
|
||||
|
||||
export default {
|
||||
encrypt,
|
||||
decrypt,
|
||||
decryptString
|
||||
};
|
||||
64
apps/server/src/services/encryption/my_scrypt.ts
Normal file
64
apps/server/src/services/encryption/my_scrypt.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import optionService from "../options.js";
|
||||
import crypto from "crypto";
|
||||
import sql from "../sql.js";
|
||||
|
||||
function getVerificationHash(password: crypto.BinaryLike) {
|
||||
const salt = optionService.getOption("passwordVerificationSalt");
|
||||
|
||||
return getScryptHash(password, salt);
|
||||
}
|
||||
|
||||
function getPasswordDerivedKey(password: crypto.BinaryLike) {
|
||||
const salt = optionService.getOption("passwordDerivedKeySalt");
|
||||
|
||||
return getScryptHash(password, salt);
|
||||
}
|
||||
|
||||
function getScryptHash(password: crypto.BinaryLike, salt: crypto.BinaryLike) {
|
||||
const hashed = crypto.scryptSync(password, salt, 32, { N: 16384, r: 8, p: 1 });
|
||||
|
||||
return hashed;
|
||||
}
|
||||
|
||||
function getSubjectIdentifierVerificationHash(
|
||||
guessedUserId: string | crypto.BinaryLike,
|
||||
salt?: string
|
||||
) {
|
||||
if (salt != null) return getScryptHash(guessedUserId, salt);
|
||||
|
||||
const savedSalt = sql.getValue("SELECT salt FROM user_data;");
|
||||
if (!savedSalt) {
|
||||
console.error("User salt undefined!");
|
||||
return undefined;
|
||||
}
|
||||
return getScryptHash(guessedUserId, savedSalt.toString());
|
||||
}
|
||||
|
||||
function getSubjectIdentifierDerivedKey(
|
||||
subjectIdentifer: crypto.BinaryLike,
|
||||
givenSalt?: string
|
||||
) {
|
||||
if (givenSalt !== undefined) {
|
||||
return getScryptHash(subjectIdentifer, givenSalt.toString());
|
||||
}
|
||||
|
||||
const salt = sql.getValue("SELECT salt FROM user_data;");
|
||||
if (!salt) return undefined;
|
||||
|
||||
return getScryptHash(subjectIdentifer, salt.toString());
|
||||
}
|
||||
|
||||
function createSubjectIdentifierDerivedKey(
|
||||
subjectIdentifer: string | crypto.BinaryLike,
|
||||
salt: string | crypto.BinaryLike
|
||||
) {
|
||||
return getScryptHash(subjectIdentifer, salt);
|
||||
}
|
||||
|
||||
export default {
|
||||
getVerificationHash,
|
||||
getPasswordDerivedKey,
|
||||
getSubjectIdentifierVerificationHash,
|
||||
getSubjectIdentifierDerivedKey,
|
||||
createSubjectIdentifierDerivedKey
|
||||
};
|
||||
145
apps/server/src/services/encryption/open_id_encryption.ts
Normal file
145
apps/server/src/services/encryption/open_id_encryption.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import utils from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
import sql from "../sql.js";
|
||||
import sqlInit from "../sql_init.js";
|
||||
import OpenIdError from "../../errors/open_id_error.js";
|
||||
|
||||
function saveUser(subjectIdentifier: string, name: string, email: string) {
|
||||
if (isUserSaved()) return false;
|
||||
|
||||
const verificationSalt = utils.randomSecureToken(32);
|
||||
const derivedKeySalt = utils.randomSecureToken(32);
|
||||
|
||||
const verificationHash = myScryptService.getSubjectIdentifierVerificationHash(
|
||||
subjectIdentifier,
|
||||
verificationSalt
|
||||
);
|
||||
if (!verificationHash) {
|
||||
throw new OpenIdError("Verification hash undefined!")
|
||||
}
|
||||
|
||||
const userIDEncryptedDataKey = setDataKey(
|
||||
subjectIdentifier,
|
||||
utils.randomSecureToken(16),
|
||||
verificationSalt
|
||||
);
|
||||
|
||||
if (!userIDEncryptedDataKey) {
|
||||
console.error("UserID encrypted data key null");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data = {
|
||||
tmpID: 0,
|
||||
userIDVerificationHash: utils.toBase64(verificationHash),
|
||||
salt: verificationSalt,
|
||||
derivedKey: derivedKeySalt,
|
||||
userIDEncryptedDataKey: userIDEncryptedDataKey,
|
||||
isSetup: "true",
|
||||
username: name,
|
||||
email: email
|
||||
};
|
||||
|
||||
sql.upsert("user_data", "tmpID", data);
|
||||
return true;
|
||||
}
|
||||
|
||||
function isSubjectIdentifierSaved() {
|
||||
const value = sql.getValue("SELECT userIDEncryptedDataKey FROM user_data;");
|
||||
if (value === undefined || value === null || value === "") return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function isUserSaved() {
|
||||
const isSaved = sql.getValue<string>("SELECT isSetup FROM user_data;");
|
||||
return isSaved === "true" ? true : false;
|
||||
}
|
||||
|
||||
function verifyOpenIDSubjectIdentifier(subjectIdentifier: string) {
|
||||
if (!sqlInit.isDbInitialized()) {
|
||||
throw new OpenIdError("Database not initialized!");
|
||||
}
|
||||
|
||||
if (isUserSaved()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const salt = sql.getValue("SELECT salt FROM user_data;");
|
||||
if (salt == undefined) {
|
||||
console.log("Salt undefined");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const givenHash = myScryptService
|
||||
.getSubjectIdentifierVerificationHash(subjectIdentifier)
|
||||
?.toString("base64");
|
||||
if (givenHash === undefined) {
|
||||
console.log("Sub id hash undefined!");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const savedHash = sql.getValue(
|
||||
"SELECT userIDVerificationHash FROM user_data"
|
||||
);
|
||||
if (savedHash === undefined) {
|
||||
console.log("verification hash undefined");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
console.log("Matches: " + givenHash === savedHash);
|
||||
return givenHash === savedHash;
|
||||
}
|
||||
|
||||
function setDataKey(
|
||||
subjectIdentifier: string,
|
||||
plainTextDataKey: string | Buffer,
|
||||
salt: string
|
||||
) {
|
||||
const subjectIdentifierDerivedKey =
|
||||
myScryptService.getSubjectIdentifierDerivedKey(subjectIdentifier, salt);
|
||||
|
||||
if (subjectIdentifierDerivedKey === undefined) {
|
||||
console.error("SOMETHING WENT WRONG SAVING USER ID DERIVED KEY");
|
||||
return undefined;
|
||||
}
|
||||
const newEncryptedDataKey = dataEncryptionService.encrypt(
|
||||
subjectIdentifierDerivedKey,
|
||||
plainTextDataKey
|
||||
);
|
||||
|
||||
return newEncryptedDataKey;
|
||||
}
|
||||
|
||||
function getDataKey(subjectIdentifier: string) {
|
||||
const subjectIdentifierDerivedKey =
|
||||
myScryptService.getSubjectIdentifierDerivedKey(subjectIdentifier);
|
||||
|
||||
const encryptedDataKey = sql.getValue(
|
||||
"SELECT userIDEncryptedDataKey FROM user_data"
|
||||
);
|
||||
|
||||
if (!encryptedDataKey) {
|
||||
console.error("Encrypted data key empty!");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!subjectIdentifierDerivedKey) {
|
||||
console.error("SOMETHING WENT WRONG SAVING USER ID DERIVED KEY");
|
||||
return undefined;
|
||||
}
|
||||
const decryptedDataKey = dataEncryptionService.decrypt(
|
||||
subjectIdentifierDerivedKey,
|
||||
encryptedDataKey.toString()
|
||||
);
|
||||
|
||||
return decryptedDataKey;
|
||||
}
|
||||
|
||||
export default {
|
||||
verifyOpenIDSubjectIdentifier,
|
||||
getDataKey,
|
||||
setDataKey,
|
||||
saveUser,
|
||||
isSubjectIdentifierSaved,
|
||||
};
|
||||
84
apps/server/src/services/encryption/password.ts
Normal file
84
apps/server/src/services/encryption/password.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import sql from "../sql.js";
|
||||
import optionService from "../options.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import { randomSecureToken, toBase64 } from "../utils.js";
|
||||
import passwordEncryptionService from "./password_encryption.js";
|
||||
|
||||
function isPasswordSet() {
|
||||
return !!sql.getValue("SELECT value FROM options WHERE name = 'passwordVerificationHash'");
|
||||
}
|
||||
|
||||
function changePassword(currentPassword: string, newPassword: string) {
|
||||
if (!isPasswordSet()) {
|
||||
throw new Error("Password has not been set yet, so it cannot be changed. Use 'setPassword' instead.");
|
||||
}
|
||||
|
||||
if (!passwordEncryptionService.verifyPassword(currentPassword)) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Given current password doesn't match hash"
|
||||
};
|
||||
}
|
||||
|
||||
sql.transactional(() => {
|
||||
const decryptedDataKey = passwordEncryptionService.getDataKey(currentPassword);
|
||||
|
||||
optionService.setOption("passwordVerificationSalt", randomSecureToken(32));
|
||||
optionService.setOption("passwordDerivedKeySalt", randomSecureToken(32));
|
||||
|
||||
const newPasswordVerificationKey = toBase64(myScryptService.getVerificationHash(newPassword));
|
||||
|
||||
if (decryptedDataKey) {
|
||||
// TODO: what should happen if the decrypted data key is null?
|
||||
passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
|
||||
}
|
||||
|
||||
optionService.setOption("passwordVerificationHash", newPasswordVerificationKey);
|
||||
});
|
||||
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
}
|
||||
|
||||
function setPassword(password: string) {
|
||||
if (isPasswordSet()) {
|
||||
throw new Error("Password is set already. Either change it or perform 'reset password' first.");
|
||||
}
|
||||
|
||||
optionService.createOption("passwordVerificationSalt", randomSecureToken(32), true);
|
||||
optionService.createOption("passwordDerivedKeySalt", randomSecureToken(32), true);
|
||||
|
||||
const passwordVerificationKey = toBase64(myScryptService.getVerificationHash(password));
|
||||
optionService.createOption("passwordVerificationHash", passwordVerificationKey, true);
|
||||
|
||||
// passwordEncryptionService expects these options to already exist
|
||||
optionService.createOption("encryptedDataKey", "", true);
|
||||
|
||||
passwordEncryptionService.setDataKey(password, randomSecureToken(16));
|
||||
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
}
|
||||
|
||||
function resetPassword() {
|
||||
// user forgot the password,
|
||||
sql.transactional(() => {
|
||||
optionService.setOption("passwordVerificationSalt", "");
|
||||
optionService.setOption("passwordDerivedKeySalt", "");
|
||||
optionService.setOption("encryptedDataKey", "");
|
||||
optionService.setOption("passwordVerificationHash", "");
|
||||
});
|
||||
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
isPasswordSet,
|
||||
changePassword,
|
||||
setPassword,
|
||||
resetPassword
|
||||
};
|
||||
40
apps/server/src/services/encryption/password_encryption.ts
Normal file
40
apps/server/src/services/encryption/password_encryption.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import optionService from "../options.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import { toBase64 } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
|
||||
function verifyPassword(password: string) {
|
||||
const givenPasswordHash = toBase64(myScryptService.getVerificationHash(password));
|
||||
|
||||
const dbPasswordHash = optionService.getOptionOrNull("passwordVerificationHash");
|
||||
|
||||
if (!dbPasswordHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return givenPasswordHash === dbPasswordHash;
|
||||
}
|
||||
|
||||
function setDataKey(password: string, plainTextDataKey: string | Buffer) {
|
||||
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
|
||||
|
||||
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, plainTextDataKey);
|
||||
|
||||
optionService.setOption("encryptedDataKey", newEncryptedDataKey);
|
||||
}
|
||||
|
||||
function getDataKey(password: string) {
|
||||
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
|
||||
|
||||
const encryptedDataKey = optionService.getOption("encryptedDataKey");
|
||||
|
||||
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKey);
|
||||
|
||||
return decryptedDataKey;
|
||||
}
|
||||
|
||||
export default {
|
||||
verifyPassword,
|
||||
getDataKey,
|
||||
setDataKey
|
||||
};
|
||||
73
apps/server/src/services/encryption/recovery_codes.ts
Normal file
73
apps/server/src/services/encryption/recovery_codes.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import crypto from 'crypto';
|
||||
import optionService from '../options.js';
|
||||
import sql from '../sql.js';
|
||||
|
||||
function isRecoveryCodeSet() {
|
||||
return optionService.getOptionBool('encryptedRecoveryCodes');
|
||||
}
|
||||
|
||||
function setRecoveryCodes(recoveryCodes: string) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const securityKey = crypto.randomBytes(32);
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', securityKey, iv);
|
||||
const encryptedRecoveryCodes = cipher.update(recoveryCodes, 'utf-8', 'hex');
|
||||
|
||||
sql.transactional(() => {
|
||||
optionService.setOption('recoveryCodeInitialVector', iv.toString('hex'));
|
||||
optionService.setOption('recoveryCodeSecurityKey', securityKey.toString('hex'));
|
||||
optionService.setOption('recoveryCodesEncrypted', encryptedRecoveryCodes + cipher.final('hex'));
|
||||
optionService.setOption('encryptedRecoveryCodes', 'true');
|
||||
return true;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function getRecoveryCodes() {
|
||||
if (!isRecoveryCodeSet()) {
|
||||
return []
|
||||
}
|
||||
|
||||
return sql.transactional<string[]>(() => {
|
||||
const iv = Buffer.from(optionService.getOption('recoveryCodeInitialVector'), 'hex');
|
||||
const securityKey = Buffer.from(optionService.getOption('recoveryCodeSecurityKey'), 'hex');
|
||||
const encryptedRecoveryCodes = optionService.getOption('recoveryCodesEncrypted');
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', securityKey, iv);
|
||||
const decryptedData = decipher.update(encryptedRecoveryCodes, 'hex', 'utf-8');
|
||||
|
||||
const decryptedString = decryptedData + decipher.final('utf-8');
|
||||
return decryptedString.split(',');
|
||||
});
|
||||
}
|
||||
|
||||
function removeRecoveryCode(usedCode: string) {
|
||||
const oldCodes = getRecoveryCodes();
|
||||
const today = new Date();
|
||||
oldCodes[oldCodes.indexOf(usedCode)] = today.toJSON().replace(/-/g, '/');
|
||||
setRecoveryCodes(oldCodes.toString());
|
||||
}
|
||||
|
||||
function verifyRecoveryCode(recoveryCodeGuess: string) {
|
||||
const recoveryCodeRegex = RegExp(/^.{22}==$/gm);
|
||||
if (!recoveryCodeRegex.test(recoveryCodeGuess)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const recoveryCodes = getRecoveryCodes();
|
||||
let loginSuccess = false;
|
||||
recoveryCodes.forEach((recoveryCode) => {
|
||||
if (recoveryCodeGuess === recoveryCode) {
|
||||
removeRecoveryCode(recoveryCode);
|
||||
loginSuccess = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return loginSuccess;
|
||||
}
|
||||
|
||||
export default {
|
||||
setRecoveryCodes,
|
||||
getRecoveryCodes,
|
||||
verifyRecoveryCode,
|
||||
isRecoveryCodeSet
|
||||
};
|
||||
83
apps/server/src/services/encryption/totp_encryption.ts
Normal file
83
apps/server/src/services/encryption/totp_encryption.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
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 "@triliumnext/commons";
|
||||
|
||||
const TOTP_OPTIONS: Record<string, OptionNames> = {
|
||||
SALT: "totpEncryptionSalt",
|
||||
ENCRYPTED_SECRET: "totpEncryptedSecret",
|
||||
VERIFICATION_HASH: "totpVerificationHash"
|
||||
};
|
||||
|
||||
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) {
|
||||
if (!secret) {
|
||||
throw new Error("TOTP secret cannot be empty");
|
||||
}
|
||||
|
||||
const encryptionSalt = randomSecureToken(32);
|
||||
optionService.setOption(TOTP_OPTIONS.SALT, encryptionSalt);
|
||||
|
||||
const verificationHash = toBase64(myScryptService.getVerificationHash(secret));
|
||||
optionService.setOption(TOTP_OPTIONS.VERIFICATION_HASH, verificationHash);
|
||||
|
||||
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() {
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user