refactor: modularize backend security and observability
This commit is contained in:
68
backend/middleware/csrf.js
Normal file
68
backend/middleware/csrf.js
Normal file
@@ -0,0 +1,68 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user