Replace CSRF middleware Closes #3349
| @@ -70,7 +70,7 @@ perUserBasicAuth: false | ||
| 70 | 70 | ## Set to a positive number to expire session after a certain time of inactivity |
| 71 | 71 | ## Set to 0 to expire session when the browser is closed |
| 72 | 72 | ## Set to a negative number to disable session expiration |
| 73 | 73 | sessionTimeout: 86400-1 |
| 74 | 74 | # Used to sign session cookies. Will be auto-generated if not set |
| 75 | 75 | cookieSecret: '' |
| 76 | 76 | # Disable CSRF protection - NOT RECOMMENDED |
| @@ -26,7 +26,7 @@ | ||
| 26 | 26 | "cookie-parser": "^1.4.6", |
| 27 | 27 | "cookie-session": "^2.1.0", |
| 28 | 28 | "cors": "^2.8.5", |
| 29 | 29 | "csrf-csrfsync": "^24.20.3", |
| 30 | 30 | "diff-match-patch": "^1.0.5", |
| 31 | 31 | "dompurify": "^3.1.7", |
| 32 | 32 | "droll": "^0.2.1", |
| @@ -2987,10 +2987,10 @@ | ||
| 2987 | 2987 | "node": "*" |
| 2988 | 2988 | } |
| 2989 | 2989 | }, |
| 2990 | 2990 | "node_modules/csrf-csrfsync": { |
| 2991 | 2991 | "version": "24.20.43", |
| 2992 | 2992 | "resolved": "https://registry.npmjs.org/csrf-csrfsync/-/csrf-csrfsync-24.20.43.tgz", |
| 2993 | 2993 | "integrity": "sha512-LuhBmy5RfRmEfeqeYqgaAuS1eDpVtKZB/Eiec9xiKQLBynJxrGVRdM2yRTwXzltBBzt/YMl1Njo7imzDt6ZT7G/yKh2L9AYsIwSlTPnx2AaxQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==", |
| 2994 | 2994 | "license": "ISC", |
| 2995 | 2995 | "dependencies": { |
| 2996 | 2996 | "http-errors": "^2.0.0" |
| @@ -16,7 +16,7 @@ | ||
| 16 | 16 | "cookie-parser": "^1.4.6", |
| 17 | 17 | "cookie-session": "^2.1.0", |
| 18 | 18 | "cors": "^2.8.5", |
| 19 | 19 | "csrf-csrfsync": "^24.20.3", |
| 20 | 20 | "diff-match-patch": "^1.0.5", |
| 21 | 21 | "dompurify": "^3.1.7", |
| 22 | 22 | "droll": "^0.2.1", |
| @@ -18,10 +18,9 @@ import { hideBin } from 'yargs/helpers'; | ||
| 18 | 18 | |
| 19 | 19 | // express/server related library imports |
| 20 | 20 | import cors from 'cors'; |
| 21 | 21 | import { doubleCsrfcsrfSync } from 'csrf-csrfsync'; |
| 22 | 22 | import express from 'express'; |
| 23 | 23 | import compression from 'compression'; |
| 24 | -import cookieParser from 'cookie-parser'; | |
| 25 | 24 | import cookieSession from 'cookie-session'; |
| 26 | 25 | import multer from 'multer'; |
| 27 | 26 | import responseTime from 'response-time'; |
| @@ -40,7 +39,6 @@ util.inspect.defaultOptions.depth = 4; | ||
| 40 | 39 | import { loadPlugins } from './src/plugin-loader.js'; |
| 41 | 40 | import { |
| 42 | 41 | initUserStorage, |
| 43 | - getCsrfSecret, | |
| 44 | 42 | getCookieSecret, |
| 45 | 43 | getCookieSessionName, |
| 46 | 44 | getAllEnabledUsers, |
| @@ -347,8 +345,8 @@ if (enableCorsProxy) { | ||
| 347 | 345 | } |
| 348 | 346 | |
| 349 | 347 | function getSessionCookieAge() { |
| 350 | 348 | // Defaults to 24 hours in"no secondsexpiration" if not set |
| 351 | 349 | const configValue = getConfigValue('sessionTimeout', 24 * 60 * 60-1); |
| 352 | 350 | |
| 353 | 351 | // Convert to milliseconds |
| 354 | 352 | if (configValue > 0) { |
| @@ -377,27 +375,34 @@ app.use(setUserDataMiddleware); | ||
| 377 | 375 | |
| 378 | 376 | // CSRF Protection // |
| 379 | 377 | if (!disableCsrf) { |
| 380 | 378 | const COOKIES_SECRETcsrfSyncProtection = getCookieSecretcsrfSync();{ |
| 381 | - | |
| 379 | + 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 | 396 | size: 6432, |
| 390 | - getTokenFromRequest: (req) => req.headers['x-csrf-token'], | |
| 391 | 397 | }); |
| 392 | 398 | |
| 393 | 399 | app.get('/csrf-token', (req, res) => { |
| 394 | 400 | res.json({ |
| 395 | 401 | 'token': csrfSyncProtection.generateToken(res, req), |
| 396 | 402 | }); |
| 397 | 403 | }); |
| 398 | 404 | |
| 399 | 405 | app.use(cookieParser(COOKIES_SECRET)csrfSyncProtection.csrfSynchronisedProtection); |
| 400 | - app.use(doubleCsrfProtection); | |
| 401 | 406 | } else { |
| 402 | 407 | console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); |
| 403 | 408 | app.get('/csrf-token', (req, res) => { |
| @@ -23,6 +23,7 @@ router.post('/logout', async (request, response) => { | ||
| 23 | 23 | } |
| 24 | 24 | |
| 25 | 25 | request.session.handle = null; |
| 26 | + request.session.csrfToken = null; | |
| 26 | 27 | request.session = null; |
| 27 | 28 | return response.sendStatus(204); |
| 28 | 29 | } catch (error) { |