mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
added CSRF protection using csurf express middleware, fixes #455
This commit is contained in:
parent
f6413d095c
commit
9fc5d328b4
58
package-lock.json
generated
58
package-lock.json
generated
@ -2012,6 +2012,26 @@
|
|||||||
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
|
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"csrf": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz",
|
||||||
|
"integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=",
|
||||||
|
"requires": {
|
||||||
|
"rndm": "1.2.0",
|
||||||
|
"tsscmp": "1.0.5",
|
||||||
|
"uid-safe": "2.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"uid-safe": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz",
|
||||||
|
"integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=",
|
||||||
|
"requires": {
|
||||||
|
"random-bytes": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-select": {
|
"css-select": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||||
@ -2041,6 +2061,34 @@
|
|||||||
"cssom": "0.3.4"
|
"cssom": "0.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"csurf": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.9.0.tgz",
|
||||||
|
"integrity": "sha1-SdLGkl/87Ht95VlZfBU/pTM2QTM=",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.3.1",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"csrf": "3.0.6",
|
||||||
|
"http-errors": "1.5.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"http-errors": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz",
|
||||||
|
"integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "2.0.3",
|
||||||
|
"setprototypeof": "1.0.2",
|
||||||
|
"statuses": "1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"setprototypeof": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cuint": {
|
"cuint": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||||
@ -10450,6 +10498,11 @@
|
|||||||
"glob": "7.1.3"
|
"glob": "7.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rndm": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
|
||||||
|
},
|
||||||
"run-async": {
|
"run-async": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
||||||
@ -11700,6 +11753,11 @@
|
|||||||
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
|
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tsscmp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc="
|
||||||
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"cls-hooked": "4.2.2",
|
"cls-hooked": "4.2.2",
|
||||||
"commonmark": "0.28.1",
|
"commonmark": "0.28.1",
|
||||||
"cookie-parser": "1.4.4",
|
"cookie-parser": "1.4.4",
|
||||||
|
"csurf": "^1.9.0",
|
||||||
"dayjs": "1.8.11",
|
"dayjs": "1.8.11",
|
||||||
"debug": "4.1.1",
|
"debug": "4.1.1",
|
||||||
"ejs": "2.6.1",
|
"ejs": "2.6.1",
|
||||||
|
@ -98,7 +98,8 @@ $(document).on("click", "button[data-help-page]", e => {
|
|||||||
$("#logout-button").toggle(!utils.isElectron());
|
$("#logout-button").toggle(!utils.isElectron());
|
||||||
|
|
||||||
$("#logout-button").click(() => {
|
$("#logout-button").click(() => {
|
||||||
const $logoutForm = $('<form action="logout" method="POST">');
|
const $logoutForm = $('<form action="logout" method="POST">')
|
||||||
|
.append($(`<input type="hidden" name="_csrf" value="${glob.csrfToken}"/>`));
|
||||||
|
|
||||||
$("body").append($logoutForm);
|
$("body").append($logoutForm);
|
||||||
$logoutForm.submit();
|
$logoutForm.submit();
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import protectedSessionHolder from './protected_session_holder.js';
|
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
|
|
||||||
@ -7,7 +6,8 @@ function getHeaders() {
|
|||||||
// so hypothetical protectedSessionId becomes protectedsessionid on the backend
|
// so hypothetical protectedSessionId becomes protectedsessionid on the backend
|
||||||
// also avoiding using underscores instead of dashes since nginx filters them out by default
|
// also avoiding using underscores instead of dashes since nginx filters them out by default
|
||||||
return {
|
return {
|
||||||
'trilium-source-id': glob.sourceId
|
'trilium-source-id': glob.sourceId,
|
||||||
|
'x-csrf-token': glob.csrfToken
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
src/public/libraries/ckeditor/ckeditor.js
vendored
2
src/public/libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -4,6 +4,8 @@ const fileUploadService = require('./api/file_upload');
|
|||||||
const scriptService = require('../services/script');
|
const scriptService = require('../services/script');
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
|
// explicitly no CSRF middleware since it's meant to allow integration from external services
|
||||||
|
|
||||||
router.all('/custom/:path*', async (req, res, next) => {
|
router.all('/custom/:path*', async (req, res, next) => {
|
||||||
// express puts content after first slash into 0 index element
|
// express puts content after first slash into 0 index element
|
||||||
const path = req.params.path + req.params[0];
|
const path = req.params.path + req.params[0];
|
||||||
|
@ -12,6 +12,7 @@ async function index(req, res) {
|
|||||||
const view = req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop';
|
const view = req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop';
|
||||||
|
|
||||||
res.render(view, {
|
res.render(view, {
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
theme: options.theme,
|
theme: options.theme,
|
||||||
leftPaneMinWidth: parseInt(options.leftPaneMinWidth),
|
leftPaneMinWidth: parseInt(options.leftPaneMinWidth),
|
||||||
leftPaneWidthPercent: parseInt(options.leftPaneWidthPercent),
|
leftPaneWidthPercent: parseInt(options.leftPaneWidthPercent),
|
||||||
|
@ -38,6 +38,9 @@ const auth = require('../services/auth');
|
|||||||
const cls = require('../services/cls');
|
const cls = require('../services/cls');
|
||||||
const sql = require('../services/sql');
|
const sql = require('../services/sql');
|
||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
|
const csurf = require('csurf');
|
||||||
|
|
||||||
|
const csrfMiddleware = csurf({ cookie: true });
|
||||||
|
|
||||||
function apiResultHandler(req, res, result) {
|
function apiResultHandler(req, res, result) {
|
||||||
// if it's an array and first element is integer then we consider this to be [statusCode, response] format
|
// if it's an array and first element is integer then we consider this to be [statusCode, response] format
|
||||||
@ -59,7 +62,7 @@ function apiResultHandler(req, res, result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function apiRoute(method, path, routeHandler) {
|
function apiRoute(method, path, routeHandler) {
|
||||||
route(method, path, [auth.checkApiAuth], routeHandler, apiResultHandler);
|
route(method, path, [auth.checkApiAuth, csrfMiddleware], routeHandler, apiResultHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
function route(method, path, middleware, routeHandler, resultHandler, transactional = true) {
|
function route(method, path, middleware, routeHandler, resultHandler, transactional = true) {
|
||||||
@ -95,10 +98,10 @@ const GET = 'get', POST = 'post', PUT = 'put', DELETE = 'delete';
|
|||||||
const uploadMiddleware = multer.single('upload');
|
const uploadMiddleware = multer.single('upload');
|
||||||
|
|
||||||
function register(app) {
|
function register(app) {
|
||||||
route(GET, '/', [auth.checkAuth], indexRoute.index);
|
route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index);
|
||||||
route(GET, '/login', [auth.checkAppInitialized], loginRoute.loginPage);
|
route(GET, '/login', [auth.checkAppInitialized], loginRoute.loginPage);
|
||||||
route(POST, '/login', [], loginRoute.login);
|
route(POST, '/login', [], loginRoute.login);
|
||||||
route(POST, '/logout', [auth.checkAuth], loginRoute.logout);
|
route(POST, '/logout', [csrfMiddleware, auth.checkAuth], loginRoute.logout);
|
||||||
route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage);
|
route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage);
|
||||||
|
|
||||||
apiRoute(GET, '/api/tree', treeApiRoute.getTree);
|
apiRoute(GET, '/api/tree', treeApiRoute.getTree);
|
||||||
@ -129,9 +132,9 @@ function register(app) {
|
|||||||
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
||||||
|
|
||||||
route(GET, '/api/notes/:branchId/export/:type/:format/:version/:exportId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
|
route(GET, '/api/notes/:branchId/export/:type/:format/:version/:exportId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
|
||||||
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler);
|
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], importRoute.importToBranch, apiResultHandler);
|
||||||
|
|
||||||
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
|
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware],
|
||||||
filesRoute.uploadFile, apiResultHandler);
|
filesRoute.uploadFile, apiResultHandler);
|
||||||
|
|
||||||
route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
|
route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
|
||||||
@ -148,7 +151,7 @@ function register(app) {
|
|||||||
apiRoute(GET, '/api/attributes/values/:attributeName', attributesRoute.getValuesForAttribute);
|
apiRoute(GET, '/api/attributes/values/:attributeName', attributesRoute.getValuesForAttribute);
|
||||||
|
|
||||||
route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
|
route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
|
||||||
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware], imageRoute.uploadImage, apiResultHandler);
|
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.uploadImage, apiResultHandler);
|
||||||
|
|
||||||
apiRoute(GET, '/api/recent-changes', recentChangesApiRoute.getRecentChanges);
|
apiRoute(GET, '/api/recent-changes', recentChangesApiRoute.getRecentChanges);
|
||||||
|
|
||||||
@ -176,6 +179,7 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/recent-notes', recentNotesRoute.addRecentNote);
|
apiRoute(POST, '/api/recent-notes', recentNotesRoute.addRecentNote);
|
||||||
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
|
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
|
||||||
|
|
||||||
|
// group of services below are meant to be executed from outside
|
||||||
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
|
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
|
||||||
route(POST, '/api/setup/new-document', [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler);
|
route(POST, '/api/setup/new-document', [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler);
|
||||||
route(POST, '/api/setup/sync-from-server', [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler, false);
|
route(POST, '/api/setup/sync-from-server', [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler, false);
|
||||||
@ -188,7 +192,7 @@ function register(app) {
|
|||||||
|
|
||||||
apiRoute(POST, '/api/cleanup/cleanup-unused-images', cleanupRoute.cleanupUnusedImages);
|
apiRoute(POST, '/api/cleanup/cleanup-unused-images', cleanupRoute.cleanupUnusedImages);
|
||||||
// VACUUM requires execution outside of transaction
|
// VACUUM requires execution outside of transaction
|
||||||
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron], cleanupRoute.vacuumDatabase, apiResultHandler, false);
|
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.vacuumDatabase, apiResultHandler, false);
|
||||||
|
|
||||||
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
||||||
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
||||||
@ -196,6 +200,7 @@ function register(app) {
|
|||||||
apiRoute(GET, '/api/script/bundle/:noteId', scriptRoute.getBundle);
|
apiRoute(GET, '/api/script/bundle/:noteId', scriptRoute.getBundle);
|
||||||
apiRoute(GET, '/api/script/relation/:noteId/:relationName', scriptRoute.getRelationBundles);
|
apiRoute(GET, '/api/script/relation/:noteId/:relationName', scriptRoute.getRelationBundles);
|
||||||
|
|
||||||
|
// no CSRF since this is called from android app
|
||||||
route(POST, '/api/sender/login', [], senderRoute.login, apiResultHandler);
|
route(POST, '/api/sender/login', [], senderRoute.login, apiResultHandler);
|
||||||
route(POST, '/api/sender/image', [auth.checkSenderToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
|
route(POST, '/api/sender/image', [auth.checkSenderToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
|
||||||
route(POST, '/api/sender/note', [auth.checkSenderToken], senderRoute.saveNote, apiResultHandler);
|
route(POST, '/api/sender/note', [auth.checkSenderToken], senderRoute.saveNote, apiResultHandler);
|
||||||
|
@ -237,7 +237,8 @@
|
|||||||
activeDialog: null,
|
activeDialog: null,
|
||||||
sourceId: '<%= sourceId %>',
|
sourceId: '<%= sourceId %>',
|
||||||
maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>,
|
maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>,
|
||||||
instanceName: '<%= instanceName %>'
|
instanceName: '<%= instanceName %>',
|
||||||
|
csrfToken: '<%= csrfToken %>'
|
||||||
};
|
};
|
||||||
window.appCssNoteIds = <%- JSON.stringify(appCssNoteIds) %>;
|
window.appCssNoteIds = <%- JSON.stringify(appCssNoteIds) %>;
|
||||||
</script>
|
</script>
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
||||||
|
|
||||||
<form action="logout" id="logout-form" method="POST" style="display: none;"></form>
|
<form action="logout" id="logout-form" method="POST" style="display: none;">
|
||||||
|
<input type="hidden" name="_csrf" value="<%= csrfToken %>"/>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -78,7 +80,8 @@
|
|||||||
activeDialog: null,
|
activeDialog: null,
|
||||||
sourceId: '<%= sourceId %>',
|
sourceId: '<%= sourceId %>',
|
||||||
maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>,
|
maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>,
|
||||||
instanceName: '<%= instanceName %>'
|
instanceName: '<%= instanceName %>',
|
||||||
|
csrfToken: '<%= csrfToken %>'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user