mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	removed dataKey where it's not necessary anymore (use of CLS instead)
This commit is contained in:
		| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user