fix(admin): CSRF cookie sameSite=false for Tor, auth cookie fix, async handlers, validation
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user