mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08:00 
			
		
		
		
	protected session is now global application state to avoid weird issues with multiple tabs/windows/reloads
This commit is contained in:
		
							parent
							
								
									fddab59265
								
							
						
					
					
						commit
						de6108f95d
					
				| @ -119,8 +119,6 @@ const appContext = new AppContext(window.glob.isMainWindow); | |||||||
| 
 | 
 | ||||||
| // we should save all outstanding changes before the page/app is closed
 | // we should save all outstanding changes before the page/app is closed
 | ||||||
| $(window).on('beforeunload', () => { | $(window).on('beforeunload', () => { | ||||||
|     protectedSessionHolder.resetSessionCookie(); |  | ||||||
| 
 |  | ||||||
|     let allSaved = true; |     let allSaved = true; | ||||||
| 
 | 
 | ||||||
|     appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter(wr => !!wr.deref()); |     appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter(wr => !!wr.deref()); | ||||||
|  | |||||||
| @ -67,8 +67,6 @@ function setupGlobs() { | |||||||
|         return false; |         return false; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     protectedSessionHolder.setProtectedSessionId(null); |  | ||||||
| 
 |  | ||||||
|     for (const appCssNoteId of glob.appCssNoteIds || []) { |     for (const appCssNoteId of glob.appCssNoteIds || []) { | ||||||
|         libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`); |         libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,16 +1,16 @@ | |||||||
| import utils from './utils.js'; |  | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import protectedSessionHolder from './protected_session_holder.js'; | import protectedSessionHolder from './protected_session_holder.js'; | ||||||
| import toastService from "./toast.js"; | import toastService from "./toast.js"; | ||||||
| import ws from "./ws.js"; | import ws from "./ws.js"; | ||||||
| import appContext from "./app_context.js"; | import appContext from "./app_context.js"; | ||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
|  | import utils from "./utils.js"; | ||||||
| 
 | 
 | ||||||
| let protectedSessionDeferred = null; | let protectedSessionDeferred = null; | ||||||
| 
 | 
 | ||||||
| async function leaveProtectedSession() { | async function leaveProtectedSession() { | ||||||
|     if (protectedSessionHolder.isProtectedSessionAvailable()) { |     if (protectedSessionHolder.isProtectedSessionAvailable()) { | ||||||
|         protectedSessionHolder.resetProtectedSession(); |         await protectedSessionHolder.resetProtectedSession(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -41,37 +41,37 @@ async function reloadData() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function setupProtectedSession(password) { | async function setupProtectedSession(password) { | ||||||
|     const response = await enterProtectedSessionOnServer(password); |     const response = await server.post('login/protected', { password: password }); | ||||||
| 
 | 
 | ||||||
|     if (!response.success) { |     if (!response.success) { | ||||||
|         toastService.showError("Wrong password.", 3000); |         toastService.showError("Wrong password.", 3000); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protectedSessionHolder.setProtectedSessionId(response.protectedSessionId); |     protectedSessionHolder.enableProtectedSession(); | ||||||
|     protectedSessionHolder.touchProtectedSession(); | } | ||||||
| 
 | 
 | ||||||
|     await reloadData(); | ws.subscribeToMessages(async message => { | ||||||
|  |     if (message.type === 'protectedSessionLogin') { | ||||||
|  |         await reloadData(); | ||||||
| 
 | 
 | ||||||
|     await appContext.triggerEvent('treeCacheReloaded'); |         await appContext.triggerEvent('treeCacheReloaded'); | ||||||
| 
 | 
 | ||||||
|     appContext.triggerEvent('protectedSessionStarted'); |         appContext.triggerEvent('protectedSessionStarted'); | ||||||
| 
 | 
 | ||||||
|     if (protectedSessionDeferred !== null) { |         if (protectedSessionDeferred !== null) { | ||||||
|         import("../dialogs/protected_session.js").then(dialog => dialog.close()); |             import("../dialogs/protected_session.js").then(dialog => dialog.close()); | ||||||
| 
 | 
 | ||||||
|         protectedSessionDeferred.resolve(true); |             protectedSessionDeferred.resolve(true); | ||||||
|         protectedSessionDeferred = null; |             protectedSessionDeferred = null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         toastService.showMessage("Protected session has been started."); | ||||||
|     } |     } | ||||||
| 
 |     else if (message.type === 'protectedSessionLogout') { | ||||||
|     toastService.showMessage("Protected session has been started."); |         utils.reloadApp(); | ||||||
| } |     } | ||||||
| 
 | }); | ||||||
| async function enterProtectedSessionOnServer(password) { |  | ||||||
|     return await server.post('login/protected', { |  | ||||||
|         password: password |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| async function protectNote(noteId, protect, includingSubtree) { | async function protectNote(noteId, protect, includingSubtree) { | ||||||
|     await enterProtectedSession(); |     await enterProtectedSession(); | ||||||
|  | |||||||
| @ -1,9 +1,6 @@ | |||||||
| import utils from "./utils.js"; |  | ||||||
| import options from './options.js'; | import options from './options.js'; | ||||||
| import server from "./server.js"; | import server from "./server.js"; | ||||||
| 
 | 
 | ||||||
| const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; |  | ||||||
| 
 |  | ||||||
| let lastProtectedSessionOperationDate = 0; | let lastProtectedSessionOperationDate = 0; | ||||||
| 
 | 
 | ||||||
| setInterval(() => { | setInterval(() => { | ||||||
| @ -15,32 +12,23 @@ setInterval(() => { | |||||||
|     } |     } | ||||||
| }, 10000); | }, 10000); | ||||||
| 
 | 
 | ||||||
| function setProtectedSessionId(id) { | function enableProtectedSession() { | ||||||
|     // using session cookie so that it disappears after browser/tab is closed
 |     glob.isProtectedSessionAvailable = true; | ||||||
|     utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, id); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| function resetSessionCookie() { |     touchProtectedSession(); | ||||||
|     utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function resetProtectedSession() { | async function resetProtectedSession() { | ||||||
|     resetSessionCookie(); |  | ||||||
| 
 |  | ||||||
|     await server.post("logout/protected"); |     await server.post("logout/protected"); | ||||||
| 
 |  | ||||||
|     utils.reloadApp(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function isProtectedSessionAvailable() { | function isProtectedSessionAvailable() { | ||||||
|     return !!utils.getCookie(PROTECTED_SESSION_ID_KEY); |     return glob.isProtectedSessionAvailable; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function touchProtectedSession() { | function touchProtectedSession() { | ||||||
|     if (isProtectedSessionAvailable()) { |     if (isProtectedSessionAvailable()) { | ||||||
|         lastProtectedSessionOperationDate = Date.now(); |         lastProtectedSessionOperationDate = Date.now(); | ||||||
| 
 |  | ||||||
|         setProtectedSessionId(utils.getCookie(PROTECTED_SESSION_ID_KEY)); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -51,8 +39,7 @@ function touchProtectedSessionIfNecessary(note) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|     setProtectedSessionId, |     enableProtectedSession, | ||||||
|     resetSessionCookie, |  | ||||||
|     resetProtectedSession, |     resetProtectedSession, | ||||||
|     isProtectedSessionAvailable, |     isProtectedSessionAvailable, | ||||||
|     touchProtectedSession, |     touchProtectedSession, | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import BasicWidget from "./basic_widget.js"; | import BasicWidget from "./basic_widget.js"; | ||||||
| import HistoryNavigationWidget from "./history_navigation.js"; | import HistoryNavigationWidget from "./history_navigation.js"; | ||||||
|  | import protectedSessionHolder from "../services/protected_session_holder.js"; | ||||||
| import protectedSessionService from "../services/protected_session.js"; | import protectedSessionService from "../services/protected_session.js"; | ||||||
| import QuickSearchWidget from "./quick_search.js"; | import QuickSearchWidget from "./quick_search.js"; | ||||||
| 
 | 
 | ||||||
| @ -68,8 +69,7 @@ const TPL = ` | |||||||
|         </button> |         </button> | ||||||
|      |      | ||||||
|         <button class="btn btn-sm leave-protected-session-button noborder" |         <button class="btn btn-sm leave-protected-session-button noborder" | ||||||
|                 title="Leave protected session so that protected notes are not accessible any more." |                 title="Leave protected session so that protected notes are not accessible any more."> | ||||||
|                 style="display: none;"> |  | ||||||
|             <span class="bx bx-log-out"></span> |             <span class="bx bx-log-out"></span> | ||||||
|      |      | ||||||
|             Leave protected session |             Leave protected session | ||||||
| @ -96,9 +96,11 @@ export default class StandardTopWidget extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|         this.$enterProtectedSessionButton = this.$widget.find(".enter-protected-session-button"); |         this.$enterProtectedSessionButton = this.$widget.find(".enter-protected-session-button"); | ||||||
|         this.$enterProtectedSessionButton.on('click', protectedSessionService.enterProtectedSession); |         this.$enterProtectedSessionButton.on('click', protectedSessionService.enterProtectedSession); | ||||||
|  |         this.$enterProtectedSessionButton.toggle(!protectedSessionHolder.isProtectedSessionAvailable()); | ||||||
| 
 | 
 | ||||||
|         this.$leaveProtectedSessionButton = this.$widget.find(".leave-protected-session-button"); |         this.$leaveProtectedSessionButton = this.$widget.find(".leave-protected-session-button"); | ||||||
|         this.$leaveProtectedSessionButton.on('click', protectedSessionService.leaveProtectedSession); |         this.$leaveProtectedSessionButton.on('click', protectedSessionService.leaveProtectedSession); | ||||||
|  |         this.$leaveProtectedSessionButton.toggle(protectedSessionHolder.isProtectedSessionAvailable()); | ||||||
| 
 | 
 | ||||||
|         return this.$widget; |         return this.$widget; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ import treeService from "../../services/tree.js"; | |||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | ||||||
| 
 | 
 | ||||||
| const ENABLE_INSPECTOR = true; | const ENABLE_INSPECTOR = false; | ||||||
| 
 | 
 | ||||||
| const mentionSetup = { | const mentionSetup = { | ||||||
|     feeds: [ |     feeds: [ | ||||||
| @ -121,7 +121,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); |         this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); | ||||||
| console.log('glob.isDev', glob.isDev); | 
 | ||||||
|         if (glob.isDev && ENABLE_INSPECTOR) { |         if (glob.isDev && ENABLE_INSPECTOR) { | ||||||
|             await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector.js'); |             await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector.js'); | ||||||
|             CKEditorInspector.attach(this.textEditor); |             CKEditorInspector.attach(this.textEditor); | ||||||
|  | |||||||
| @ -8,11 +8,11 @@ const passwordEncryptionService = require('../../services/password_encryption'); | |||||||
| const protectedSessionService = require('../../services/protected_session'); | const protectedSessionService = require('../../services/protected_session'); | ||||||
| const appInfo = require('../../services/app_info'); | const appInfo = require('../../services/app_info'); | ||||||
| const eventService = require('../../services/events'); | const eventService = require('../../services/events'); | ||||||
| const cls = require('../../services/cls'); |  | ||||||
| const sqlInit = require('../../services/sql_init'); | const sqlInit = require('../../services/sql_init'); | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const optionService = require('../../services/options'); | const optionService = require('../../services/options'); | ||||||
| const ApiToken = require('../../entities/api_token'); | const ApiToken = require('../../entities/api_token'); | ||||||
|  | const ws = require("../../services/ws.js"); | ||||||
| 
 | 
 | ||||||
| function loginSync(req) { | function loginSync(req) { | ||||||
|     if (!sqlInit.schemaExists()) { |     if (!sqlInit.schemaExists()) { | ||||||
| @ -65,16 +65,14 @@ function loginToProtectedSession(req) { | |||||||
| 
 | 
 | ||||||
|     const decryptedDataKey = passwordEncryptionService.getDataKey(password); |     const decryptedDataKey = passwordEncryptionService.getDataKey(password); | ||||||
| 
 | 
 | ||||||
|     const protectedSessionId = protectedSessionService.setDataKey(decryptedDataKey); |     protectedSessionService.setDataKey(decryptedDataKey); | ||||||
| 
 |  | ||||||
|     // this is set here so that event handlers have access to the protected session
 |  | ||||||
|     cls.set('protectedSessionId', protectedSessionId); |  | ||||||
| 
 | 
 | ||||||
|     eventService.emit(eventService.ENTER_PROTECTED_SESSION); |     eventService.emit(eventService.ENTER_PROTECTED_SESSION); | ||||||
| 
 | 
 | ||||||
|  |     ws.sendMessageToAllClients({ type: 'protectedSessionLogin' }); | ||||||
|  | 
 | ||||||
|     return { |     return { | ||||||
|         success: true, |         success: true | ||||||
|         protectedSessionId: protectedSessionId |  | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -82,6 +80,8 @@ function logoutFromProtectedSession() { | |||||||
|     protectedSessionService.resetDataKey(); |     protectedSessionService.resetDataKey(); | ||||||
| 
 | 
 | ||||||
|     eventService.emit(eventService.LEAVE_PROTECTED_SESSION); |     eventService.emit(eventService.LEAVE_PROTECTED_SESSION); | ||||||
|  | 
 | ||||||
|  |     ws.sendMessageToAllClients({ type: 'protectedSessionLogout' }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function token(req) { | function token(req) { | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ const config = require('../services/config'); | |||||||
| const optionService = require('../services/options'); | const optionService = require('../services/options'); | ||||||
| const log = require('../services/log'); | const log = require('../services/log'); | ||||||
| const env = require('../services/env'); | const env = require('../services/env'); | ||||||
|  | const protectedSessionService = require("../services/protected_session.js"); | ||||||
| 
 | 
 | ||||||
| function index(req, res) { | function index(req, res) { | ||||||
|     const options = optionService.getOptionsMap(); |     const options = optionService.getOptionsMap(); | ||||||
| @ -30,7 +31,8 @@ function index(req, res) { | |||||||
|         appCssNoteIds: getAppCssNoteIds(), |         appCssNoteIds: getAppCssNoteIds(), | ||||||
|         isDev: env.isDev(), |         isDev: env.isDev(), | ||||||
|         isMainWindow: !req.query.extra, |         isMainWindow: !req.query.extra, | ||||||
|         extraHoistedNoteId: req.query.extraHoistedNoteId |         extraHoistedNoteId: req.query.extraHoistedNoteId, | ||||||
|  |         isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable() | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -93,7 +93,6 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio | |||||||
|                 cls.set('sourceId', req.headers['trilium-source-id']); |                 cls.set('sourceId', req.headers['trilium-source-id']); | ||||||
|                 cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); |                 cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); | ||||||
|                 cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root'); |                 cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root'); | ||||||
|                 protectedSessionService.setProtectedSessionId(req); |  | ||||||
| 
 | 
 | ||||||
|                 const cb = () => routeHandler(req, res, next); |                 const cb = () => routeHandler(req, res, next); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,42 +1,24 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const utils = require('./utils'); |  | ||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
| const dataEncryptionService = require('./data_encryption'); | const dataEncryptionService = require('./data_encryption'); | ||||||
| const cls = require('./cls'); |  | ||||||
| 
 | 
 | ||||||
| let dataKeyMap = {}; | let dataKey = null; | ||||||
| 
 | 
 | ||||||
| function setDataKey(decryptedDataKey) { | function setDataKey(decryptedDataKey) { | ||||||
|     const protectedSessionId = utils.randomSecureToken(32); |     dataKey = Array.from(decryptedDataKey); | ||||||
| 
 |  | ||||||
|     dataKeyMap[protectedSessionId] = Array.from(decryptedDataKey); // can't store buffer in session
 |  | ||||||
| 
 |  | ||||||
|     return protectedSessionId; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function setProtectedSessionId(req) { |  | ||||||
|     cls.set('protectedSessionId', req.cookies.protectedSessionId); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function getProtectedSessionId() { |  | ||||||
|     return cls.get('protectedSessionId'); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getDataKey() { | function getDataKey() { | ||||||
|     const protectedSessionId = getProtectedSessionId(); |     return dataKey; | ||||||
| 
 |  | ||||||
|     return dataKeyMap[protectedSessionId]; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function resetDataKey() { | function resetDataKey() { | ||||||
|     dataKeyMap = {}; |     dataKey = null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function isProtectedSessionAvailable() { | function isProtectedSessionAvailable() { | ||||||
|     const protectedSessionId = getProtectedSessionId(); |     return !!dataKey; | ||||||
| 
 |  | ||||||
|     return !!dataKeyMap[protectedSessionId]; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function decryptNotes(notes) { | function decryptNotes(notes) { | ||||||
| @ -74,12 +56,10 @@ function decryptString(cipherText) { | |||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     setDataKey, |     setDataKey, | ||||||
|     getDataKey, |  | ||||||
|     resetDataKey, |     resetDataKey, | ||||||
|     isProtectedSessionAvailable, |     isProtectedSessionAvailable, | ||||||
|     encrypt, |     encrypt, | ||||||
|     decrypt, |     decrypt, | ||||||
|     decryptString, |     decryptString, | ||||||
|     decryptNotes, |     decryptNotes | ||||||
|     setProtectedSessionId |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -56,6 +56,7 @@ | |||||||
|         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>, |         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>, | ||||||
|         isMainWindow: <%= isMainWindow %>, |         isMainWindow: <%= isMainWindow %>, | ||||||
|         extraHoistedNoteId: '<%= extraHoistedNoteId %>', |         extraHoistedNoteId: '<%= extraHoistedNoteId %>', | ||||||
|  |         isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>, | ||||||
|     }; |     }; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam