mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
fix db anonymization
This commit is contained in:
parent
38723e0189
commit
91e5f24798
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.42.2",
|
"version": "0.42.5",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -3345,9 +3345,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "9.0.0",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.1.tgz",
|
||||||
"integrity": "sha512-JsaSQNPh+XDYkLj8APtVKTtvpb86KIG57W5OOss4TNrn8L3isC9LsCITwfnVmGIXHhvX6oY/weCtN5hAAytjVg==",
|
"integrity": "sha512-PZsQ0juL5YyDfOKES3HWz7zbWidcRcmtTzFCHSNzeVMjlkWB+hQToWVczFuGEWzwbAM1rCFs9MT0V/zpYT3pqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@electron/get": "^1.0.1",
|
"@electron/get": "^1.0.1",
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
"yazl": "^2.5.1"
|
"yazl": "^2.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "9.0.0",
|
"electron": "9.0.1",
|
||||||
"electron-builder": "22.6.0",
|
"electron-builder": "22.6.0",
|
||||||
"electron-packager": "14.2.1",
|
"electron-packager": "14.2.1",
|
||||||
"electron-rebuild": "1.10.1",
|
"electron-rebuild": "1.10.1",
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
const anonymizationService = require('./services/anonymization');
|
const backupService = require('./services/backup');
|
||||||
|
|
||||||
anonymizationService.anonymize().then(filePath => {
|
backupService.anonymize().then(resp => {
|
||||||
console.log("Anonymized file has been saved to:", filePath);
|
if (resp.success) {
|
||||||
|
console.log("Anonymization failed.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Anonymized file has been saved to: " + resp.anonymizedFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
@ -541,12 +541,13 @@ class Note extends Entity {
|
|||||||
/**
|
/**
|
||||||
* @return {Promise<Attribute>}
|
* @return {Promise<Attribute>}
|
||||||
*/
|
*/
|
||||||
async addAttribute(type, name, value = "") {
|
async addAttribute(type, name, value = "", isInheritable = false) {
|
||||||
const attr = new Attribute({
|
const attr = new Attribute({
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
type: type,
|
type: type,
|
||||||
name: name,
|
name: name,
|
||||||
value: value
|
value: value,
|
||||||
|
isInheritable: isInheritable
|
||||||
});
|
});
|
||||||
|
|
||||||
await attr.save();
|
await attr.save();
|
||||||
@ -556,12 +557,12 @@ class Note extends Entity {
|
|||||||
return attr;
|
return attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addLabel(name, value = "") {
|
async addLabel(name, value = "", isInheritable = false) {
|
||||||
return await this.addAttribute(LABEL, name, value);
|
return await this.addAttribute(LABEL, name, value, isInheritable);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addRelation(name, targetNoteId) {
|
async addRelation(name, targetNoteId, isInheritable = false) {
|
||||||
return await this.addAttribute(RELATION, name, targetNoteId);
|
return await this.addAttribute(RELATION, name, targetNoteId, isInheritable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,9 +61,14 @@ export default class AdvancedOptions {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$anonymizeButton.on('click', async () => {
|
this.$anonymizeButton.on('click', async () => {
|
||||||
await server.post('anonymization/anonymize');
|
const resp = await server.post('database/anonymize');
|
||||||
|
|
||||||
toastService.showMessage("Created anonymized database");
|
if (!resp.success) {
|
||||||
|
toastService.showError("Could not create anonymized database, check backend logs for details");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toastService.showMessage(`Created anonymized database in ${resp.anonymizedFilePath}`, 10000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$backupDatabaseButton.on('click', async () => {
|
this.$backupDatabaseButton.on('click', async () => {
|
||||||
|
@ -23,6 +23,7 @@ const mentionSetup = {
|
|||||||
row.text = row.name = row.noteTitle;
|
row.text = row.name = row.noteTitle;
|
||||||
row.id = '@' + row.text;
|
row.id = '@' + row.text;
|
||||||
row.link = '#' + row.path;
|
row.link = '#' + row.path;
|
||||||
|
row.notePath = row.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
res(rows);
|
res(rows);
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const anonymization = require('../../services/anonymization');
|
|
||||||
|
|
||||||
async function anonymize() {
|
|
||||||
await anonymization.anonymize();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
anonymize
|
|
||||||
};
|
|
@ -5,6 +5,10 @@ const log = require('../../services/log');
|
|||||||
const backupService = require('../../services/backup');
|
const backupService = require('../../services/backup');
|
||||||
const consistencyChecksService = require('../../services/consistency_checks');
|
const consistencyChecksService = require('../../services/consistency_checks');
|
||||||
|
|
||||||
|
async function anonymize() {
|
||||||
|
return await backupService.anonymize();
|
||||||
|
}
|
||||||
|
|
||||||
async function backupDatabase() {
|
async function backupDatabase() {
|
||||||
return {
|
return {
|
||||||
backupFile: await backupService.backupNow("now")
|
backupFile: await backupService.backupNow("now")
|
||||||
@ -24,5 +28,6 @@ async function findAndFixConsistencyIssues() {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
backupDatabase,
|
backupDatabase,
|
||||||
vacuumDatabase,
|
vacuumDatabase,
|
||||||
findAndFixConsistencyIssues
|
findAndFixConsistencyIssues,
|
||||||
|
anonymize
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,6 @@ const exportRoute = require('./api/export');
|
|||||||
const importRoute = require('./api/import');
|
const importRoute = require('./api/import');
|
||||||
const setupApiRoute = require('./api/setup');
|
const setupApiRoute = require('./api/setup');
|
||||||
const sqlRoute = require('./api/sql');
|
const sqlRoute = require('./api/sql');
|
||||||
const anonymizationRoute = require('./api/anonymization');
|
|
||||||
const databaseRoute = require('./api/database');
|
const databaseRoute = require('./api/database');
|
||||||
const imageRoute = require('./api/image');
|
const imageRoute = require('./api/image');
|
||||||
const attributesRoute = require('./api/attributes');
|
const attributesRoute = require('./api/attributes');
|
||||||
@ -220,7 +219,7 @@ function register(app) {
|
|||||||
|
|
||||||
apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema);
|
apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema);
|
||||||
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
||||||
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
route(POST, '/api/database/anonymize', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.anonymize, apiResultHandler, false);
|
||||||
|
|
||||||
// backup requires execution outside of transaction
|
// backup requires execution outside of transaction
|
||||||
route(POST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false);
|
route(POST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false);
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const dataDir = require('./data_dir');
|
|
||||||
const dateUtils = require('./date_utils');
|
|
||||||
const fs = require('fs-extra');
|
|
||||||
const sqlite = require('sqlite');
|
|
||||||
|
|
||||||
async function anonymize() {
|
|
||||||
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
|
|
||||||
fs.mkdirSync(dataDir.ANONYMIZED_DB_DIR, 0o700);
|
|
||||||
}
|
|
||||||
|
|
||||||
const anonymizedFile = dataDir.ANONYMIZED_DB_DIR + "/" + "anonymized-" + dateUtils.getDateTimeForFile() + ".db";
|
|
||||||
|
|
||||||
fs.copySync(dataDir.DOCUMENT_PATH, anonymizedFile);
|
|
||||||
|
|
||||||
const db = await sqlite.open(anonymizedFile, {Promise});
|
|
||||||
|
|
||||||
await db.run("UPDATE notes SET title = 'title'");
|
|
||||||
await db.run("UPDATE note_contents SET content = 'text'");
|
|
||||||
await db.run("UPDATE note_revisions SET title = 'title'");
|
|
||||||
await db.run("UPDATE note_revision_contents SET content = 'title'");
|
|
||||||
await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
|
|
||||||
await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation'");
|
|
||||||
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
|
||||||
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
|
||||||
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
|
||||||
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
|
||||||
await db.run("VACUUM");
|
|
||||||
|
|
||||||
await db.close();
|
|
||||||
|
|
||||||
return anonymizedFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
anonymize
|
|
||||||
};
|
|
@ -8,6 +8,8 @@ const log = require('./log');
|
|||||||
const sqlInit = require('./sql_init');
|
const sqlInit = require('./sql_init');
|
||||||
const syncMutexService = require('./sync_mutex');
|
const syncMutexService = require('./sync_mutex');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
|
const sqlite = require('sqlite');
|
||||||
|
const sqlite3 = require('sqlite3');
|
||||||
|
|
||||||
async function regularBackup() {
|
async function regularBackup() {
|
||||||
await periodBackup('lastDailyBackupDate', 'daily', 24 * 3600);
|
await periodBackup('lastDailyBackupDate', 'daily', 24 * 3600);
|
||||||
@ -28,36 +30,42 @@ async function periodBackup(optionName, fileName, periodInSeconds) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BACKUP_ATTEMPT_COUNT = 50;
|
const COPY_ATTEMPT_COUNT = 50;
|
||||||
|
|
||||||
async function backupNow(name) {
|
async function copyFile(backupFile) {
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
|
|
||||||
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
|
||||||
return await syncMutexService.doExclusively(async () => {
|
|
||||||
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(backupFile);
|
fs.unlinkSync(backupFile);
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {} // unlink throws exception if the file did not exist
|
} // unlink throws exception if the file did not exist
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
let attemptCount = 0
|
let attemptCount = 0
|
||||||
|
|
||||||
for (; attemptCount < BACKUP_ATTEMPT_COUNT && !success; attemptCount++) {
|
for (; attemptCount < COPY_ATTEMPT_COUNT && !success; attemptCount++) {
|
||||||
try {
|
try {
|
||||||
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
|
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
|
||||||
success++;
|
|
||||||
}
|
success = true;
|
||||||
catch (e) {
|
} catch (e) {
|
||||||
log.info(`Backup attempt ${attemptCount + 1} failed with "${e.message}", retrying...`);
|
log.info(`Copy DB attempt ${attemptCount + 1} failed with "${e.message}", retrying...`);
|
||||||
}
|
}
|
||||||
// we re-try since VACUUM is very picky and it can't run if there's any other query currently running
|
// we re-try since VACUUM is very picky and it can't run if there's any other query currently running
|
||||||
// which is difficult to guarantee so we just re-try
|
// which is difficult to guarantee so we just re-try
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attemptCount === BACKUP_ATTEMPT_COUNT) {
|
return attemptCount !== COPY_ATTEMPT_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function backupNow(name) {
|
||||||
|
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
||||||
|
return await syncMutexService.doExclusively(async () => {
|
||||||
|
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
||||||
|
|
||||||
|
const success = await copyFile(backupFile, sql);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
log.error(`Creating backup ${backupFile} failed`);
|
log.error(`Creating backup ${backupFile} failed`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -68,6 +76,44 @@ async function backupNow(name) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function anonymize() {
|
||||||
|
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
|
||||||
|
fs.mkdirSync(dataDir.ANONYMIZED_DB_DIR, 0o700);
|
||||||
|
}
|
||||||
|
|
||||||
|
const anonymizedFile = dataDir.ANONYMIZED_DB_DIR + "/" + "anonymized-" + dateUtils.getDateTimeForFile() + ".db";
|
||||||
|
|
||||||
|
const success = await copyFile(anonymizedFile);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await sqlite.open({
|
||||||
|
filename: anonymizedFile,
|
||||||
|
driver: sqlite3.Database
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.run("UPDATE notes SET title = 'title'");
|
||||||
|
await db.run("UPDATE note_contents SET content = 'text'");
|
||||||
|
await db.run("UPDATE note_revisions SET title = 'title'");
|
||||||
|
await db.run("UPDATE note_revision_contents SET content = 'title'");
|
||||||
|
await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
|
||||||
|
await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name != 'template'");
|
||||||
|
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
||||||
|
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
||||||
|
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
||||||
|
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
||||||
|
await db.run("VACUUM");
|
||||||
|
|
||||||
|
await db.close();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
anonymizedFilePath: anonymizedFile
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(dataDir.BACKUP_DIR)) {
|
if (!fs.existsSync(dataDir.BACKUP_DIR)) {
|
||||||
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
|
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
|
||||||
}
|
}
|
||||||
@ -80,5 +126,6 @@ sqlInit.dbReady.then(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
backupNow
|
backupNow,
|
||||||
|
anonymize
|
||||||
};
|
};
|
||||||
|
@ -98,7 +98,7 @@ async function createNewNote(params) {
|
|||||||
const parentNote = await repository.getNote(params.parentNoteId);
|
const parentNote = await repository.getNote(params.parentNoteId);
|
||||||
|
|
||||||
if (!parentNote) {
|
if (!parentNote) {
|
||||||
throw new Error(`Parent note ${params.parentNoteId} not found.`);
|
throw new Error(`Parent note "${params.parentNoteId}" not found.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.title || params.title.trim().length === 0) {
|
if (!params.title || params.title.trim().length === 0) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user