From 4ce5ea9886b4b8cde39c1cf0386cf8cec0e1e906 Mon Sep 17 00:00:00 2001 From: azivner Date: Fri, 20 Apr 2018 00:12:01 -0400 Subject: [PATCH] autocomplete supports encrypted notes now as well --- src/entities/note.js | 6 ++-- src/entities/note_revision.js | 7 ++-- src/routes/api/login.js | 8 ++++- src/services/autocomplete.js | 55 +++++++++++++++++++++++-------- src/services/events.js | 28 ++++++++++++++++ src/services/protected_session.js | 15 +++++++-- src/services/sync_table.js | 16 +++------ 7 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 src/services/events.js diff --git a/src/entities/note.js b/src/entities/note.js index c14cb1824..16b13d744 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -1,7 +1,7 @@ "use strict"; const Entity = require('./entity'); -const protected_session = require('../services/protected_session'); +const protectedSessionService = require('../services/protected_session'); const repository = require('../services/repository'); 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 if (this.isProtected && this.noteId) { - protected_session.decryptNote(this); + protectedSessionService.decryptNote(this); } this.setContent(this.content); @@ -146,7 +146,7 @@ class Note extends Entity { } if (this.isProtected) { - protected_session.encryptNote(this); + protectedSessionService.encryptNote(this); } if (!this.isDeleted) { diff --git a/src/entities/note_revision.js b/src/entities/note_revision.js index a96c0f1b6..d1f010bb1 100644 --- a/src/entities/note_revision.js +++ b/src/entities/note_revision.js @@ -1,8 +1,7 @@ "use strict"; const Entity = require('./entity'); -const protected_session = require('../services/protected_session'); -const utils = require('../services/utils'); +const protectedSessionService = require('../services/protected_session'); const repository = require('../services/repository'); class NoteRevision extends Entity { @@ -13,7 +12,7 @@ class NoteRevision extends Entity { super(row); if (this.isProtected) { - protected_session.decryptNoteRevision(this); + protectedSessionService.decryptNoteRevision(this); } } @@ -25,7 +24,7 @@ class NoteRevision extends Entity { super.beforeSaving(); if (this.isProtected) { - protected_session.encryptNoteRevision(this); + protectedSessionService.encryptNoteRevision(this); } } } diff --git a/src/routes/api/login.js b/src/routes/api/login.js index 9e0277f52..bdb289d04 100644 --- a/src/routes/api/login.js +++ b/src/routes/api/login.js @@ -7,6 +7,8 @@ const sourceIdService = require('../../services/source_id'); const passwordEncryptionService = require('../../services/password_encryption'); const protectedSessionService = require('../../services/protected_session'); const appInfo = require('../../services/app_info'); +const eventService = require('../../services/events'); +const cls = require('../../services/cls'); async function loginSync(req) { const timestampStr = req.body.timestamp; @@ -53,7 +55,11 @@ async function loginToProtectedSession(req) { 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 { success: true, diff --git a/src/services/autocomplete.js b/src/services/autocomplete.js index 0ff728c02..7a6e93f8c 100644 --- a/src/services/autocomplete.js +++ b/src/services/autocomplete.js @@ -1,9 +1,11 @@ const sql = require('./sql'); const sqlInit = require('./sql_init'); -const syncTableService = require('./sync_table'); +const eventService = require('./events'); const repository = require('./repository'); +const protectedSessionService = require('./protected_session'); let noteTitles; +let protectedNoteTitles; let noteIds; const childToParent = {}; 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`); 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`); @@ -39,7 +41,13 @@ function getResults(query) { const tokens = query.toLowerCase().split(" "); 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]) { continue; } @@ -50,8 +58,7 @@ function getResults(query) { } for (const parentNoteId of parents) { - const prefix = prefixes[noteId + '-' + parentNoteId]; - const title = (prefix || '') + ' ' + noteTitles[noteId]; + const title = getNoteTitle(noteId, parentNoteId); const foundTokens = []; for (const token of tokens) { @@ -78,7 +85,7 @@ function search(noteId, tokens, path, results) { const retPath = getSomePath(noteId, path); if (retPath) { - const noteTitle = getNoteTitle(retPath); + const noteTitle = getNoteTitleForPath(retPath); results.push({ title: noteTitle, @@ -102,9 +109,7 @@ function search(noteId, tokens, path, results) { if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) { continue; } - - const prefix = prefixes[noteId + '-' + parentNoteId]; - const title = (prefix || '') + ' ' + noteTitles[noteId]; + const title = getNoteTitle(noteId, parentNoteId); const foundTokens = []; 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 = []; let parentNoteId = 'root'; for (const noteId of path) { - const prefix = prefixes[noteId + '-' + parentNoteId]; - const title = (prefix ? (prefix + ' - ') : '') + noteTitles[noteId]; + const title = getNoteTitle(noteId, parentNoteId); titles.push(title); parentNoteId = noteId; @@ -163,7 +184,7 @@ function getSomePath(noteId, path) { return false; } -syncTableService.addListener(async (entityName, entityId) => { +eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => { if (entityName === 'notes') { 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); module.exports = { diff --git a/src/services/events.js b/src/services/events.js new file mode 100644 index 000000000..1e7c78b57 --- /dev/null +++ b/src/services/events.js @@ -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 +}; \ No newline at end of file diff --git a/src/services/protected_session.js b/src/services/protected_session.js index 58fc77775..6a796e5b2 100644 --- a/src/services/protected_session.js +++ b/src/services/protected_session.js @@ -6,7 +6,7 @@ const cls = require('./cls'); const dataKeyMap = {}; -function setDataKey(req, decryptedDataKey) { +function setDataKey(decryptedDataKey) { const protectedSessionId = utils.randomSecureToken(32); dataKeyMap[protectedSessionId] = Array.from(decryptedDataKey); // can't store buffer in session @@ -28,12 +28,20 @@ function getDataKey() { return dataKeyMap[protectedSessionId]; } -function isProtectedSessionAvailable(req) { - const protectedSessionId = getProtectedSessionId(req); +function isProtectedSessionAvailable() { + const protectedSessionId = getProtectedSessionId(); return !!dataKeyMap[protectedSessionId]; } +function decryptNoteTitle(noteId, encryptedTitle) { + const dataKey = getDataKey(); + + const iv = dataEncryptionService.noteTitleIv(noteId); + + return dataEncryptionService.decryptString(dataKey, iv, encryptedTitle); +} + function decryptNote(note) { const dataKey = getDataKey(); @@ -99,6 +107,7 @@ module.exports = { setDataKey, getDataKey, isProtectedSessionAvailable, + decryptNoteTitle, decryptNote, decryptNotes, decryptNoteRevision, diff --git a/src/services/sync_table.js b/src/services/sync_table.js index e2c70ccf0..056f85431 100644 --- a/src/services/sync_table.js +++ b/src/services/sync_table.js @@ -4,12 +4,7 @@ const dateUtils = require('./date_utils'); const syncSetup = require('./sync_setup'); const log = require('./log'); const cls = require('./cls'); - -const listeners = []; - -function addListener(listener) { - listeners.push(listener); -} +const eventService = require('./events'); async function addNoteSync(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')"); } - for (const listener of listeners) { - // not awaiting to not block the execution - listener(entityName, entityId); - } + eventService.emit(eventService.ENTITY_CHANGED, { + entityName, + entityId + }); } async function cleanupSyncRowsForMissingEntities(entityName, entityKey) { @@ -115,7 +110,6 @@ async function fillAllSyncRows() { } module.exports = { - addListener, addNoteSync, addBranchSync, addNoteReorderingSync,