mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 19:49:01 +01: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", | ||||
|   "version": "0.42.2", | ||||
|   "version": "0.42.5", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @ -3345,9 +3345,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "electron": { | ||||
|       "version": "9.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0.tgz", | ||||
|       "integrity": "sha512-JsaSQNPh+XDYkLj8APtVKTtvpb86KIG57W5OOss4TNrn8L3isC9LsCITwfnVmGIXHhvX6oY/weCtN5hAAytjVg==", | ||||
|       "version": "9.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.1.tgz", | ||||
|       "integrity": "sha512-PZsQ0juL5YyDfOKES3HWz7zbWidcRcmtTzFCHSNzeVMjlkWB+hQToWVczFuGEWzwbAM1rCFs9MT0V/zpYT3pqQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@electron/get": "^1.0.1", | ||||
|  | ||||
| @ -78,7 +78,7 @@ | ||||
|     "yazl": "^2.5.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "electron": "9.0.0", | ||||
|     "electron": "9.0.1", | ||||
|     "electron-builder": "22.6.0", | ||||
|     "electron-packager": "14.2.1", | ||||
|     "electron-rebuild": "1.10.1", | ||||
|  | ||||
| @ -1,7 +1,12 @@ | ||||
| const anonymizationService = require('./services/anonymization'); | ||||
| const backupService = require('./services/backup'); | ||||
| 
 | ||||
| anonymizationService.anonymize().then(filePath => { | ||||
|     console.log("Anonymized file has been saved to:", filePath); | ||||
| backupService.anonymize().then(resp => { | ||||
|     if (resp.success) { | ||||
|         console.log("Anonymization failed."); | ||||
|     } | ||||
|     else { | ||||
|         console.log("Anonymized file has been saved to: " + resp.anonymizedFilePath); | ||||
|     } | ||||
| 
 | ||||
|     process.exit(0); | ||||
| }); | ||||
| }); | ||||
|  | ||||
| @ -541,12 +541,13 @@ class Note extends Entity { | ||||
|     /** | ||||
|      * @return {Promise<Attribute>} | ||||
|      */ | ||||
|     async addAttribute(type, name, value = "") { | ||||
|     async addAttribute(type, name, value = "", isInheritable = false) { | ||||
|         const attr = new Attribute({ | ||||
|             noteId: this.noteId, | ||||
|             type: type, | ||||
|             name: name, | ||||
|             value: value | ||||
|             value: value, | ||||
|             isInheritable: isInheritable | ||||
|         }); | ||||
| 
 | ||||
|         await attr.save(); | ||||
| @ -556,12 +557,12 @@ class Note extends Entity { | ||||
|         return attr; | ||||
|     } | ||||
| 
 | ||||
|     async addLabel(name, value = "") { | ||||
|         return await this.addAttribute(LABEL, name, value); | ||||
|     async addLabel(name, value = "", isInheritable = false) { | ||||
|         return await this.addAttribute(LABEL, name, value, isInheritable); | ||||
|     } | ||||
| 
 | ||||
|     async addRelation(name, targetNoteId) { | ||||
|         return await this.addAttribute(RELATION, name, targetNoteId); | ||||
|     async addRelation(name, targetNoteId, isInheritable = false) { | ||||
|         return await this.addAttribute(RELATION, name, targetNoteId, isInheritable); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -61,9 +61,14 @@ export default class AdvancedOptions { | ||||
|         }); | ||||
| 
 | ||||
|         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 () => { | ||||
|  | ||||
| @ -23,6 +23,7 @@ const mentionSetup = { | ||||
|                             row.text = row.name = row.noteTitle; | ||||
|                             row.id = '@' + row.text; | ||||
|                             row.link = '#' + row.path; | ||||
|                             row.notePath = row.path; | ||||
|                         } | ||||
| 
 | ||||
|                         res(rows); | ||||
| @ -256,4 +257,4 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | ||||
|             this.textEditor.model.insertContent(imageElement, this.textEditor.model.document.selection); | ||||
|         } ); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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 consistencyChecksService = require('../../services/consistency_checks'); | ||||
| 
 | ||||
| async function anonymize() { | ||||
|     return await backupService.anonymize(); | ||||
| } | ||||
| 
 | ||||
| async function backupDatabase() { | ||||
|     return { | ||||
|         backupFile: await backupService.backupNow("now") | ||||
| @ -24,5 +28,6 @@ async function findAndFixConsistencyIssues() { | ||||
| module.exports = { | ||||
|     backupDatabase, | ||||
|     vacuumDatabase, | ||||
|     findAndFixConsistencyIssues | ||||
|     findAndFixConsistencyIssues, | ||||
|     anonymize | ||||
| }; | ||||
|  | ||||
| @ -24,7 +24,6 @@ const exportRoute = require('./api/export'); | ||||
| const importRoute = require('./api/import'); | ||||
| const setupApiRoute = require('./api/setup'); | ||||
| const sqlRoute = require('./api/sql'); | ||||
| const anonymizationRoute = require('./api/anonymization'); | ||||
| const databaseRoute = require('./api/database'); | ||||
| const imageRoute = require('./api/image'); | ||||
| const attributesRoute = require('./api/attributes'); | ||||
| @ -220,7 +219,7 @@ function register(app) { | ||||
| 
 | ||||
|     apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema); | ||||
|     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
 | ||||
|     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 syncMutexService = require('./sync_mutex'); | ||||
| const cls = require('./cls'); | ||||
| const sqlite = require('sqlite'); | ||||
| const sqlite3 = require('sqlite3'); | ||||
| 
 | ||||
| async function regularBackup() { | ||||
|     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'); | ||||
| 
 | ||||
|     try { | ||||
|         fs.unlinkSync(backupFile); | ||||
|     } catch (e) { | ||||
|     } // unlink throws exception if the file did not exist
 | ||||
| 
 | ||||
|     let success = false; | ||||
|     let attemptCount = 0 | ||||
| 
 | ||||
|     for (; attemptCount < COPY_ATTEMPT_COUNT && !success; attemptCount++) { | ||||
|         try { | ||||
|             await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`); | ||||
| 
 | ||||
|             success = true; | ||||
|         } catch (e) { | ||||
|             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
 | ||||
|         // which is difficult to guarantee so we just re-try
 | ||||
|     } | ||||
| 
 | ||||
|     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`; | ||||
| 
 | ||||
|         try { | ||||
|             fs.unlinkSync(backupFile); | ||||
|         } | ||||
|         catch (e) {} // unlink throws exception if the file did not exist
 | ||||
|         const success = await copyFile(backupFile, sql); | ||||
| 
 | ||||
|         let success = false; | ||||
|         let attemptCount = 0 | ||||
| 
 | ||||
|         for (; attemptCount < BACKUP_ATTEMPT_COUNT && !success; attemptCount++) { | ||||
|             try { | ||||
|                 await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`); | ||||
|                 success++; | ||||
|             } | ||||
|             catch (e) { | ||||
|                 log.info(`Backup 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
 | ||||
|             // which is difficult to guarantee so we just re-try
 | ||||
|         } | ||||
| 
 | ||||
|         if (attemptCount === BACKUP_ATTEMPT_COUNT) { | ||||
|         if (success) { | ||||
|             log.error(`Creating backup ${backupFile} failed`); | ||||
|         } | ||||
|         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)) { | ||||
|     fs.mkdirSync(dataDir.BACKUP_DIR, 0o700); | ||||
| } | ||||
| @ -80,5 +126,6 @@ sqlInit.dbReady.then(() => { | ||||
| }); | ||||
| 
 | ||||
| module.exports = { | ||||
|     backupNow | ||||
|     backupNow, | ||||
|     anonymize | ||||
| }; | ||||
|  | ||||
| @ -98,7 +98,7 @@ async function createNewNote(params) { | ||||
|     const parentNote = await repository.getNote(params.parentNoteId); | ||||
| 
 | ||||
|     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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam