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 };