Replace CSRF middleware

Closes #3349
This commit is contained in:
Cohee
2025-01-25 16:56:11 +02:00
parent 0937f44f39
commit 5ff402aabf
5 changed files with 31 additions and 25 deletions

View File

@ -70,7 +70,7 @@ perUserBasicAuth: false
## Set to a positive number to expire session after a certain time of inactivity ## Set to a positive number to expire session after a certain time of inactivity
## Set to 0 to expire session when the browser is closed ## Set to 0 to expire session when the browser is closed
## Set to a negative number to disable session expiration ## Set to a negative number to disable session expiration
sessionTimeout: 86400 sessionTimeout: -1
# Used to sign session cookies. Will be auto-generated if not set # Used to sign session cookies. Will be auto-generated if not set
cookieSecret: '' cookieSecret: ''
# Disable CSRF protection - NOT RECOMMENDED # Disable CSRF protection - NOT RECOMMENDED

10
package-lock.json generated
View File

@ -26,7 +26,7 @@
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cookie-session": "^2.1.0", "cookie-session": "^2.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"csrf-csrf": "^2.2.3", "csrf-sync": "^4.0.3",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"dompurify": "^3.1.7", "dompurify": "^3.1.7",
"droll": "^0.2.1", "droll": "^0.2.1",
@ -2987,10 +2987,10 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/csrf-csrf": { "node_modules/csrf-sync": {
"version": "2.2.4", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/csrf-csrf/-/csrf-csrf-2.2.4.tgz", "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.0.3.tgz",
"integrity": "sha512-LuhBmy5RfRmEfeqeYqgaAuS1eDpVtKZB/Eiec9xiKQLBynJxrGVRdM2yRT/YMl1Njo/yKh2L9AYsIwSlTPnx2A==", "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"http-errors": "^2.0.0" "http-errors": "^2.0.0"

View File

@ -16,7 +16,7 @@
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cookie-session": "^2.1.0", "cookie-session": "^2.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"csrf-csrf": "^2.2.3", "csrf-sync": "^4.0.3",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"dompurify": "^3.1.7", "dompurify": "^3.1.7",
"droll": "^0.2.1", "droll": "^0.2.1",

View File

@ -18,10 +18,9 @@ import { hideBin } from 'yargs/helpers';
// express/server related library imports // express/server related library imports
import cors from 'cors'; import cors from 'cors';
import { doubleCsrf } from 'csrf-csrf'; import { csrfSync } from 'csrf-sync';
import express from 'express'; import express from 'express';
import compression from 'compression'; import compression from 'compression';
import cookieParser from 'cookie-parser';
import cookieSession from 'cookie-session'; import cookieSession from 'cookie-session';
import multer from 'multer'; import multer from 'multer';
import responseTime from 'response-time'; import responseTime from 'response-time';
@ -40,7 +39,6 @@ util.inspect.defaultOptions.depth = 4;
import { loadPlugins } from './src/plugin-loader.js'; import { loadPlugins } from './src/plugin-loader.js';
import { import {
initUserStorage, initUserStorage,
getCsrfSecret,
getCookieSecret, getCookieSecret,
getCookieSessionName, getCookieSessionName,
getAllEnabledUsers, getAllEnabledUsers,
@ -347,8 +345,8 @@ if (enableCorsProxy) {
} }
function getSessionCookieAge() { function getSessionCookieAge() {
// Defaults to 24 hours in seconds if not set // Defaults to "no expiration" if not set
const configValue = getConfigValue('sessionTimeout', 24 * 60 * 60); const configValue = getConfigValue('sessionTimeout', -1);
// Convert to milliseconds // Convert to milliseconds
if (configValue > 0) { if (configValue > 0) {
@ -377,27 +375,34 @@ app.use(setUserDataMiddleware);
// CSRF Protection // // CSRF Protection //
if (!disableCsrf) { if (!disableCsrf) {
const COOKIES_SECRET = getCookieSecret(); const csrfSyncProtection = csrfSync({
getTokenFromState: (req) => {
const { generateToken, doubleCsrfProtection } = doubleCsrf({ if (!req.session) {
getSecret: getCsrfSecret, console.error('(CSRF error) getTokenFromState: Session object not initialized');
cookieName: 'X-CSRF-Token', return;
cookieOptions: { }
sameSite: 'strict', return req.session.csrfToken;
secure: false,
}, },
size: 64, getTokenFromRequest: (req) => {
getTokenFromRequest: (req) => req.headers['x-csrf-token'], return req.headers['x-csrf-token']?.toString();
},
storeTokenInState: (req, token) => {
if (!req.session) {
console.error('(CSRF error) storeTokenInState: Session object not initialized');
return;
}
req.session.csrfToken = token;
},
size: 32,
}); });
app.get('/csrf-token', (req, res) => { app.get('/csrf-token', (req, res) => {
res.json({ res.json({
'token': generateToken(res, req), 'token': csrfSyncProtection.generateToken(req),
}); });
}); });
app.use(cookieParser(COOKIES_SECRET)); app.use(csrfSyncProtection.csrfSynchronisedProtection);
app.use(doubleCsrfProtection);
} else { } else {
console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n');
app.get('/csrf-token', (req, res) => { app.get('/csrf-token', (req, res) => {

View File

@ -23,6 +23,7 @@ router.post('/logout', async (request, response) => {
} }
request.session.handle = null; request.session.handle = null;
request.session.csrfToken = null;
request.session = null; request.session = null;
return response.sendStatus(204); return response.sendStatus(204);
} catch (error) { } catch (error) {