mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	server side WIP - saving encrypted note now works, changing terminology of "encrypted note" to "protected note"
This commit is contained in:
		
							
								
								
									
										52
									
								
								migrations/0028__rename_encryption_to_protected.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								migrations/0028__rename_encryption_to_protected.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | UPDATE audit_log SET category = 'PROTECTED' WHERE category = 'ENCRYPTION'; | ||||||
|  |  | ||||||
|  | DELETE FROM notes WHERE note_clone_id IS NOT NULL AND note_clone_id != ''; | ||||||
|  |  | ||||||
|  | CREATE TABLE `notes_mig` ( | ||||||
|  | 	`note_id`	TEXT NOT NULL, | ||||||
|  | 	`note_title`	TEXT, | ||||||
|  | 	`note_text`	TEXT, | ||||||
|  | 	`date_created`	INT, | ||||||
|  | 	`date_modified`	INT, | ||||||
|  | 	`is_protected`	INT NOT NULL DEFAULT 0, | ||||||
|  | 	`is_deleted`	INT NOT NULL DEFAULT 0, | ||||||
|  | 	PRIMARY KEY(`note_id`) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO notes_mig (note_id, note_title, note_text, date_created, date_modified, is_protected, is_deleted) | ||||||
|  |     SELECT note_id, note_title, note_text, date_created, date_modified, encryption, is_deleted FROM notes; | ||||||
|  |  | ||||||
|  | DROP TABLE notes; | ||||||
|  | ALTER TABLE notes_mig RENAME TO notes; | ||||||
|  |  | ||||||
|  | CREATE INDEX `IDX_notes_is_deleted` ON `notes` ( | ||||||
|  | 	`is_deleted` | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | CREATE TABLE `notes_history_mig` ( | ||||||
|  | 	`note_history_id`	TEXT NOT NULL PRIMARY KEY, | ||||||
|  | 	`note_id`	TEXT NOT NULL, | ||||||
|  | 	`note_title`	TEXT, | ||||||
|  | 	`note_text`	TEXT, | ||||||
|  | 	`is_protected`	INT, | ||||||
|  | 	`date_modified_from` INT, | ||||||
|  | 	`date_modified_to` INT | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO notes_history_mig (note_history_id, note_id, note_title, note_text, is_protected, date_modified_from, date_modified_to) | ||||||
|  |                         SELECT note_history_id, note_id, note_title, note_text, encryption, date_modified_from, date_modified_to FROM notes_history; | ||||||
|  |  | ||||||
|  | DROP TABLE notes_history; | ||||||
|  | ALTER TABLE notes_history_mig RENAME TO notes_history; | ||||||
|  |  | ||||||
|  | CREATE INDEX `IDX_notes_history_note_id` ON `notes_history` ( | ||||||
|  | 	`note_id` | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | CREATE INDEX `IDX_notes_history_note_date_modified_from` ON `notes_history` ( | ||||||
|  | 	`date_modified_from` | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | CREATE INDEX `IDX_notes_history_note_date_modified_to` ON `notes_history` ( | ||||||
|  | 	`date_modified_to` | ||||||
|  | ); | ||||||
| @@ -57,9 +57,9 @@ const contextMenu = (function() { | |||||||
|  |  | ||||||
|             if (ui.cmd === "insertNoteHere") { |             if (ui.cmd === "insertNoteHere") { | ||||||
|                 const parentKey = treeUtils.getParentKey(node); |                 const parentKey = treeUtils.getParentKey(node); | ||||||
|                 const encryption = treeUtils.getParentEncryption(node); |                 const isProtected = treeUtils.getParentEncryption(node); | ||||||
|  |  | ||||||
|                 noteEditor.createNote(node, parentKey, 'after', encryption); |                 noteEditor.createNote(node, parentKey, 'after', isProtected); | ||||||
|             } |             } | ||||||
|             else if (ui.cmd === "insertChildNote") { |             else if (ui.cmd === "insertChildNote") { | ||||||
|                 noteEditor.createNote(node, node.key, 'into'); |                 noteEditor.createNote(node, node.key, 'into'); | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ const noteHistory = (function() { | |||||||
|         let noteTitle = historyItem.note_title; |         let noteTitle = historyItem.note_title; | ||||||
|         let noteText = historyItem.note_text; |         let noteText = historyItem.note_text; | ||||||
|  |  | ||||||
|         if (historyItem.encryption > 0) { |         if (historyItem.is_protected) { | ||||||
|             noteTitle = encryption.decryptString(noteTitle); |             noteTitle = encryption.decryptString(noteTitle); | ||||||
|             noteText = encryption.decryptString(noteText); |             noteText = encryption.decryptString(noteText); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ const encryption = (function() { | |||||||
|         encryptionSessionTimeout = encSessTimeout; |         encryptionSessionTimeout = encSessTimeout; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function ensureEncryptionIsAvailable(requireEncryption, modal) { |     function ensureProtectedSession(requireEncryption, modal) { | ||||||
|         const dfd = $.Deferred(); |         const dfd = $.Deferred(); | ||||||
|  |  | ||||||
|         if (requireEncryption && !isEncryptionAvailable()) { |         if (requireEncryption && !isEncryptionAvailable()) { | ||||||
| @@ -116,15 +116,6 @@ const encryption = (function() { | |||||||
|         return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5)); |         return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function encryptNoteIfNecessary(note) { |  | ||||||
|         if (note.detail.encryption === 0) { |  | ||||||
|             return note; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             return encryptNote(note); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     function encryptString(str) { |     function encryptString(str) { | ||||||
|         return encrypt(getDataAes(), str); |         return encrypt(getDataAes(), str); | ||||||
|     } |     } | ||||||
| @@ -184,36 +175,34 @@ const encryption = (function() { | |||||||
|         note.detail.note_title = encryptString(note.detail.note_title); |         note.detail.note_title = encryptString(note.detail.note_title); | ||||||
|         note.detail.note_text = encryptString(note.detail.note_text); |         note.detail.note_text = encryptString(note.detail.note_text); | ||||||
|  |  | ||||||
|         note.detail.encryption = 1; |         note.detail.is_protected = true; | ||||||
|  |  | ||||||
|         return note; |         return note; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function encryptNoteAndSendToServer() { |     async function protectNoteAndSendToServer() { | ||||||
|         await ensureEncryptionIsAvailable(true, true); |         await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|         const note = noteEditor.getCurrentNote(); |         const note = noteEditor.getCurrentNote(); | ||||||
|  |  | ||||||
|         noteEditor.updateNoteFromInputs(note); |         noteEditor.updateNoteFromInputs(note); | ||||||
|  |  | ||||||
|         encryptNote(note); |         note.detail.is_protected = true; | ||||||
|  |  | ||||||
|         await noteEditor.saveNoteToServer(note); |         await noteEditor.saveNoteToServer(note); | ||||||
|  |  | ||||||
|         await changeEncryptionOnNoteHistory(note.detail.note_id, true); |  | ||||||
|  |  | ||||||
|         noteEditor.setNoteBackgroundIfEncrypted(note); |         noteEditor.setNoteBackgroundIfEncrypted(note); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function changeEncryptionOnNoteHistory(noteId, encrypt) { |     async function changeEncryptionOnNoteHistory(noteId, protect) { | ||||||
|         const result = await $.ajax({ |         const result = await $.ajax({ | ||||||
|             url: baseApiUrl + 'notes-history/' + noteId + "?encryption=" + (encrypt ? 0 : 1), |             url: baseApiUrl + 'notes-history/' + noteId + "?encryption=" + (protect ? 0 : 1), | ||||||
|             type: 'GET', |             type: 'GET', | ||||||
|             error: () => showError("Error getting note history.") |             error: () => showError("Error getting note history.") | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         for (const row of result) { |         for (const row of result) { | ||||||
|             if (encrypt) { |             if (protect) { | ||||||
|                 row.note_title = encryptString(row.note_title); |                 row.note_title = encryptString(row.note_title); | ||||||
|                 row.note_text = encryptString(row.note_text); |                 row.note_text = encryptString(row.note_text); | ||||||
|             } |             } | ||||||
| @@ -222,7 +211,7 @@ const encryption = (function() { | |||||||
|                 row.note_text = decryptString(row.note_text); |                 row.note_text = decryptString(row.note_text); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             row.encryption = encrypt ? 1 : 0; |             row.is_protected = protect; | ||||||
|  |  | ||||||
|             await $.ajax({ |             await $.ajax({ | ||||||
|                 url: baseApiUrl + 'notes-history', |                 url: baseApiUrl + 'notes-history', | ||||||
| @@ -236,14 +225,14 @@ const encryption = (function() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function decryptNoteAndSendToServer() { |     async function unprotectNoteAndSendToServer() { | ||||||
|         await ensureEncryptionIsAvailable(true, true); |         await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|         const note = noteEditor.getCurrentNote(); |         const note = noteEditor.getCurrentNote(); | ||||||
|  |  | ||||||
|         noteEditor.updateNoteFromInputs(note); |         noteEditor.updateNoteFromInputs(note); | ||||||
|  |  | ||||||
|         note.detail.encryption = 0; |         note.detail.is_protected = false; | ||||||
|  |  | ||||||
|         await noteEditor.saveNoteToServer(note); |         await noteEditor.saveNoteToServer(note); | ||||||
|  |  | ||||||
| @@ -253,13 +242,13 @@ const encryption = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function encryptSubTree(noteId) { |     async function encryptSubTree(noteId) { | ||||||
|         await ensureEncryptionIsAvailable(true, true); |         await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|         updateSubTreeRecursively(noteId, note => { |         updateSubTreeRecursively(noteId, note => { | ||||||
|                 if (note.detail.encryption === null || note.detail.encryption === 0) { |                 if (!note.detail.is_protected) { | ||||||
|                     encryptNote(note); |                     encryptNote(note); | ||||||
|  |  | ||||||
|                     note.detail.encryption = 1; |                     note.detail.is_protected = true; | ||||||
|  |  | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
| @@ -280,13 +269,13 @@ const encryption = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function decryptSubTree(noteId) { |     async function decryptSubTree(noteId) { | ||||||
|         await ensureEncryptionIsAvailable(true, true); |         await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|         updateSubTreeRecursively(noteId, note => { |         updateSubTreeRecursively(noteId, note => { | ||||||
|                 if (note.detail.encryption === 1) { |                 if (note.detail.is_protected) { | ||||||
|                     decryptNote(note); |                     decryptNote(note); | ||||||
|  |  | ||||||
|                     note.detail.encryption = 0; |                     note.detail.is_protected = false; | ||||||
|  |  | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
| @@ -368,14 +357,13 @@ const encryption = (function() { | |||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         setEncryptionSessionTimeout, |         setEncryptionSessionTimeout, | ||||||
|         ensureEncryptionIsAvailable, |         ensureProtectedSession, | ||||||
|         resetEncryptionSession, |         resetEncryptionSession, | ||||||
|         isEncryptionAvailable, |         isEncryptionAvailable, | ||||||
|         encryptNoteIfNecessary, |  | ||||||
|         encryptString, |         encryptString, | ||||||
|         decryptString, |         decryptString, | ||||||
|         encryptNoteAndSendToServer, |         protectNoteAndSendToServer, | ||||||
|         decryptNoteAndSendToServer, |         unprotectNoteAndSendToServer, | ||||||
|         encryptSubTree, |         encryptSubTree, | ||||||
|         decryptSubTree, |         decryptSubTree, | ||||||
|         getProtectedSessionId |         getProtectedSessionId | ||||||
|   | |||||||
| @@ -59,8 +59,6 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|         updateNoteFromInputs(note); |         updateNoteFromInputs(note); | ||||||
|  |  | ||||||
|         encryption.encryptNoteIfNecessary(note); |  | ||||||
|  |  | ||||||
|         await saveNoteToServer(note); |         await saveNoteToServer(note); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -70,7 +68,7 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|         note.detail.note_text = contents; |         note.detail.note_text = contents; | ||||||
|  |  | ||||||
|         if (!note.detail.encryption) { |         if (!note.detail.is_protected) { | ||||||
|             const linkRegexp = /<a[^>]+?href="[^"]*app#([A-Za-z0-9]{22})"[^>]*?>[^<]+?<\/a>/g; |             const linkRegexp = /<a[^>]+?href="[^"]*app#([A-Za-z0-9]{22})"[^>]*?>[^<]+?<\/a>/g; | ||||||
|             let match; |             let match; | ||||||
|  |  | ||||||
| @@ -170,11 +168,11 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|     function setTreeBasedOnEncryption(note) { |     function setTreeBasedOnEncryption(note) { | ||||||
|         const node = treeUtils.getNodeByKey(note.detail.note_id); |         const node = treeUtils.getNodeByKey(note.detail.note_id); | ||||||
|         node.toggleClass("encrypted", note.detail.encryption > 0); |         node.toggleClass("encrypted", note.detail.is_protected); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function setNoteBackgroundIfEncrypted(note) { |     function setNoteBackgroundIfEncrypted(note) { | ||||||
|         if (note.detail.encryption > 0) { |         if (note.detail.is_protected) { | ||||||
|             $(".note-editable").addClass("encrypted"); |             $(".note-editable").addClass("encrypted"); | ||||||
|             encryptButton.hide(); |             encryptButton.hide(); | ||||||
|             decryptButton.show(); |             decryptButton.show(); | ||||||
| @@ -197,7 +195,7 @@ const noteEditor = (function() { | |||||||
|             noteTitleEl.focus().select(); |             noteTitleEl.focus().select(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await encryption.ensureEncryptionIsAvailable(currentNote.detail.encryption > 0, false); |         await encryption.ensureProtectedSession(currentNote.detail.is_protected, false); | ||||||
|  |  | ||||||
|         noteDetailWrapperEl.show(); |         noteDetailWrapperEl.show(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ const noteTree = (function() { | |||||||
|  |  | ||||||
|             note.title = note.note_title; |             note.title = note.note_title; | ||||||
|  |  | ||||||
|             if (note.encryption > 0) { |             if (note.is_protected) { | ||||||
|                 note.extraClasses = "encrypted"; |                 note.extraClasses = "encrypted"; | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
| @@ -63,7 +63,7 @@ const noteTree = (function() { | |||||||
|                 noteEditor.createNote(node, parentKey, 'after', encryption); |                 noteEditor.createNote(node, parentKey, 'after', encryption); | ||||||
|             }, |             }, | ||||||
|             "ctrl+insert": node => { |             "ctrl+insert": node => { | ||||||
|                 noteEditor.createNote(node, node.key, 'into', node.data.encryption); |                 noteEditor.createNote(node, node.key, 'into', node.data.is_protected); | ||||||
|             }, |             }, | ||||||
|             "del": node => { |             "del": node => { | ||||||
|                 treeChanges.deleteNode(node); |                 treeChanges.deleteNode(node); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ const treeUtils = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getParentEncryption(node) { |     function getParentEncryption(node) { | ||||||
|         return node.getParent() === null ? 0 : node.getParent().data.encryption; |         return node.getParent() === null ? 0 : node.getParent().data.is_protected; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getNodeByKey(noteId) { |     function getNodeByKey(noteId) { | ||||||
|   | |||||||
| @@ -7,15 +7,15 @@ const auth = require('../../services/auth'); | |||||||
|  |  | ||||||
| router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
|     const encryption = req.query.encryption; |     const isProtected = req.query.is_protected; | ||||||
|  |  | ||||||
|     let history; |     let history; | ||||||
|  |  | ||||||
|     if (encryption === undefined) { |     if (isProtected === undefined) { | ||||||
|         history = await sql.getResults("select * from notes_history where note_id = ? order by date_modified_to desc", [noteId]); |         history = await sql.getResults("select * from notes_history where note_id = ? order by date_modified_to desc", [noteId]); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         history = await sql.getResults("select * from notes_history where note_id = ? and encryption = ? order by date_modified_to desc", [noteId, encryption]); |         history = await sql.getResults("select * from notes_history where note_id = ? and is_protected = ? order by date_modified_to desc", [noteId, is_protected]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send(history); |     res.send(history); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ const utils = require('../../services/utils'); | |||||||
| const notes = require('../../services/notes'); | const notes = require('../../services/notes'); | ||||||
| const protected_session = require('../../services/protected_session'); | const protected_session = require('../../services/protected_session'); | ||||||
| const data_encryption = require('../../services/data_encryption'); | const data_encryption = require('../../services/data_encryption'); | ||||||
|  | const RequestContext = require('../../services/request_context'); | ||||||
|  |  | ||||||
| router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | ||||||
|     let noteId = req.params.noteId; |     let noteId = req.params.noteId; | ||||||
| @@ -17,12 +18,7 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | |||||||
|  |  | ||||||
|     let detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); |     let detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); | ||||||
|  |  | ||||||
|     if (detail.note_clone_id) { |     if (detail.is_protected) { | ||||||
|         noteId = detail.note_clone_id; |  | ||||||
|         detail = sql.getSingleResult("select * from notes where note_id = ?", [noteId]); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (detail.encryption > 0) { |  | ||||||
|         const dataKey = protected_session.getDataKey(req); |         const dataKey = protected_session.getDataKey(req); | ||||||
|  |  | ||||||
|         detail.note_title = data_encryption.decrypt(dataKey, detail.note_title); |         detail.note_title = data_encryption.decrypt(dataKey, detail.note_title); | ||||||
| @@ -49,11 +45,11 @@ router.post('/:parentNoteId/children', async (req, res, next) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| router.put('/:noteId', async (req, res, next) => { | router.put('/:noteId', async (req, res, next) => { | ||||||
|     const newNote = req.body; |     const note = req.body; | ||||||
|     let noteId = req.params.noteId; |     let noteId = req.params.noteId; | ||||||
|     const browserId = utils.browserId(req); |     const reqCtx = new RequestContext(req); | ||||||
|  |  | ||||||
|     await notes.updateNote(noteId, newNote, browserId); |     await notes.updateNote(noteId, note, reqCtx); | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -13,13 +13,10 @@ const data_encryption = require('../../services/data_encryption'); | |||||||
| router.get('/', auth.checkApiAuth, async (req, res, next) => { | router.get('/', auth.checkApiAuth, async (req, res, next) => { | ||||||
|     const notes = await sql.getResults("select " |     const notes = await sql.getResults("select " | ||||||
|         + "notes_tree.*, " |         + "notes_tree.*, " | ||||||
|         + "COALESCE(clone.note_title, notes.note_title) as note_title, " |         + "notes.note_title, " | ||||||
|         + "notes.note_clone_id, " |         + "notes.is_protected " | ||||||
|         + "notes.encryption, " |  | ||||||
|         + "case when notes.note_clone_id is null or notes.note_clone_id = '' then 0 else 1 end as is_clone " |  | ||||||
|         + "from notes_tree " |         + "from notes_tree " | ||||||
|         + "join notes on notes.note_id = notes_tree.note_id " |         + "join notes on notes.note_id = notes_tree.note_id " | ||||||
|         + "left join notes as clone on notes.note_clone_id = clone.note_id " |  | ||||||
|         + "where notes.is_deleted = 0 and notes_tree.is_deleted = 0 " |         + "where notes.is_deleted = 0 and notes_tree.is_deleted = 0 " | ||||||
|         + "order by note_pid, note_pos"); |         + "order by note_pid, note_pos"); | ||||||
|  |  | ||||||
| @@ -29,7 +26,7 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => { | |||||||
|     const dataKey = protected_session.getDataKey(req); |     const dataKey = protected_session.getDataKey(req); | ||||||
|  |  | ||||||
|     for (const note of notes) { |     for (const note of notes) { | ||||||
|         if (note['encryption']) { |         if (note.is_protected) { | ||||||
|             note.note_title = data_encryption.decrypt(dataKey, note.note_title); |             note.note_title = data_encryption.decrypt(dataKey, note.note_title); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ module.exports = { | |||||||
|     CREATE_NOTE: 'CREATE', |     CREATE_NOTE: 'CREATE', | ||||||
|     DELETE_NOTE: 'DELETE', |     DELETE_NOTE: 'DELETE', | ||||||
|     CHANGE_PARENT: 'PARENT', |     CHANGE_PARENT: 'PARENT', | ||||||
|     ENCRYPTION: 'ENCRYPTION', |     PROTECTED: 'PROTECTED', | ||||||
|     CHANGE_PASSWORD: 'PASSWORD', |     CHANGE_PASSWORD: 'PASSWORD', | ||||||
|     SETTINGS: 'SETTINGS', |     SETTINGS: 'SETTINGS', | ||||||
|     SYNC: 'SYNC' |     SYNC: 'SYNC' | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| const protected_session = require('./protected_session'); | "use strict"; | ||||||
|  |  | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const aesjs = require('./aes'); | const aesjs = require('./aes'); | ||||||
|  | const sha256 = require('./sha256'); | ||||||
|  |  | ||||||
| function getProtectedSessionId(req) { | function getProtectedSessionId(req) { | ||||||
|     return req.headers['x-protected-session-id']; |     return req.headers['x-protected-session-id']; | ||||||
| @@ -10,6 +12,15 @@ function getDataAes(dataKey) { | |||||||
|     return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5)); |     return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function arraysIdentical(a, b) { | ||||||
|  |     let i = a.length; | ||||||
|  |     if (i !== b.length) return false; | ||||||
|  |     while (i--) { | ||||||
|  |         if (a[i] !== b[i]) return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| function decrypt(dataKey, encryptedBase64) { | function decrypt(dataKey, encryptedBase64) { | ||||||
|     if (!dataKey) { |     if (!dataKey) { | ||||||
|         return "[protected]"; |         return "[protected]"; | ||||||
| @@ -24,10 +35,42 @@ function decrypt(dataKey, encryptedBase64) { | |||||||
|     const digest = decryptedBytes.slice(0, 4); |     const digest = decryptedBytes.slice(0, 4); | ||||||
|     const payload = decryptedBytes.slice(4); |     const payload = decryptedBytes.slice(4); | ||||||
|  |  | ||||||
|  |     const hashArray = sha256Array(payload); | ||||||
|  |  | ||||||
|  |     const computedDigest = hashArray.slice(0, 4); | ||||||
|  |  | ||||||
|  |     if (!arraysIdentical(digest, computedDigest)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return aesjs.utils.utf8.fromBytes(payload); |     return aesjs.utils.utf8.fromBytes(payload); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function encrypt(dataKey, plainText) { | ||||||
|  |     if (!dataKey) { | ||||||
|  |         throw new Error("No data key!"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const aes = getDataAes(dataKey); | ||||||
|  |  | ||||||
|  |     const payload = Array.from(aesjs.utils.utf8.toBytes(plainText)); | ||||||
|  |     const digest = sha256Array(payload).slice(0, 4); | ||||||
|  |  | ||||||
|  |     const digestWithPayload = digest.concat(payload); | ||||||
|  |  | ||||||
|  |     const encryptedBytes = aes.encrypt(digestWithPayload); | ||||||
|  |  | ||||||
|  |     return utils.toBase64(encryptedBytes); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function sha256Array(content) { | ||||||
|  |     const hash = sha256.create(); | ||||||
|  |     hash.update(content); | ||||||
|  |     return hash.array(); | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getProtectedSessionId, |     getProtectedSessionId, | ||||||
|     decrypt |     decrypt, | ||||||
|  |     encrypt | ||||||
| }; | }; | ||||||
| @@ -4,7 +4,7 @@ const options = require('./options'); | |||||||
| const fs = require('fs-extra'); | const fs = require('fs-extra'); | ||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 27; | const APP_DB_VERSION = 28; | ||||||
| const MIGRATIONS_DIR = "./migrations"; | const MIGRATIONS_DIR = "./migrations"; | ||||||
|  |  | ||||||
| async function migrate() { | async function migrate() { | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ const options = require('./options'); | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const notes = require('./notes'); | const notes = require('./notes'); | ||||||
| const audit_category = require('./audit_category'); | const audit_category = require('./audit_category'); | ||||||
|  | const data_encryption = require('./data_encryption'); | ||||||
|  |  | ||||||
| async function createNewNote(parentNoteId, note, browserId) { | async function createNewNote(parentNoteId, note, browserId) { | ||||||
|     const noteId = utils.newNoteId(); |     const noteId = utils.newNoteId(); | ||||||
| @@ -46,10 +47,9 @@ async function createNewNote(parentNoteId, note, browserId) { | |||||||
|             'note_id': noteId, |             'note_id': noteId, | ||||||
|             'note_title': note.note_title, |             'note_title': note.note_title, | ||||||
|             'note_text': '', |             'note_text': '', | ||||||
|             'note_clone_id': '', |  | ||||||
|             'date_created': now, |             'date_created': now, | ||||||
|             'date_modified': now, |             'date_modified': now, | ||||||
|             'encryption': note.encryption |             'is_protected': note.is_protected | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         await sql.insert("notes_tree", { |         await sql.insert("notes_tree", { | ||||||
| @@ -64,13 +64,17 @@ async function createNewNote(parentNoteId, note, browserId) { | |||||||
|     return noteId; |     return noteId; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function updateNote(noteId, newNote, browserId) { | async function encryptNote(note, ctx) { | ||||||
|     const origNoteDetail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); |     note.detail.note_title = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_title); | ||||||
|  |     note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_text); | ||||||
|  | } | ||||||
|  |  | ||||||
|     if (origNoteDetail.note_clone_id) { | async function updateNote(noteId, newNote, ctx) { | ||||||
|         noteId = origNoteDetail.note_clone_id; |     if (newNote.detail.is_protected) { | ||||||
|  |         await encryptNote(newNote, ctx); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const origNoteDetail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); | ||||||
|  |  | ||||||
|     const now = utils.nowTimestamp(); |     const now = utils.nowTimestamp(); | ||||||
|  |  | ||||||
| @@ -82,10 +86,10 @@ async function updateNote(noteId, newNote, browserId) { | |||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         if (noteHistoryId) { |         if (noteHistoryId) { | ||||||
|             await sql.execute("update notes_history set note_title = ?, note_text = ?, encryption = ?, date_modified_to = ? where note_history_id = ?", [ |             await sql.execute("update notes_history set note_title = ?, note_text = ?, is_protected = ?, date_modified_to = ? where note_history_id = ?", [ | ||||||
|                 newNote.detail.note_title, |                 newNote.detail.note_title, | ||||||
|                 newNote.detail.note_text, |                 newNote.detail.note_text, | ||||||
|                 newNote.detail.encryption, |                 newNote.detail.is_protected, | ||||||
|                 now, |                 now, | ||||||
|                 noteHistoryId |                 noteHistoryId | ||||||
|             ]); |             ]); | ||||||
| @@ -93,25 +97,25 @@ async function updateNote(noteId, newNote, browserId) { | |||||||
|         else { |         else { | ||||||
|             noteHistoryId = utils.randomString(16); |             noteHistoryId = utils.randomString(16); | ||||||
|  |  | ||||||
|             await sql.execute("insert into notes_history (note_history_id, note_id, note_title, note_text, encryption, date_modified_from, date_modified_to) " + |             await sql.execute("insert into notes_history (note_history_id, note_id, note_title, note_text, is_protected, date_modified_from, date_modified_to) " + | ||||||
|                 "values (?, ?, ?, ?, ?, ?, ?)", [ |                 "values (?, ?, ?, ?, ?, ?, ?)", [ | ||||||
|                 noteHistoryId, |                 noteHistoryId, | ||||||
|                 noteId, |                 noteId, | ||||||
|                 newNote.detail.note_title, |                 newNote.detail.note_title, | ||||||
|                 newNote.detail.note_text, |                 newNote.detail.note_text, | ||||||
|                 newNote.detail.encryption, |                 newNote.detail.is_protected, | ||||||
|                 now, |                 now, | ||||||
|                 now |                 now | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await sql.addNoteHistorySync(noteHistoryId); |         await sql.addNoteHistorySync(noteHistoryId); | ||||||
|         await addNoteAudits(origNoteDetail, newNote.detail, browserId); |         await addNoteAudits(origNoteDetail, newNote.detail, ctx.browserId); | ||||||
|  |  | ||||||
|         await sql.execute("update notes set note_title = ?, note_text = ?, encryption = ?, date_modified = ? where note_id = ?", [ |         await sql.execute("update notes set note_title = ?, note_text = ?, is_protected = ?, date_modified = ? where note_id = ?", [ | ||||||
|             newNote.detail.note_title, |             newNote.detail.note_title, | ||||||
|             newNote.detail.note_text, |             newNote.detail.note_text, | ||||||
|             newNote.detail.encryption, |             newNote.detail.is_protected, | ||||||
|             now, |             now, | ||||||
|             noteId]); |             noteId]); | ||||||
|  |  | ||||||
| @@ -147,10 +151,10 @@ async function addNoteAudits(origNote, newNote, browserId) { | |||||||
|         await sql.addAudit(audit_category.UPDATE_CONTENT, browserId, noteId); |         await sql.addAudit(audit_category.UPDATE_CONTENT, browserId, noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!origNote || newNote.encryption !== origNote.encryption) { |     if (!origNote || newNote.is_protected !== origNote.is_protected) { | ||||||
|         const origEncryption = origNote ? origNote.encryption : null; |         const origIsProtected = origNote ? origNote.is_protected : null; | ||||||
|  |  | ||||||
|         await sql.addAudit(audit_category.ENCRYPTION, browserId, noteId, origEncryption, newNote.encryption); |         await sql.addAudit(audit_category.PROTECTED, browserId, noteId, origIsProtected, newNote.is_protected); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
|  |  | ||||||
| function setDataKey(req, decryptedDataKey) { | function setDataKey(req, decryptedDataKey) { | ||||||
| @@ -7,8 +9,12 @@ function setDataKey(req, decryptedDataKey) { | |||||||
|     return req.session.protectedSessionId; |     return req.session.protectedSessionId; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getProtectedSessionId(req) { | ||||||
|  |     return req.headers['x-protected-session-id']; | ||||||
|  | } | ||||||
|  |  | ||||||
| function getDataKey(req) { | function getDataKey(req) { | ||||||
|     const protectedSessionId = req.headers['x-protected-session-id']; |     const protectedSessionId = getProtectedSessionId(req); | ||||||
|  |  | ||||||
|     if (protectedSessionId && req.session.protectedSessionId === protectedSessionId) { |     if (protectedSessionId && req.session.protectedSessionId === protectedSessionId) { | ||||||
|         return req.session.decryptedDataKey; |         return req.session.decryptedDataKey; | ||||||
| @@ -18,7 +24,14 @@ function getDataKey(req) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isProtectedSessionAvailable(req) { | ||||||
|  |     const protectedSessionId = getProtectedSessionId(req); | ||||||
|  |  | ||||||
|  |     return protectedSessionId && req.session.protectedSessionId === protectedSessionId; | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     setDataKey, |     setDataKey, | ||||||
|     getDataKey |     getDataKey, | ||||||
|  |     isProtectedSessionAvailable | ||||||
| }; | }; | ||||||
							
								
								
									
										25
									
								
								services/request_context.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								services/request_context.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const protected_session = require('./protected_session'); | ||||||
|  |  | ||||||
|  | module.exports = function(req) { | ||||||
|  |     const browserId = req.headers['x-browser-id']; | ||||||
|  |  | ||||||
|  |     function isProtectedSessionAvailable() { | ||||||
|  |         return protected_session.isProtectedSessionAvailable(req); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getDataKey() { | ||||||
|  |         if (!isProtectedSessionAvailable()) { | ||||||
|  |             throw new Error("Protected session is not available"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return protected_session.getDataKey(req); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         browserId, | ||||||
|  |         isProtectedSessionAvailable, | ||||||
|  |         getDataKey | ||||||
|  |     }; | ||||||
|  | }; | ||||||
							
								
								
									
										1
									
								
								services/sha256.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/sha256.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -239,6 +239,10 @@ async function syncRequest(syncContext, method, uri, body) { | |||||||
| if (isSyncSetup) { | if (isSyncSetup) { | ||||||
|     log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT); |     log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT); | ||||||
|  |  | ||||||
|  |     if (SYNC_PROXY) { | ||||||
|  |         log.info("Sync proxy: " + SYNC_PROXY); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     setInterval(sync, 60000); |     setInterval(sync, 60000); | ||||||
|  |  | ||||||
|     // kickoff initial sync immediately |     // kickoff initial sync immediately | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ | |||||||
|  |  | ||||||
|       <div class="hide-toggle" style="grid-area: title;"> |       <div class="hide-toggle" style="grid-area: title;"> | ||||||
|         <div style="display: flex; align-items: center;"> |         <div style="display: flex; align-items: center;"> | ||||||
|           <a onclick="encryption.encryptNoteAndSendToServer()" |           <a onclick="encryption.protectNoteAndSendToServer()" | ||||||
|              title="Encrypt the note so that password will be required to view the note" |              title="Encrypt the note so that password will be required to view the note" | ||||||
|              class="icon-action" |              class="icon-action" | ||||||
|              id="encrypt-button" |              id="encrypt-button" | ||||||
| @@ -76,7 +76,7 @@ | |||||||
|             <img src="images/icons/lock.png" alt="Encrypt note"/> |             <img src="images/icons/lock.png" alt="Encrypt note"/> | ||||||
|           </a> |           </a> | ||||||
|  |  | ||||||
|           <a onclick="encryption.decryptNoteAndSendToServer()" |           <a onclick="encryption.unprotectNoteAndSendToServer()" | ||||||
|              title="Decrypt note permamently so that password will not be required to access this note in the future" |              title="Decrypt note permamently so that password will not be required to access this note in the future" | ||||||
|              class="icon-action" |              class="icon-action" | ||||||
|              id="decrypt-button" |              id="decrypt-button" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user