Replace CSRF middleware Closes #3349

5ff402aabfab1146802ecd7d18112f1e5933d11e

Cohee <18619528+Cohee1207@users.noreply.github.com>

5 files changed, +31 -25Ignore whitespace
default/config.yaml+1 -1
@@ -70,7 +70,7 @@ perUserBasicAuth: false
70## Set to a positive number to expire session after a certain time of inactivity70## Set to a positive number to expire session after a certain time of inactivity
71## Set to 0 to expire session when the browser is closed71## Set to 0 to expire session when the browser is closed
72## Set to a negative number to disable session expiration72## Set to a negative number to disable session expiration
73sessionTimeout: 8640073sessionTimeout: -1
74# Used to sign session cookies. Will be auto-generated if not set74# Used to sign session cookies. Will be auto-generated if not set
75cookieSecret: ''75cookieSecret: ''
76# Disable CSRF protection - NOT RECOMMENDED76# Disable CSRF protection - NOT RECOMMENDED
package-lock.json+5 -5
@@ -26,7 +26,7 @@
26 "cookie-parser": "^1.4.6",26 "cookie-parser": "^1.4.6",
27 "cookie-session": "^2.1.0",27 "cookie-session": "^2.1.0",
28 "cors": "^2.8.5",28 "cors": "^2.8.5",
29 "csrf-csrf": "^2.2.3",29 "csrf-sync": "^4.0.3",
30 "diff-match-patch": "^1.0.5",30 "diff-match-patch": "^1.0.5",
31 "dompurify": "^3.1.7",31 "dompurify": "^3.1.7",
32 "droll": "^0.2.1",32 "droll": "^0.2.1",
@@ -2987,10 +2987,10 @@
2987 "node": "*"2987 "node": "*"
2988 }2988 }
2989 },2989 },
2990 "node_modules/csrf-csrf": {2990 "node_modules/csrf-sync": {
2991 "version": "2.2.4",2991 "version": "4.0.3",
2992 "resolved": "https://registry.npmjs.org/csrf-csrf/-/csrf-csrf-2.2.4.tgz",2992 "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.0.3.tgz",
2993 "integrity": "sha512-LuhBmy5RfRmEfeqeYqgaAuS1eDpVtKZB/Eiec9xiKQLBynJxrGVRdM2yRT/YMl1Njo/yKh2L9AYsIwSlTPnx2A==",2993 "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==",
2994 "license": "ISC",2994 "license": "ISC",
2995 "dependencies": {2995 "dependencies": {
2996 "http-errors": "^2.0.0"2996 "http-errors": "^2.0.0"
package.json+1 -1
@@ -16,7 +16,7 @@
16 "cookie-parser": "^1.4.6",16 "cookie-parser": "^1.4.6",
17 "cookie-session": "^2.1.0",17 "cookie-session": "^2.1.0",
18 "cors": "^2.8.5",18 "cors": "^2.8.5",
19 "csrf-csrf": "^2.2.3",19 "csrf-sync": "^4.0.3",
20 "diff-match-patch": "^1.0.5",20 "diff-match-patch": "^1.0.5",
21 "dompurify": "^3.1.7",21 "dompurify": "^3.1.7",
22 "droll": "^0.2.1",22 "droll": "^0.2.1",
server.js+23 -18
@@ -18,10 +18,9 @@ import { hideBin } from 'yargs/helpers';
1818
19// express/server related library imports19// express/server related library imports
20import cors from 'cors';20import cors from 'cors';
21import { doubleCsrf } from 'csrf-csrf';21import { csrfSync } from 'csrf-sync';
22import express from 'express';22import express from 'express';
23import compression from 'compression';23import compression from 'compression';
24import cookieParser from 'cookie-parser';
25import cookieSession from 'cookie-session';24import cookieSession from 'cookie-session';
26import multer from 'multer';25import multer from 'multer';
27import responseTime from 'response-time';26import responseTime from 'response-time';
@@ -40,7 +39,6 @@ util.inspect.defaultOptions.depth = 4;
40import { loadPlugins } from './src/plugin-loader.js';39import { loadPlugins } from './src/plugin-loader.js';
41import {40import {
42 initUserStorage,41 initUserStorage,
43 getCsrfSecret,
44 getCookieSecret,42 getCookieSecret,
45 getCookieSessionName,43 getCookieSessionName,
46 getAllEnabledUsers,44 getAllEnabledUsers,
@@ -347,8 +345,8 @@ if (enableCorsProxy) {
347}345}
348346
349function getSessionCookieAge() {347function getSessionCookieAge() {
350 // Defaults to 24 hours in seconds if not set348 // Defaults to "no expiration" if not set
351 const configValue = getConfigValue('sessionTimeout', 24 * 60 * 60);349 const configValue = getConfigValue('sessionTimeout', -1);
352350
353 // Convert to milliseconds351 // Convert to milliseconds
354 if (configValue > 0) {352 if (configValue > 0) {
@@ -377,27 +375,34 @@ app.use(setUserDataMiddleware);
377375
378// CSRF Protection //376// CSRF Protection //
379if (!disableCsrf) {377if (!disableCsrf) {
380 const COOKIES_SECRET = getCookieSecret();378 const csrfSyncProtection = csrfSync({
381379 getTokenFromState: (req) => {
382 const { generateToken, doubleCsrfProtection } = doubleCsrf({380 if (!req.session) {
383 getSecret: getCsrfSecret,381 console.error('(CSRF error) getTokenFromState: Session object not initialized');
384 cookieName: 'X-CSRF-Token',382 return;
385 cookieOptions: {383 }
386 sameSite: 'strict',384 return req.session.csrfToken;
387 secure: false,385 },
386 getTokenFromRequest: (req) => {
387 return req.headers['x-csrf-token']?.toString();
388 },
389 storeTokenInState: (req, token) => {
390 if (!req.session) {
391 console.error('(CSRF error) storeTokenInState: Session object not initialized');
392 return;
393 }
394 req.session.csrfToken = token;
388 },395 },
389 size: 64,396 size: 32,
390 getTokenFromRequest: (req) => req.headers['x-csrf-token'],
391 });397 });
392398
393 app.get('/csrf-token', (req, res) => {399 app.get('/csrf-token', (req, res) => {
394 res.json({400 res.json({
395 'token': generateToken(res, req),401 'token': csrfSyncProtection.generateToken(req),
396 });402 });
397 });403 });
398404
399 app.use(cookieParser(COOKIES_SECRET));405 app.use(csrfSyncProtection.csrfSynchronisedProtection);
400 app.use(doubleCsrfProtection);
401} else {406} else {
402 console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n');407 console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n');
403 app.get('/csrf-token', (req, res) => {408 app.get('/csrf-token', (req, res) => {
src/endpoints/users-private.js+1 -0
@@ -23,6 +23,7 @@ router.post('/logout', async (request, response) => {
23 }23 }
2424
25 request.session.handle = null;25 request.session.handle = null;
26 request.session.csrfToken = null;
26 request.session = null;27 request.session = null;
27 return response.sendStatus(204);28 return response.sendStatus(204);
28 } catch (error) {29 } catch (error) {