From 9cf935efd16398f68598ac7fc9b2093cabed4358 Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 15 Nov 2017 00:04:26 -0500 Subject: [PATCH] protect/unprotect subtree --- public/javascripts/context_menu.js | 15 +++-- public/javascripts/protected_session.js | 17 ++++- routes/api/notes.js | 8 +-- routes/api/tree.js | 13 ++++ services/notes.js | 85 +++++++++++++++++++------ 5 files changed, 109 insertions(+), 29 deletions(-) diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index d573dabc9..51c6f4953 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -31,8 +31,8 @@ const contextMenu = (function() { {title: "Insert child note", cmd: "insertChildNote", uiIcon: "ui-icon-pencil"}, {title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"}, {title: "----"}, - {title: "Encrypt sub-tree", cmd: "encryptSubTree", uiIcon: "ui-icon-locked"}, - {title: "Decrypt sub-tree", cmd: "decryptSubTree", uiIcon: "ui-icon-unlocked"}, + {title: "Protect sub-tree", cmd: "protectSubTree", uiIcon: "ui-icon-locked"}, + {title: "Unprotect sub-tree", cmd: "unprotectSubTree", uiIcon: "ui-icon-unlocked"}, {title: "----"}, {title: "Cut", cmd: "cut", uiIcon: "ui-icon-scissors"}, {title: "Copy / clone", cmd: "copy", uiIcon: "ui-icon-copy"}, @@ -45,6 +45,9 @@ const contextMenu = (function() { treeEl.contextmenu("enableEntry", "pasteAfter", noteTree.getClipboardNoteId() !== null); treeEl.contextmenu("enableEntry", "pasteInto", noteTree.getClipboardNoteId() !== null); + treeEl.contextmenu("enableEntry", "protectSubTree", protected_session.isProtectedSessionAvailable()); + treeEl.contextmenu("enableEntry", "unprotectSubTree", protected_session.isProtectedSessionAvailable()); + // Activate node on right-click node.setActive(); // Disable tree keyboard handling @@ -64,11 +67,11 @@ const contextMenu = (function() { else if (ui.cmd === "insertChildNote") { noteEditor.createNote(node, node.key, 'into'); } - else if (ui.cmd === "encryptSubTree") { - protected_session.encryptSubTree(node.key); + else if (ui.cmd === "protectSubTree") { + protected_session.protectSubTree(node.key, true); } - else if (ui.cmd === "decryptSubTree") { - protected_session.decryptSubTree(node.key); + else if (ui.cmd === "unprotectSubTree") { + protected_session.protectSubTree(node.key, false); } else if (ui.cmd === "cut") { cut(node); diff --git a/public/javascripts/protected_session.js b/public/javascripts/protected_session.js index 67ddf0151..9e7ec3781 100644 --- a/public/javascripts/protected_session.js +++ b/public/javascripts/protected_session.js @@ -147,6 +147,20 @@ const protected_session = (function() { } } + async function protectSubTree(noteId, protect) { + await $.ajax({ + url: baseApiUrl + 'tree/' + noteId + "/protectSubTree/" + (protect ? 1 : 0), + type: 'PUT', + contentType: 'application/json', + error: () => showError("Request to un/protect sub tree has failed.") + }); + + showMessage("Request to un/protect sub tree has finished successfully"); + + noteTree.reload(); + noteEditor.reload(); + } + passwordFormEl.submit(() => { setupProtectedSession(); @@ -167,6 +181,7 @@ const protected_session = (function() { protectNoteAndSendToServer, unprotectNoteAndSendToServer, getProtectedSessionId, - touchProtectedSession + touchProtectedSession, + protectSubTree }; })(); \ No newline at end of file diff --git a/routes/api/notes.js b/routes/api/notes.js index 5b83ac47a..be9f0768d 100644 --- a/routes/api/notes.js +++ b/routes/api/notes.js @@ -12,11 +12,11 @@ const data_encryption = require('../../services/data_encryption'); const RequestContext = require('../../services/request_context'); router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { - let noteId = req.params.noteId; + const noteId = req.params.noteId; await options.setOption('start_node', noteId); - let detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); + const detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); if (detail.is_protected) { const dataKey = protected_session.getDataKey(req); @@ -33,7 +33,7 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { }); router.post('/:parentNoteId/children', async (req, res, next) => { - let parentNoteId = req.params.parentNoteId; + const parentNoteId = req.params.parentNoteId; const browserId = utils.browserId(req); const note = req.body; @@ -46,7 +46,7 @@ router.post('/:parentNoteId/children', async (req, res, next) => { router.put('/:noteId', async (req, res, next) => { const note = req.body; - let noteId = req.params.noteId; + const noteId = req.params.noteId; const reqCtx = new RequestContext(req); await notes.updateNote(noteId, note, reqCtx); diff --git a/routes/api/tree.js b/routes/api/tree.js index 98fa3e286..23615daee 100644 --- a/routes/api/tree.js +++ b/routes/api/tree.js @@ -9,6 +9,7 @@ const auth = require('../../services/auth'); const log = require('../../services/log'); const protected_session = require('../../services/protected_session'); const data_encryption = require('../../services/data_encryption'); +const notes = require('../../services/notes'); router.get('/', auth.checkApiAuth, async (req, res, next) => { const notes = await sql.getResults("select " @@ -59,4 +60,16 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => { }); }); +router.put('/:noteId/protectSubTree/:isProtected', auth.checkApiAuth, async (req, res, next) => { + const noteId = req.params.noteId; + const isProtected = !!parseInt(req.params.isProtected); + const dataKey = protected_session.getDataKey(req); + + await sql.doInTransaction(async () => { + await notes.protectNoteRecursively(noteId, dataKey, isProtected); + }); + + res.send({}); +}); + module.exports = router; diff --git a/services/notes.js b/services/notes.js index 097fe91d4..eaaa46704 100644 --- a/services/notes.js +++ b/services/notes.js @@ -69,6 +69,70 @@ async function encryptNote(note, ctx) { note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_text); } +async function protectNoteRecursively(noteId, dataKey, protect) { + const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]); + + await protectNote(note, dataKey, protect); + + const children = await sql.getFlattenedResults("note_id", "SELECT note_id FROM notes_tree WHERE note_pid = ?", [noteId]); + + for (const childNoteId of children) { + await protectNoteRecursively(childNoteId, dataKey, protect); + } +} + +async function protectNote(note, dataKey, protect) { + let changed = false; + + if (protect && !note.is_protected) { + note.note_title = data_encryption.encrypt(dataKey, note.note_title); + note.note_text = data_encryption.encrypt(dataKey, note.note_text); + note.is_protected = true; + + changed = true; + } + else if (!protect && note.is_protected) { + note.note_title = data_encryption.decrypt(dataKey, note.note_title); + note.note_text = data_encryption.decrypt(dataKey, note.note_text); + note.is_protected = false; + + changed = true; + } + + if (changed) { + console.log("Updating..."); + + await sql.execute("UPDATE notes SET note_title = ?, note_text = ?, is_protected = ? WHERE note_id = ?", + [note.note_title, note.note_text, note.is_protected, note.note_id]); + + await sql.addNoteSync(note.note_id); + } + + await protectNoteHistory(note.note_id, dataKey, protect); +} + +async function protectNoteHistory(noteId, dataKey, protect) { + const historyToChange = await sql.getResults("SELECT * FROM notes_history WHERE note_id = ? AND is_protected != ?", [noteId, protect]); + + for (const history of historyToChange) { + if (protect) { + history.note_title = data_encryption.encrypt(dataKey, history.note_title); + history.note_text = data_encryption.encrypt(dataKey, history.note_text); + history.is_protected = true; + } + else { + history.note_title = data_encryption.decrypt(dataKey, history.note_title); + history.note_text = data_encryption.decrypt(dataKey, history.note_text); + history.is_protected = false; + } + + await sql.execute("UPDATE notes_history SET note_title = ?, note_text = ?, is_protected = ? WHERE note_history_id = ?", + [history.note_title, history.note_text, history.is_protected, history.note_history_id]); + + await sql.addNoteHistorySync(history.note_history_id); + } +} + async function updateNote(noteId, newNote, ctx) { if (newNote.detail.is_protected) { await encryptNote(newNote, ctx); @@ -109,23 +173,7 @@ async function updateNote(noteId, newNote, ctx) { ]); } - const historyToChange = await sql.getResults("SELECT * FROM notes_history WHERE note_id = ? AND is_protected != ?", [noteId, newNote.detail.is_protected]); - - for (const history of historyToChange) { - if (newNote.detail.is_protected) { - history.note_title = data_encryption.encrypt(history.note_title); - history.note_text = data_encryption.encrypt(history.note_text); - history.is_protected = true; - } - else { - history.note_title = data_encryption.decrypt(history.note_title); - history.note_text = data_encryption.decrypt(history.note_text); - history.is_protected = false; - } - - await sql.execute("UPDATE notes_history SET note_title = ?, note_text = ?, is_protected = ? WHERE note_history_id = ?", - [history.note_title, history.note_text, history.is_protected, history.note_history_id]); - } + await protectNoteHistory(noteId, ctx.getDataKey(), newNote.detail.is_protected); await sql.addNoteHistorySync(noteHistoryId); await addNoteAudits(origNoteDetail, newNote.detail, ctx.browserId); @@ -199,5 +247,6 @@ module.exports = { createNewNote, updateNote, addNoteAudits, - deleteNote + deleteNote, + protectNoteRecursively }; \ No newline at end of file