69 lines
1.9 KiB
JavaScript
69 lines
1.9 KiB
JavaScript
const crypto = require('crypto');
|
|
|
|
const { logger } = require('../utils/logger');
|
|
|
|
const CSRF_COOKIE_NAME = 'csrf_token';
|
|
const CSRF_COOKIE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
|
|
function generateCsrfToken() {
|
|
return crypto.randomBytes(32).toString('hex');
|
|
}
|
|
|
|
function setCsrfCookie(res, token, secureCookies) {
|
|
res.cookie(CSRF_COOKIE_NAME, token, {
|
|
httpOnly: false,
|
|
secure: secureCookies,
|
|
sameSite: secureCookies ? 'strict' : 'lax',
|
|
maxAge: CSRF_COOKIE_MAX_AGE_MS
|
|
});
|
|
}
|
|
|
|
function createCsrfCookieMiddleware({ secureCookies = false } = {}) {
|
|
return (req, res, next) => {
|
|
if (!req.cookies[CSRF_COOKIE_NAME]) {
|
|
setCsrfCookie(res, generateCsrfToken(), secureCookies);
|
|
}
|
|
next();
|
|
};
|
|
}
|
|
|
|
function tokensEqual(left, right) {
|
|
if (typeof left !== 'string' || typeof right !== 'string') return false;
|
|
const leftBuffer = Buffer.from(left);
|
|
const rightBuffer = Buffer.from(right);
|
|
return leftBuffer.length === rightBuffer.length && crypto.timingSafeEqual(leftBuffer, rightBuffer);
|
|
}
|
|
|
|
function csrfProtection(req, res, next) {
|
|
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
|
|
|
|
const hasCookieAuth = Boolean(req.cookies?.token || req.cookies?.refreshToken);
|
|
if (!hasCookieAuth) return next();
|
|
|
|
const cookieToken = req.cookies[CSRF_COOKIE_NAME];
|
|
const headerToken = req.headers['x-csrf-token'];
|
|
if (!tokensEqual(cookieToken, headerToken)) {
|
|
(req.log || logger).warn({
|
|
path: req.path,
|
|
hasCookieToken: Boolean(cookieToken),
|
|
hasHeaderToken: Boolean(headerToken)
|
|
}, 'CSRF validation failed');
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'CSRF 验证失败,请刷新页面后重试',
|
|
requestId: req.id
|
|
});
|
|
}
|
|
|
|
return next();
|
|
}
|
|
|
|
module.exports = {
|
|
CSRF_COOKIE_NAME,
|
|
createCsrfCookieMiddleware,
|
|
csrfProtection,
|
|
generateCsrfToken,
|
|
setCsrfCookie,
|
|
tokensEqual
|
|
};
|