chore(nx): move all monorepo-style in subfolder for processing

This commit is contained in:
Elian Doran
2025-04-22 10:06:06 +03:00
parent 2e200eab39
commit 62dbcc0a2e
1469 changed files with 16 additions and 16 deletions

View File

@@ -1,114 +0,0 @@
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
};

View File

@@ -1,64 +0,0 @@
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
};

View File

@@ -1,145 +0,0 @@
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,
};

View File

@@ -1,84 +0,0 @@
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
};

View File

@@ -1,40 +0,0 @@
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
};

View File

@@ -1,73 +0,0 @@
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
};

View File

@@ -1,83 +0,0 @@
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
};