diff --git a/package.json b/package.json index 2d543c907..cd7c06ba8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.46.6", + "version": "0.46.7", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/entities/note.js b/src/entities/note.js index 78173c582..07fcd644d 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -49,7 +49,12 @@ class Note extends Entity { this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable(); if (this.isContentAvailable) { - this.title = protectedSessionService.decryptString(this.title); + try { + this.title = protectedSessionService.decryptString(this.title); + } + catch (e) { + throw new Error(`Could not decrypt title of note ${this.noteId}: ${e.message} ${e.stack}`) + } } else { this.title = "[protected]"; diff --git a/src/public/app/services/protected_session_holder.js b/src/public/app/services/protected_session_holder.js index fc041b51e..be2dfc500 100644 --- a/src/public/app/services/protected_session_holder.js +++ b/src/public/app/services/protected_session_holder.js @@ -1,5 +1,6 @@ import utils from "./utils.js"; import options from './options.js'; +import server from "./server.js"; const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; @@ -23,11 +24,11 @@ function resetSessionCookie() { utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); } -function resetProtectedSession() { +async function resetProtectedSession() { resetSessionCookie(); - // most secure solution - guarantees nothing remained in memory - // since this expires because user doesn't use the app, it shouldn't be disruptive + await server.post("logout/protected"); + utils.reloadApp(); } diff --git a/src/routes/api/login.js b/src/routes/api/login.js index 7a16566a9..159e43be1 100644 --- a/src/routes/api/login.js +++ b/src/routes/api/login.js @@ -78,6 +78,12 @@ function loginToProtectedSession(req) { }; } +function logoutFromProtectedSession() { + protectedSessionService.resetDataKey(); + + eventService.emit(eventService.LEAVE_PROTECTED_SESSION); +} + function token(req) { const username = req.body.username; const password = req.body.password; @@ -101,5 +107,6 @@ function token(req) { module.exports = { loginSync, loginToProtectedSession, + logoutFromProtectedSession, token }; diff --git a/src/routes/api/sync.js b/src/routes/api/sync.js index 214acd2e8..43bbdb656 100644 --- a/src/routes/api/sync.js +++ b/src/routes/api/sync.js @@ -200,9 +200,7 @@ function queueSector(req) { const entityName = utils.sanitizeSqlIdentifier(req.params.entityName); const sector = utils.sanitizeSqlIdentifier(req.params.sector); - const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; - - entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector); + entityChangesService.addEntityChangesForSector(entityName, sector); } module.exports = { diff --git a/src/routes/routes.js b/src/routes/routes.js index 830497b60..bdab78429 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -271,6 +271,8 @@ function register(app) { route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); + apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession); + route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler); // in case of local electron, local calls are allowed unauthenticated, for server they need auth diff --git a/src/services/build.js b/src/services/build.js index b7a3eb896..31aecc327 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2021-03-25T20:28:57+01:00", buildRevision: "9139c597e549add45aafdb70f461d6bc39f975d9" }; +module.exports = { buildDate:"2021-04-03T22:37:04+02:00", buildRevision: "ef37a52a06b471e60f9c0f11da704283bbcef6ab" }; diff --git a/src/services/entity_changes.js b/src/services/entity_changes.js index b9b8e600b..72a389dfa 100644 --- a/src/services/entity_changes.js +++ b/src/services/entity_changes.js @@ -53,22 +53,14 @@ function moveEntityChangeToTop(entityName, entityId) { addEntityChange(entityName, entityId, hash, null, isSynced); } -function addEntityChangesForSector(entityName, entityPrimaryKey, sector) { +function addEntityChangesForSector(entityName, sector) { const startTime = Date.now(); - const repository = require('./repository'); + + const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]); sql.transactional(() => { - const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} WHERE SUBSTR(${entityPrimaryKey}, 1, 1) = ?`, [sector]); - - for (const entityId of entityIds) { - // retrieving entity one by one to avoid memory issues with note_contents - const entity = repository.getEntity(`SELECT * FROM ${entityName} WHERE ${entityPrimaryKey} = ?`, [entityId]); - - if (entityName === 'options' && !entity.isSynced) { - continue - } - - insertEntityChange(entityName, entityId, entity.generateHash(), false, entity.getUtcDateChanged(), 'content-check', true); + for (const ec of entityChanges) { + insertEntityChange(entityName, ec.entityId, ec.hash, ec.isErased, ec.utcDateChanged, ec.sourceId, ec.isSynced); } }); diff --git a/src/services/events.js b/src/services/events.js index 0d38f4e3f..826afeb6a 100644 --- a/src/services/events.js +++ b/src/services/events.js @@ -2,6 +2,7 @@ const log = require('./log'); const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED"; const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; +const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION"; const ENTITY_CREATED = "ENTITY_CREATED"; const ENTITY_CHANGED = "ENTITY_CHANGED"; const ENTITY_DELETED = "ENTITY_DELETED"; @@ -47,6 +48,7 @@ module.exports = { // event types: NOTE_TITLE_CHANGED, ENTER_PROTECTED_SESSION, + LEAVE_PROTECTED_SESSION, ENTITY_CREATED, ENTITY_CHANGED, ENTITY_DELETED, diff --git a/src/services/note_cache/entities/note.js b/src/services/note_cache/entities/note.js index 855b0474e..bcf32ac25 100644 --- a/src/services/note_cache/entities/note.js +++ b/src/services/note_cache/entities/note.js @@ -1,6 +1,7 @@ "use strict"; const protectedSessionService = require('../../protected_session'); +const log = require('../../log'); class Note { constructor(noteCache, row) { @@ -424,9 +425,14 @@ class Note { decrypt() { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { - this.title = protectedSessionService.decryptString(this.title); + try { + this.title = protectedSessionService.decryptString(this.title); - this.isDecrypted = true; + this.isDecrypted = true; + } + catch (e) { + log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`); + } } } diff --git a/src/services/note_cache/note_cache_loader.js b/src/services/note_cache/note_cache_loader.js index 63a3de371..07c41de22 100644 --- a/src/services/note_cache/note_cache_loader.js +++ b/src/services/note_cache/note_cache_loader.js @@ -179,6 +179,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { } }); +eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => { + load(); +}); + module.exports = { load }; diff --git a/src/services/protected_session.js b/src/services/protected_session.js index 3decbe960..b86af84e6 100644 --- a/src/services/protected_session.js +++ b/src/services/protected_session.js @@ -5,7 +5,7 @@ const log = require('./log'); const dataEncryptionService = require('./data_encryption'); const cls = require('./cls'); -const dataKeyMap = {}; +let dataKeyMap = {}; function setDataKey(decryptedDataKey) { const protectedSessionId = utils.randomSecureToken(32); @@ -29,6 +29,10 @@ function getDataKey() { return dataKeyMap[protectedSessionId]; } +function resetDataKey() { + dataKeyMap = {}; +} + function isProtectedSessionAvailable() { const protectedSessionId = getProtectedSessionId(); @@ -71,6 +75,7 @@ function decryptString(cipherText) { module.exports = { setDataKey, getDataKey, + resetDataKey, isProtectedSessionAvailable, encrypt, decrypt, diff --git a/src/services/sync.js b/src/services/sync.js index ece81682c..a162c3d95 100644 --- a/src/services/sync.js +++ b/src/services/sync.js @@ -249,9 +249,7 @@ async function checkContentHash(syncContext) { const failedChecks = contentHashService.checkContentHashes(resp.entityHashes); for (const {entityName, sector} of failedChecks) { - const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; - - entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector); + entityChangesService.addEntityChangesForSector(entityName, sector); await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`); } diff --git a/src/services/tree.js b/src/services/tree.js index 46c99e66a..98d1b6205 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -1,6 +1,7 @@ "use strict"; const sql = require('./sql'); +const log = require('./log'); const repository = require('./repository'); const Branch = require('../entities/branch'); const entityChangesService = require('./entity_changes.js'); @@ -139,7 +140,12 @@ function sortNotesByTitle(parentNoteId, foldersFirst = false, reverse = false) { sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [position, note.branchId]); - noteCache.branches[note.branchId].notePosition = position; + if (note.branchId in noteCache.branches) { + noteCache.branches[note.branchId].notePosition = position; + } + else { + log.info(`Branch "${note.branchId}" was not found in note cache.`); + } position += 10; }