fix(admin): CSRF cookie sameSite=false for Tor, auth cookie fix, async handlers, validation

This commit is contained in:
NW
2026-07-08 12:08:08 +01:00
parent 046c40349d
commit 2b30bc4a91
7 changed files with 81 additions and 27 deletions

View File

@@ -13,11 +13,10 @@ export function csrfMiddleware(req, res, next) {
if (!token) {
token = generateToken();
res.cookie(CSRF_COOKIE, token, {
httpOnly: false,
sameSite: 'strict',
path: '/',
secure: process.env.NODE_ENV === 'production'
});
httpOnly: false,
sameSite: false,
path: '/'
});
}
res.locals.csrfToken = token;
next();
@@ -27,11 +26,16 @@ export function validateCsrf(req, res, next) {
// Only validate state-changing methods
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
// Skip multipart/form-data — multer hasn't parsed the body yet.
// Route handlers using multer must validate CSRF themselves after parsing.
const ct = req.headers['content-type'] || '';
if (ct.startsWith('multipart/form-data')) return next();
const token = req.body?._csrf || req.headers[CSRF_HEADER];
const cookieToken = req.cookies?.[CSRF_COOKIE];
if (!token || !cookieToken) {
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method }, 'CSRF token missing');
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method, hasBodyToken: !!token, hasCookieToken: !!cookieToken, contentType: ct }, 'CSRF token missing');
return res.status(403).send('CSRF token missing. Please reload the page.');
}
@@ -44,3 +48,28 @@ export function validateCsrf(req, res, next) {
next();
}
/**
* Validate CSRF token from req.body._csrf (for use inside multer handlers).
* Returns true if valid, false otherwise. Sends 403 response on failure.
*/
export function validateCsrfFromBody(req, res) {
const token = req.body?._csrf;
const cookieToken = req.cookies?.[CSRF_COOKIE];
if (!token || !cookieToken) {
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method }, 'CSRF token missing (multipart)');
res.status(403).send('CSRF token missing. Please reload the page.');
return false;
}
const provided = Buffer.from(token, 'utf8');
const expected = Buffer.from(cookieToken, 'utf8');
if (provided.length !== expected.length || !crypto.timingSafeEqual(provided, expected)) {
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method }, 'CSRF token mismatch (multipart)');
res.status(403).send('CSRF token invalid. Please reload the page.');
return false;
}
return true;
}