diff --git a/src/entities/note.js b/src/entities/note.js index 7d3263991..8af159663 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -11,7 +11,7 @@ class Note extends Entity { super(repository, row); if (this.isProtected) { - protected_session.decryptNote(this.dataKey, this); + protected_session.decryptNote(this); } if (this.isJson()) { @@ -129,7 +129,7 @@ class Note extends Entity { this.content = JSON.stringify(this.jsonContent, null, '\t'); if (this.isProtected) { - protected_session.encryptNote(this.dataKey, this); + protected_session.encryptNote(this); } } } diff --git a/src/routes/api/file_upload.js b/src/routes/api/file_upload.js index e52cde0de..bc77d966c 100644 --- a/src/routes/api/file_upload.js +++ b/src/routes/api/file_upload.js @@ -37,21 +37,18 @@ async function uploadFile(req) { async function downloadFile(req, res) { const noteId = req.params.noteId; const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); - const protectedSessionId = req.query.protectedSessionId; if (!note) { return res.status(404).send(`Note ${noteId} doesn't exist.`); } if (note.isProtected) { - const dataKey = protected_session.getDataKeyForProtectedSessionId(protectedSessionId); - - if (!dataKey) { + if (!protected_session.isProtectedSessionAvailable()) { res.status(401).send("Protected session not available"); return; } - protected_session.decryptNote(dataKey, note); + protected_session.decryptNote(note); } const labelMap = await labels.getNoteLabelMap(noteId); diff --git a/src/routes/api/note_revisions.js b/src/routes/api/note_revisions.js index ec5b960eb..d72f19282 100644 --- a/src/routes/api/note_revisions.js +++ b/src/routes/api/note_revisions.js @@ -6,7 +6,7 @@ const protected_session = require('../../services/protected_session'); async function getNoteRevisions(req) { const noteId = req.params.noteId; const revisions = await sql.getRows("SELECT * FROM note_revisions WHERE noteId = ? order by dateModifiedTo desc", [noteId]); - protected_session.decryptNoteRevisions(req, revisions); + protected_session.decryptNoteRevisions(revisions); return revisions; } diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 8b81050ca..6d1dbff42 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -20,7 +20,7 @@ async function getNote(req) { return [404, "Note " + noteId + " has not been found."]; } - protected_session.decryptNote(req, note); + protected_session.decryptNote(note); if (note.type === 'file') { // no need to transfer (potentially large) file payload for this request @@ -46,24 +46,21 @@ async function createNote(req) { async function updateNote(req) { const note = req.body; const noteId = req.params.noteId; - const dataKey = protected_session.getDataKey(req); - await notes.updateNote(noteId, note, dataKey); + await notes.updateNote(noteId, note); } async function sortNotes(req) { const noteId = req.params.noteId; - const dataKey = protected_session.getDataKey(req); - await tree.sortNotesAlphabetically(noteId, dataKey); + await tree.sortNotesAlphabetically(noteId); } async function protectBranch(req) { const noteId = req.params.noteId; const isProtected = !!parseInt(req.params.isProtected); - const dataKey = protected_session.getDataKey(req); - await notes.protectNoteRecursively(noteId, dataKey, isProtected); + await notes.protectNoteRecursively(noteId, isProtected); } async function setNoteTypeMime(req) { diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index 9ad9ba5d7..1041b6ad0 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -45,7 +45,7 @@ async function getTree(req) { WHERE notes.isDeleted = 0`)); - protected_session.decryptNotes(req, notes); + protected_session.decryptNotes(notes); notes.forEach(note => { note.hideInAutocomplete = !!note.hideInAutocomplete; diff --git a/src/routes/routes.js b/src/routes/routes.js index 1de2b1364..15cd5ce71 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -38,6 +38,7 @@ const router = express.Router(); const auth = require('../services/auth'); const cls = require('../services/cls'); const sql = require('../services/sql'); +const protectedSessionService = require('../services/protected_session'); function apiResultHandler(res, result) { // if it's an array and first element is integer then we consider this to be [statusCode, response] format @@ -67,6 +68,7 @@ function route(method, path, middleware, routeHandler, resultHandler) { try { const result = await cls.init(async () => { cls.namespace.set('sourceId', req.headers.source_id); + protectedSessionService.setProtectedSessionId(req); return await sql.doInTransaction(async () => { return await routeHandler(req, res, next); diff --git a/src/services/notes.js b/src/services/notes.js index bbe61b9e2..d7b750b2d 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -5,7 +5,7 @@ const sync_table = require('./sync_table'); const labels = require('./labels'); const protected_session = require('./protected_session'); -async function createNewNote(parentNoteId, noteOpts, dataKey) { +async function createNewNote(parentNoteId, noteOpts) { const noteId = utils.newNoteId(); const branchId = utils.newBranchId(); @@ -57,7 +57,7 @@ async function createNewNote(parentNoteId, noteOpts, dataKey) { }; if (note.isProtected) { - protected_session.encryptNote(dataKey, note); + protected_session.encryptNote(note); } await sql.insert("notes", note); @@ -106,7 +106,7 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) note.mime = "text/html"; } - const {noteId} = await createNewNote(parentNoteId, note, extraOptions.dataKey); + const {noteId} = await createNewNote(parentNoteId, note); if (extraOptions.labels) { for (const attrName in extraOptions.labels) { @@ -117,30 +117,30 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) return noteId; } -async function protectNoteRecursively(noteId, dataKey, protect) { +async function protectNoteRecursively(noteId, protect) { const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); - await protectNote(note, dataKey, protect); + await protectNote(note, protect); const children = await sql.getColumn("SELECT noteId FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [noteId]); for (const childNoteId of children) { - await protectNoteRecursively(childNoteId, dataKey, protect); + await protectNoteRecursively(childNoteId, protect); } } -async function protectNote(note, dataKey, protect) { +async function protectNote(note, protect) { let changed = false; if (protect && !note.isProtected) { - protected_session.encryptNote(dataKey, note); + protected_session.encryptNote(note); note.isProtected = true; changed = true; } else if (!protect && note.isProtected) { - protected_session.decryptNote(dataKey, note); + protected_session.decryptNote(note); note.isProtected = false; @@ -154,20 +154,20 @@ async function protectNote(note, dataKey, protect) { await sync_table.addNoteSync(note.noteId); } - await protectNoteRevisions(note.noteId, dataKey, protect); + await protectNoteRevisions(note.noteId, protect); } -async function protectNoteRevisions(noteId, dataKey, protect) { +async function protectNoteRevisions(noteId, protect) { const revisionsToChange = await sql.getRows("SELECT * FROM note_revisions WHERE noteId = ? AND isProtected != ?", [noteId, protect]); for (const revision of revisionsToChange) { if (protect) { - protected_session.encryptNoteRevision(dataKey, revision); + protected_session.encryptNoteRevision(revision); revision.isProtected = true; } else { - protected_session.decryptNoteRevision(dataKey, revision); + protected_session.decryptNoteRevision(revision); revision.isProtected = false; } @@ -179,7 +179,7 @@ async function protectNoteRevisions(noteId, dataKey, protect) { } } -async function saveNoteRevision(noteId, dataKey, nowStr) { +async function saveNoteRevision(noteId, nowStr) { const oldNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); if (oldNote.type === 'file') { @@ -187,7 +187,7 @@ async function saveNoteRevision(noteId, dataKey, nowStr) { } if (oldNote.isProtected) { - protected_session.decryptNote(dataKey, oldNote); + protected_session.decryptNote(oldNote); oldNote.isProtected = false; } @@ -255,23 +255,23 @@ async function saveNoteImages(noteId, noteText) { } } -async function loadFile(noteId, newNote, dataKey) { +async function loadFile(noteId, newNote) { const oldNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); if (oldNote.isProtected) { - await protected_session.decryptNote(dataKey, oldNote); + await protected_session.decryptNote(oldNote); } newNote.content = oldNote.content; } -async function updateNote(noteId, newNote, dataKey) { +async function updateNote(noteId, newNote) { if (newNote.type === 'file') { - await loadFile(noteId, newNote, dataKey); + await loadFile(noteId, newNote); } if (newNote.isProtected) { - await protected_session.encryptNote(dataKey, newNote); + await protected_session.encryptNote(newNote); } const labelsMap = await labels.getNoteLabelMap(noteId); @@ -293,12 +293,12 @@ async function updateNote(noteId, newNote, dataKey) { && !existingnoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) { - await saveNoteRevision(noteId, dataKey, nowStr); + await saveNoteRevision(noteId, nowStr); } await saveNoteImages(noteId, newNote.content); - await protectNoteRevisions(noteId, dataKey, newNote.isProtected); + await protectNoteRevisions(noteId, newNote.isProtected); await sql.execute("UPDATE notes SET title = ?, content = ?, isProtected = ?, dateModified = ? WHERE noteId = ?", [ newNote.title, diff --git a/src/services/protected_session.js b/src/services/protected_session.js index 7bb3ffc14..069ad51df 100644 --- a/src/services/protected_session.js +++ b/src/services/protected_session.js @@ -2,50 +2,43 @@ const utils = require('./utils'); const data_encryption = require('./data_encryption'); -const session = {}; +const dataKeyMap = {}; +const cls = require('./cls'); function setDataKey(req, decryptedDataKey) { - session.decryptedDataKey = Array.from(decryptedDataKey); // can't store buffer in session - session.protectedSessionId = utils.randomSecureToken(32); + const protectedSessionId = utils.randomSecureToken(32); - return session.protectedSessionId; + dataKeyMap[protectedSessionId] = Array.from(decryptedDataKey); // can't store buffer in session + + return protectedSessionId; } -function getProtectedSessionId(req) { - return req.headers.protected_session_id; +function setProtectedSessionId(req) { + cls.namespace.set('protectedSessionId', req.headers.protected_session_id); } -/** - * @param obj - can be either array, in that case it's considered to be already dataKey and we just return it - * if it's not a array, we consider it a request object and try to pull dataKey based on the session id header - */ -function getDataKey(obj) { - if (!obj || obj.constructor.name === 'Array') { - return obj; - } +function getProtectedSessionId() { + return cls.namespace.get('protectedSessionId'); +} - const protectedSessionId = getProtectedSessionId(obj); +function getDataKey() { + const protectedSessionId = getProtectedSessionId(); - return getDataKeyForProtectedSessionId(protectedSessionId); + return dataKeyMap[protectedSessionId]; } function getDataKeyForProtectedSessionId(protectedSessionId) { - if (protectedSessionId && session.protectedSessionId === protectedSessionId) { - return session.decryptedDataKey; - } - else { - return null; - } + return dataKeyMap[protectedSessionId]; } function isProtectedSessionAvailable(req) { const protectedSessionId = getProtectedSessionId(req); - return protectedSessionId && session.protectedSessionId === protectedSessionId; + return !!dataKeyMap[protectedSessionId]; } -function decryptNote(dataKey, note) { - dataKey = getDataKey(dataKey); +function decryptNote(note) { + const dataKey = getDataKey(); if (!note.isProtected) { return; @@ -67,16 +60,16 @@ function decryptNote(dataKey, note) { } } -function decryptNotes(dataKey, notes) { - dataKey = getDataKey(dataKey); +function decryptNotes(notes) { + const dataKey = getDataKey(); for (const note of notes) { - decryptNote(dataKey, note); + decryptNote(note); } } -function decryptNoteRevision(dataKey, hist) { - dataKey = getDataKey(dataKey); +function decryptNoteRevision(hist) { + const dataKey = getDataKey(); if (!hist.isProtected) { return; @@ -91,23 +84,21 @@ function decryptNoteRevision(dataKey, hist) { } } -function decryptNoteRevisions(dataKey, noteRevisions) { - dataKey = getDataKey(dataKey); - +function decryptNoteRevisions(noteRevisions) { for (const revision of noteRevisions) { - decryptNoteRevision(dataKey, revision); + decryptNoteRevision(revision); } } -function encryptNote(dataKey, note) { - dataKey = getDataKey(dataKey); +function encryptNote(note) { + const dataKey = getDataKey(); note.title = data_encryption.encrypt(dataKey, data_encryption.noteTitleIv(note.noteId), note.title); note.content = data_encryption.encrypt(dataKey, data_encryption.noteContentIv(note.noteId), note.content); } -function encryptNoteRevision(dataKey, revision) { - dataKey = getDataKey(dataKey); +function encryptNoteRevision(revision) { + const dataKey = getDataKey(); revision.title = data_encryption.encrypt(dataKey, data_encryption.noteTitleIv(revision.noteRevisionId), revision.title); revision.content = data_encryption.encrypt(dataKey, data_encryption.noteContentIv(revision.noteRevisionId), revision.content); @@ -123,5 +114,6 @@ module.exports = { decryptNoteRevision, decryptNoteRevisions, encryptNote, - encryptNoteRevision + encryptNoteRevision, + setProtectedSessionId }; \ No newline at end of file diff --git a/src/services/repository.js b/src/services/repository.js index 9caae4c97..0f30b87c4 100644 --- a/src/services/repository.js +++ b/src/services/repository.js @@ -7,17 +7,9 @@ const Label = require('../entities/label'); const sync_table = require('../services/sync_table'); class Repository { - constructor(dataKey) { - this.dataKey = protected_session.getDataKey(dataKey); - } - async getEntities(query, params = []) { const rows = await sql.getRows(query, params); - for (const row of rows) { - row.dataKey = this.dataKey; - } - return rows.map(row => this.createEntityFromRow(row)); } @@ -28,8 +20,6 @@ class Repository { return null; } - row.dataKey = this.dataKey; - return this.createEntityFromRow(row); } @@ -66,7 +56,6 @@ class Repository { const clone = Object.assign({}, entity); - delete clone.dataKey; delete clone.jsonContent; delete clone.repository; diff --git a/src/services/script.js b/src/services/script.js index 200815f5b..fa256d8d9 100644 --- a/src/services/script.js +++ b/src/services/script.js @@ -2,17 +2,17 @@ const sql = require('./sql'); const ScriptContext = require('./script_context'); const Repository = require('./repository'); -async function executeNote(dataKey, note) { +async function executeNote(note) { if (!note.isJavaScript()) { return; } const bundle = await getScriptBundle(note); - await executeBundle(dataKey, bundle); + await executeBundle(bundle); } -async function executeBundle(dataKey, bundle, startNote) { +async function executeBundle(bundle, startNote) { if (!startNote) { // this is the default case, the only exception is when we want to preserve frontend startNote startNote = bundle.note; @@ -21,7 +21,7 @@ async function executeBundle(dataKey, bundle, startNote) { // last \r\n is necessary if script contains line comment on its last line const script = "async function() {\r\n" + bundle.script + "\r\n}"; - const ctx = new ScriptContext(dataKey, startNote, bundle.allNotes); + const ctx = new ScriptContext(startNote, bundle.allNotes); if (await bundle.note.hasLabel('manual_transaction_handling')) { return await execute(ctx, script, ''); @@ -35,8 +35,8 @@ async function executeBundle(dataKey, bundle, startNote) { * This method preserves frontend startNode - that's why we start execution from currentNote and override * bundle's startNote. */ -async function executeScript(dataKey, script, params, startNoteId, currentNoteId) { - const repository = new Repository(dataKey); +async function executeScript(script, params, startNoteId, currentNoteId) { + const repository = new Repository(); const startNote = await repository.getNote(startNoteId); const currentNote = await repository.getNote(currentNoteId); @@ -46,7 +46,7 @@ async function executeScript(dataKey, script, params, startNoteId, currentNoteId const bundle = await getScriptBundle(currentNote); - return await executeBundle(dataKey, bundle, startNote); + return await executeBundle(bundle, startNote); } async function execute(ctx, script, paramsStr) { diff --git a/src/services/script_context.js b/src/services/script_context.js index ecd25dfbe..af20e8620 100644 --- a/src/services/script_context.js +++ b/src/services/script_context.js @@ -9,12 +9,10 @@ const config = require('./config'); const Repository = require('./repository'); const axios = require('axios'); -function ScriptContext(dataKey, startNote, allNotes) { - dataKey = protected_session.getDataKey(dataKey); - +function ScriptContext(startNote, allNotes) { this.modules = {}; this.notes = utils.toObject(allNotes, note => [note.noteId, note]); - this.apis = utils.toObject(allNotes, note => [note.noteId, new ScriptApi(dataKey, startNote, note)]); + this.apis = utils.toObject(allNotes, note => [note.noteId, new ScriptApi(startNote, note)]); this.require = moduleNoteIds => { return moduleName => { const candidates = allNotes.filter(note => moduleNoteIds.includes(note.noteId)); @@ -29,8 +27,8 @@ function ScriptContext(dataKey, startNote, allNotes) { }; } -function ScriptApi(dataKey, startNote, currentNote) { - const repository = new Repository(dataKey); +function ScriptApi(startNote, currentNote) { + const repository = new Repository(); this.startNote = startNote; this.currentNote = currentNote; @@ -59,8 +57,6 @@ function ScriptApi(dataKey, startNote, currentNote) { }; this.createNote = async function(parentNoteId, title, content = "", extraOptions = {}) { - extraOptions.dataKey = dataKey; - return await notes.createNote(parentNoteId, title, content, extraOptions); }; diff --git a/src/services/tree.js b/src/services/tree.js index b1e97847e..38888cdfa 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -76,13 +76,13 @@ async function loadSubTreeNoteIds(parentNoteId, subTreeNoteIds) { } } -async function sortNotesAlphabetically(parentNoteId, req) { +async function sortNotesAlphabetically(parentNoteId) { await sql.doInTransaction(async () => { const notes = await sql.getRows(`SELECT branchId, noteId, title, isProtected FROM notes JOIN branches USING(noteId) WHERE branches.isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]); - protected_session.decryptNotes(req, notes); + protected_session.decryptNotes(notes); notes.sort((a, b) => a.title.toLowerCase() < b.title.toLowerCase() ? -1 : 1);