diff --git a/db/migrations/0127__fix_inconsistent_isProtected.sql b/db/migrations/0127__fix_inconsistent_isProtected.sql new file mode 100644 index 000000000..6b4467e44 --- /dev/null +++ b/db/migrations/0127__fix_inconsistent_isProtected.sql @@ -0,0 +1,6 @@ +UPDATE notes SET title = 'Recovered protected note', isProtected = 0 WHERE noteId IN ( + SELECT noteId FROM notes JOIN note_contents USING(noteId) + WHERE notes.isProtected = 1 + AND note_contents.isProtected = 0 + AND notes.isDeleted = 0 +) \ No newline at end of file diff --git a/package.json b/package.json index d77138750..7d03fc370 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.30.6", + "version": "0.30.7", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/public/javascripts/services/protected_session_holder.js b/src/public/javascripts/services/protected_session_holder.js index 89a6f4324..0a958dd9e 100644 --- a/src/public/javascripts/services/protected_session_holder.js +++ b/src/public/javascripts/services/protected_session_holder.js @@ -18,6 +18,10 @@ function setProtectedSessionTimeout(encSessTimeout) { protectedSessionTimeout = encSessTimeout; } +function getProtectedSessionId() { + return utils.getCookie(PROTECTED_SESSION_ID_KEY); +} + function setProtectedSessionId(id) { // using session cookie so that it disappears after browser/tab is closed utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, id); @@ -42,6 +46,7 @@ function touchProtectedSession() { } export default { + getProtectedSessionId, setProtectedSessionId, resetProtectedSession, isProtectedSessionAvailable, diff --git a/src/public/javascripts/services/server.js b/src/public/javascripts/services/server.js index 0dbabf881..c0e50f1c3 100644 --- a/src/public/javascripts/services/server.js +++ b/src/public/javascripts/services/server.js @@ -1,11 +1,22 @@ +import protectedSessionHolder from './protected_session_holder.js'; import utils from './utils.js'; import infoService from "./info.js"; function getHeaders() { + let protectedSessionId = null; + + try { // this is because protected session might not be declared in some cases + protectedSessionId = protectedSessionHolder.getProtectedSessionId(); + } + catch(e) {} + // headers need to be lowercase because node.js automatically converts them to lower case // so hypothetical protectedSessionId becomes protectedsessionid on the backend // also avoiding using underscores instead of dashes since nginx filters them out by default return { + // protectedSessionId is normally carried in cookie, but for electron AJAX requests we bypass + // HTTP so no cookies and we need to pass it here explicitly + 'trilium-protected-session-id': protectedSessionId, 'trilium-source-id': glob.sourceId, 'x-csrf-token': glob.csrfToken }; diff --git a/src/services/build.js b/src/services/build.js index 392a6cc7f..799801087 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2019-03-28T23:17:24+01:00", buildRevision: "b2052a6ccda31712492cffc899ee132b0229613b" }; +module.exports = { buildDate:"2019-03-30T20:13:53+01:00", buildRevision: "c240fb98967d11ea3925c2c39b44138bb8e46f58" }; diff --git a/src/services/notes.js b/src/services/notes.js index d29636bd0..8f49a39d3 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -150,7 +150,7 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) isProtected: !!extraOptions.isProtected, type: extraOptions.type, mime: extraOptions.mime, - utcDateCreated: extraOptions.utcDateCreated, + dateCreated: extraOptions.dateCreated, isExpanded: extraOptions.isExpanded, notePosition: extraOptions.notePosition }; @@ -307,12 +307,12 @@ async function saveNoteRevision(note) { const now = new Date(); const noteRevisionSnapshotTimeInterval = parseInt(await optionService.getOption('noteRevisionSnapshotTimeInterval')); - const revisionCutoff = dateUtils.utcDateStr(new Date(now.getTime() - noteRevisionSnapshotTimeInterval * 1000)); + const revisionCutoff = dateUtils.dateStr(new Date(now.getTime() - noteRevisionSnapshotTimeInterval * 1000)); const existingNoteRevisionId = await sql.getValue( - "SELECT noteRevisionId FROM note_revisions WHERE noteId = ? AND utcDateModifiedTo >= ?", [note.noteId, revisionCutoff]); + "SELECT noteRevisionId FROM note_revisions WHERE noteId = ? AND dateModifiedTo >= ?", [note.noteId, revisionCutoff]); - const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.utcDateCreated).getTime(); + const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.dateCreated).getTime(); if (!existingNoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) { await new NoteRevision({ @@ -324,9 +324,7 @@ async function saveNoteRevision(note) { mime: note.mime, isProtected: false, // will be fixed in the protectNoteRevisions() call dateModifiedFrom: note.dateModified, - dateModifiedTo: dateUtils.localNowDateTime(), - utcDateModifiedFrom: note.utcDateModified, - utcDateModifiedTo: dateUtils.utcNowDateTime() + dateModifiedTo: dateUtils.nowDate() }).save(); } } @@ -346,11 +344,16 @@ async function updateNote(noteId, noteUpdates) { note.isProtected = noteUpdates.isProtected; await note.save(); + const noteContent = await note.getNoteContent(); + if (!['file', 'image'].includes(note.type)) { - noteUpdates.content = await saveLinks(note, noteUpdates.content); + noteUpdates.noteContent.content = await saveLinks(note, noteUpdates.noteContent.content); + + noteContent.content = noteUpdates.noteContent.content; } - await note.setContent(noteUpdates.content); + noteContent.isProtected = noteUpdates.isProtected; + await noteContent.save(); if (noteTitleChanged) { await triggerNoteTitleChanged(note); @@ -410,9 +413,9 @@ async function cleanupDeletedNotes() { // it's better to not use repository for this because it will complain about saving protected notes // out of protected session - await sql.execute("UPDATE note_contents SET content = NULL WHERE content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.utcDateModified <= ?)", [dateUtils.utcDateStr(cutoffDate)]); + await sql.execute("UPDATE note_contents SET content = NULL WHERE content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]); - await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.utcDateModified <= ?)", [dateUtils.utcDateStr(cutoffDate)]); + await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]); } sqlInit.dbReady.then(() => { diff --git a/src/services/protected_session.js b/src/services/protected_session.js index b18815d38..5daae46fc 100644 --- a/src/services/protected_session.js +++ b/src/services/protected_session.js @@ -15,7 +15,8 @@ function setDataKey(decryptedDataKey) { } function setProtectedSessionId(req) { - cls.namespace.set('protectedSessionId', req.cookies.protectedSessionId); + // cookies is the main storage but for electron header is used when bypassing HTTP + cls.namespace.set('protectedSessionId', req.headers['trilium-protected-session-id'] || req.cookies.protectedSessionId); } function getProtectedSessionId() { @@ -81,12 +82,17 @@ function decryptNoteRevision(hist) { return; } - if (hist.title) { - hist.title = dataEncryptionService.decryptString(dataKey, hist.title); - } + try { + if (hist.title) { + hist.title = dataEncryptionService.decryptString(dataKey, hist.title); + } - if (hist.content) { - hist.content = dataEncryptionService.decryptString(dataKey, hist.content); + if (hist.content) { + hist.content = dataEncryptionService.decryptString(dataKey, hist.content); + } + } + catch (e) { + throw new Error(`Decryption failed for note ${hist.noteId}: ` + e.message + " " + e.stack); } }