Notes/src/services/anonymization.js

98 lines
3.6 KiB
JavaScript
Raw Normal View History

const BUILTIN_ATTRIBUTES = require('./builtin_attributes.js');
const fs = require("fs-extra");
const dataDir = require('./data_dir');
2024-02-16 21:38:09 +02:00
const dateUtils = require('./date_utils');
const Database = require("better-sqlite3");
2024-02-16 22:44:12 +02:00
const sql = require('./sql');
const path = require("path");
function getFullAnonymizationScript() {
// we want to delete all non-builtin attributes because they can contain sensitive names and values
2023-10-03 23:14:02 +02:00
// on the other hand builtin/system attrs should not contain any sensitive info
const builtinAttrNames = BUILTIN_ATTRIBUTES
2023-10-03 23:17:15 +02:00
.filter(attr => !["shareCredentials", "shareAlias"].includes(attr.name))
.map(attr => `'${attr.name}'`).join(', ');
const anonymizeScript = `
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
2022-12-21 16:11:00 +01:00
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
2023-03-16 11:02:07 +01:00
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE revisions SET title = 'title';
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
2022-01-31 21:25:18 +01:00
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != '';
VACUUM;
`;
return anonymizeScript;
}
function getLightAnonymizationScript() {
2023-03-16 11:02:07 +01:00
return `UPDATE blobs SET content = 'text' WHERE content IS NOT NULL AND blobId NOT IN (
SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
UNION ALL
SELECT blobId FROM revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
2024-01-03 22:10:29 +01:00
);
UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != '';`;
}
async function createAnonymizedCopy(type) {
if (!['full', 'light'].includes(type)) {
throw new Error(`Unrecognized anonymization type '${type}'`);
}
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
fs.mkdirSync(dataDir.ANONYMIZED_DB_DIR, 0o700);
}
const anonymizedFile = `${dataDir.ANONYMIZED_DB_DIR}/anonymized-${type}-${dateUtils.getDateTimeForFile()}.db`;
await sql.copyDatabase(anonymizedFile);
const db = new Database(anonymizedFile);
const anonymizationScript = type === 'light'
? getLightAnonymizationScript()
: getFullAnonymizationScript();
db.exec(anonymizationScript);
db.close();
return {
success: true,
anonymizedFilePath: anonymizedFile
};
}
function getExistingAnonymizedDatabases() {
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
return [];
}
return fs.readdirSync(dataDir.ANONYMIZED_DB_DIR)
.filter(fileName => fileName.includes("anonymized"))
.map(fileName => ({
fileName: fileName,
filePath: path.resolve(dataDir.ANONYMIZED_DB_DIR, fileName)
}));
}
module.exports = {
getFullAnonymizationScript,
createAnonymizedCopy,
getExistingAnonymizedDatabases
}