mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	autocomplete supports encrypted notes now as well
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Entity = require('./entity');
 | 
					const Entity = require('./entity');
 | 
				
			||||||
const protected_session = require('../services/protected_session');
 | 
					const protectedSessionService = require('../services/protected_session');
 | 
				
			||||||
const repository = require('../services/repository');
 | 
					const repository = require('../services/repository');
 | 
				
			||||||
const dateUtils = require('../services/date_utils');
 | 
					const dateUtils = require('../services/date_utils');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,7 +14,7 @@ class Note extends Entity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
 | 
					        // check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
 | 
				
			||||||
        if (this.isProtected && this.noteId) {
 | 
					        if (this.isProtected && this.noteId) {
 | 
				
			||||||
            protected_session.decryptNote(this);
 | 
					            protectedSessionService.decryptNote(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setContent(this.content);
 | 
					        this.setContent(this.content);
 | 
				
			||||||
@@ -146,7 +146,7 @@ class Note extends Entity {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.isProtected) {
 | 
					        if (this.isProtected) {
 | 
				
			||||||
            protected_session.encryptNote(this);
 | 
					            protectedSessionService.encryptNote(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!this.isDeleted) {
 | 
					        if (!this.isDeleted) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Entity = require('./entity');
 | 
					const Entity = require('./entity');
 | 
				
			||||||
const protected_session = require('../services/protected_session');
 | 
					const protectedSessionService = require('../services/protected_session');
 | 
				
			||||||
const utils = require('../services/utils');
 | 
					 | 
				
			||||||
const repository = require('../services/repository');
 | 
					const repository = require('../services/repository');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NoteRevision extends Entity {
 | 
					class NoteRevision extends Entity {
 | 
				
			||||||
@@ -13,7 +12,7 @@ class NoteRevision extends Entity {
 | 
				
			|||||||
        super(row);
 | 
					        super(row);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.isProtected) {
 | 
					        if (this.isProtected) {
 | 
				
			||||||
            protected_session.decryptNoteRevision(this);
 | 
					            protectedSessionService.decryptNoteRevision(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,7 +24,7 @@ class NoteRevision extends Entity {
 | 
				
			|||||||
        super.beforeSaving();
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.isProtected) {
 | 
					        if (this.isProtected) {
 | 
				
			||||||
            protected_session.encryptNoteRevision(this);
 | 
					            protectedSessionService.encryptNoteRevision(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,8 @@ const sourceIdService = require('../../services/source_id');
 | 
				
			|||||||
const passwordEncryptionService = require('../../services/password_encryption');
 | 
					const passwordEncryptionService = require('../../services/password_encryption');
 | 
				
			||||||
const protectedSessionService = require('../../services/protected_session');
 | 
					const protectedSessionService = require('../../services/protected_session');
 | 
				
			||||||
const appInfo = require('../../services/app_info');
 | 
					const appInfo = require('../../services/app_info');
 | 
				
			||||||
 | 
					const eventService = require('../../services/events');
 | 
				
			||||||
 | 
					const cls = require('../../services/cls');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function loginSync(req) {
 | 
					async function loginSync(req) {
 | 
				
			||||||
    const timestampStr = req.body.timestamp;
 | 
					    const timestampStr = req.body.timestamp;
 | 
				
			||||||
@@ -53,7 +55,11 @@ async function loginToProtectedSession(req) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const decryptedDataKey = await passwordEncryptionService.getDataKey(password);
 | 
					    const decryptedDataKey = await passwordEncryptionService.getDataKey(password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const protectedSessionId = protectedSessionService.setDataKey(req, decryptedDataKey);
 | 
					    const protectedSessionId = protectedSessionService.setDataKey(decryptedDataKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cls.namespace.set('protectedSessionId', protectedSessionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eventService.emit(eventService.ENTER_PROTECTED_SESSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        success: true,
 | 
					        success: true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
const sql = require('./sql');
 | 
					const sql = require('./sql');
 | 
				
			||||||
const sqlInit = require('./sql_init');
 | 
					const sqlInit = require('./sql_init');
 | 
				
			||||||
const syncTableService = require('./sync_table');
 | 
					const eventService = require('./events');
 | 
				
			||||||
const repository = require('./repository');
 | 
					const repository = require('./repository');
 | 
				
			||||||
 | 
					const protectedSessionService = require('./protected_session');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let noteTitles;
 | 
					let noteTitles;
 | 
				
			||||||
 | 
					let protectedNoteTitles;
 | 
				
			||||||
let noteIds;
 | 
					let noteIds;
 | 
				
			||||||
const childToParent = {};
 | 
					const childToParent = {};
 | 
				
			||||||
const hideInAutocomplete = {};
 | 
					const hideInAutocomplete = {};
 | 
				
			||||||
@@ -15,7 +17,7 @@ async function load() {
 | 
				
			|||||||
    noteTitles = await sql.getMap(`SELECT noteId, LOWER(title) FROM notes WHERE isDeleted = 0 AND isProtected = 0`);
 | 
					    noteTitles = await sql.getMap(`SELECT noteId, LOWER(title) FROM notes WHERE isDeleted = 0 AND isProtected = 0`);
 | 
				
			||||||
    noteIds = Object.keys(noteTitles);
 | 
					    noteIds = Object.keys(noteTitles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`);
 | 
					    prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, LOWER(prefix) FROM branches WHERE prefix IS NOT NULL AND prefix != ''`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const relations = await sql.getRows(`SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
 | 
					    const relations = await sql.getRows(`SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,7 +41,13 @@ function getResults(query) {
 | 
				
			|||||||
    const tokens = query.toLowerCase().split(" ");
 | 
					    const tokens = query.toLowerCase().split(" ");
 | 
				
			||||||
    const results = [];
 | 
					    const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const noteId in noteTitles) {
 | 
					    let noteIds = Object.keys(noteTitles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (protectedSessionService.isProtectedSessionAvailable()) {
 | 
				
			||||||
 | 
					        noteIds = noteIds.concat(Object.keys(protectedNoteTitles));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const noteId of noteIds) {
 | 
				
			||||||
        if (hideInAutocomplete[noteId]) {
 | 
					        if (hideInAutocomplete[noteId]) {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -50,8 +58,7 @@ function getResults(query) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const parentNoteId of parents) {
 | 
					        for (const parentNoteId of parents) {
 | 
				
			||||||
            const prefix = prefixes[noteId + '-' + parentNoteId];
 | 
					            const title = getNoteTitle(noteId, parentNoteId);
 | 
				
			||||||
            const title = (prefix || '') + ' ' + noteTitles[noteId];
 | 
					 | 
				
			||||||
            const foundTokens = [];
 | 
					            const foundTokens = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (const token of tokens) {
 | 
					            for (const token of tokens) {
 | 
				
			||||||
@@ -78,7 +85,7 @@ function search(noteId, tokens, path, results) {
 | 
				
			|||||||
        const retPath = getSomePath(noteId, path);
 | 
					        const retPath = getSomePath(noteId, path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (retPath) {
 | 
					        if (retPath) {
 | 
				
			||||||
            const noteTitle = getNoteTitle(retPath);
 | 
					            const noteTitle = getNoteTitleForPath(retPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            results.push({
 | 
					            results.push({
 | 
				
			||||||
                title: noteTitle,
 | 
					                title: noteTitle,
 | 
				
			||||||
@@ -102,9 +109,7 @@ function search(noteId, tokens, path, results) {
 | 
				
			|||||||
        if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) {
 | 
					        if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        const title = getNoteTitle(noteId, parentNoteId);
 | 
				
			||||||
        const prefix = prefixes[noteId + '-' + parentNoteId];
 | 
					 | 
				
			||||||
        const title = (prefix || '') + ' ' + noteTitles[noteId];
 | 
					 | 
				
			||||||
        const foundTokens = [];
 | 
					        const foundTokens = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const token of tokens) {
 | 
					        for (const token of tokens) {
 | 
				
			||||||
@@ -124,14 +129,30 @@ function search(noteId, tokens, path, results) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getNoteTitle(path) {
 | 
					function getNoteTitle(noteId, parentNoteId) {
 | 
				
			||||||
 | 
					    const prefix = prefixes[noteId + '-' + parentNoteId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let title = noteTitles[noteId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!title) {
 | 
				
			||||||
 | 
					        if (protectedSessionService.isProtectedSessionAvailable()) {
 | 
				
			||||||
 | 
					            title = protectedNoteTitles[noteId];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            title = '[protected]';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (prefix ? (prefix + ' - ') : '') + title;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getNoteTitleForPath(path) {
 | 
				
			||||||
    const titles = [];
 | 
					    const titles = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let parentNoteId = 'root';
 | 
					    let parentNoteId = 'root';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const noteId of path) {
 | 
					    for (const noteId of path) {
 | 
				
			||||||
        const prefix = prefixes[noteId + '-' + parentNoteId];
 | 
					        const title = getNoteTitle(noteId, parentNoteId);
 | 
				
			||||||
        const title = (prefix ? (prefix + ' - ') : '') + noteTitles[noteId];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        titles.push(title);
 | 
					        titles.push(title);
 | 
				
			||||||
        parentNoteId = noteId;
 | 
					        parentNoteId = noteId;
 | 
				
			||||||
@@ -163,7 +184,7 @@ function getSomePath(noteId, path) {
 | 
				
			|||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
syncTableService.addListener(async (entityName, entityId) => {
 | 
					eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => {
 | 
				
			||||||
    if (entityName === 'notes') {
 | 
					    if (entityName === 'notes') {
 | 
				
			||||||
        const note = await repository.getNote(entityId);
 | 
					        const note = await repository.getNote(entityId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -212,6 +233,14 @@ syncTableService.addListener(async (entityName, entityId) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
 | 
				
			||||||
 | 
					    protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const noteId in protectedNoteTitles) {
 | 
				
			||||||
 | 
					        protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sqlInit.dbReady.then(load);
 | 
					sqlInit.dbReady.then(load);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								src/services/events.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/services/events.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
 | 
				
			||||||
 | 
					const ENTITY_CHANGED = "ENTITY_CHANGED";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const eventListeners = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function subscribe(eventType, listener) {
 | 
				
			||||||
 | 
					    eventListeners[eventType] = eventListeners[eventType] || [];
 | 
				
			||||||
 | 
					    eventListeners[eventType].push(listener);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function emit(eventType, data) {
 | 
				
			||||||
 | 
					    const listeners = eventListeners[eventType];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (listeners) {
 | 
				
			||||||
 | 
					        for (const listener of listeners) {
 | 
				
			||||||
 | 
					            // not awaiting for async processing
 | 
				
			||||||
 | 
					            listener(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					    subscribe,
 | 
				
			||||||
 | 
					    emit,
 | 
				
			||||||
 | 
					    // event types:
 | 
				
			||||||
 | 
					    ENTER_PROTECTED_SESSION,
 | 
				
			||||||
 | 
					    ENTITY_CHANGED
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -6,7 +6,7 @@ const cls = require('./cls');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const dataKeyMap = {};
 | 
					const dataKeyMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setDataKey(req, decryptedDataKey) {
 | 
					function setDataKey(decryptedDataKey) {
 | 
				
			||||||
    const protectedSessionId = utils.randomSecureToken(32);
 | 
					    const protectedSessionId = utils.randomSecureToken(32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dataKeyMap[protectedSessionId] = Array.from(decryptedDataKey); // can't store buffer in session
 | 
					    dataKeyMap[protectedSessionId] = Array.from(decryptedDataKey); // can't store buffer in session
 | 
				
			||||||
@@ -28,12 +28,20 @@ function getDataKey() {
 | 
				
			|||||||
    return dataKeyMap[protectedSessionId];
 | 
					    return dataKeyMap[protectedSessionId];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isProtectedSessionAvailable(req) {
 | 
					function isProtectedSessionAvailable() {
 | 
				
			||||||
    const protectedSessionId = getProtectedSessionId(req);
 | 
					    const protectedSessionId = getProtectedSessionId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return !!dataKeyMap[protectedSessionId];
 | 
					    return !!dataKeyMap[protectedSessionId];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function decryptNoteTitle(noteId, encryptedTitle) {
 | 
				
			||||||
 | 
					    const dataKey = getDataKey();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const iv = dataEncryptionService.noteTitleIv(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return dataEncryptionService.decryptString(dataKey, iv, encryptedTitle);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function decryptNote(note) {
 | 
					function decryptNote(note) {
 | 
				
			||||||
    const dataKey = getDataKey();
 | 
					    const dataKey = getDataKey();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,6 +107,7 @@ module.exports = {
 | 
				
			|||||||
    setDataKey,
 | 
					    setDataKey,
 | 
				
			||||||
    getDataKey,
 | 
					    getDataKey,
 | 
				
			||||||
    isProtectedSessionAvailable,
 | 
					    isProtectedSessionAvailable,
 | 
				
			||||||
 | 
					    decryptNoteTitle,
 | 
				
			||||||
    decryptNote,
 | 
					    decryptNote,
 | 
				
			||||||
    decryptNotes,
 | 
					    decryptNotes,
 | 
				
			||||||
    decryptNoteRevision,
 | 
					    decryptNoteRevision,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,12 +4,7 @@ const dateUtils = require('./date_utils');
 | 
				
			|||||||
const syncSetup = require('./sync_setup');
 | 
					const syncSetup = require('./sync_setup');
 | 
				
			||||||
const log = require('./log');
 | 
					const log = require('./log');
 | 
				
			||||||
const cls = require('./cls');
 | 
					const cls = require('./cls');
 | 
				
			||||||
 | 
					const eventService = require('./events');
 | 
				
			||||||
const listeners = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function addListener(listener) {
 | 
					 | 
				
			||||||
    listeners.push(listener);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function addNoteSync(noteId, sourceId) {
 | 
					async function addNoteSync(noteId, sourceId) {
 | 
				
			||||||
    await addEntitySync("notes", noteId, sourceId)
 | 
					    await addEntitySync("notes", noteId, sourceId)
 | 
				
			||||||
@@ -65,10 +60,10 @@ async function addEntitySync(entityName, entityId, sourceId) {
 | 
				
			|||||||
        await sql.execute("UPDATE options SET value = (SELECT MAX(id) FROM sync) WHERE name IN('lastSyncedPush', 'lastSyncedPull')");
 | 
					        await sql.execute("UPDATE options SET value = (SELECT MAX(id) FROM sync) WHERE name IN('lastSyncedPush', 'lastSyncedPull')");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const listener of listeners) {
 | 
					    eventService.emit(eventService.ENTITY_CHANGED, {
 | 
				
			||||||
        // not awaiting to not block the execution
 | 
					        entityName,
 | 
				
			||||||
        listener(entityName, entityId);
 | 
					        entityId
 | 
				
			||||||
    }
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function cleanupSyncRowsForMissingEntities(entityName, entityKey) {
 | 
					async function cleanupSyncRowsForMissingEntities(entityName, entityKey) {
 | 
				
			||||||
@@ -115,7 +110,6 @@ async function fillAllSyncRows() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    addListener,
 | 
					 | 
				
			||||||
    addNoteSync,
 | 
					    addNoteSync,
 | 
				
			||||||
    addBranchSync,
 | 
					    addBranchSync,
 | 
				
			||||||
    addNoteReorderingSync,
 | 
					    addNoteReorderingSync,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user