98 lines
2.4 KiB
JavaScript
98 lines
2.4 KiB
JavaScript
const csrf = require("csrf");
|
|
const cookieParser = require("cookie-parser");
|
|
const logger = require("../utils/logger");
|
|
|
|
// Initialize CSRF token generator
|
|
const tokens = new csrf();
|
|
|
|
// Use persistent secret from environment variable to prevent token invalidation on restart
|
|
const secret = process.env.CSRF_SECRET;
|
|
|
|
if (!secret) {
|
|
const errorMsg = "CSRF_SECRET environment variable is required.";
|
|
logger.error(errorMsg);
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
if (secret.length < 32) {
|
|
const errorMsg = "CSRF_SECRET must be at least 32 characters for security";
|
|
logger.error(errorMsg);
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
// CSRF middleware using double submit cookie pattern
|
|
const csrfProtection = (req, res, next) => {
|
|
// Skip CSRF for safe methods
|
|
if (["GET", "HEAD", "OPTIONS"].includes(req.method)) {
|
|
return next();
|
|
}
|
|
|
|
// Get token from header or body
|
|
const token =
|
|
req.headers["x-csrf-token"] || req.body.csrfToken || req.query.csrfToken;
|
|
|
|
// Get token from cookie
|
|
const cookieToken = req.cookies && req.cookies["csrf-token"];
|
|
|
|
// Verify both tokens exist and match
|
|
if (!token || !cookieToken || token !== cookieToken) {
|
|
return res.status(403).json({
|
|
error: "Invalid CSRF token",
|
|
code: "CSRF_TOKEN_MISMATCH",
|
|
});
|
|
}
|
|
|
|
// Verify token is valid
|
|
if (!tokens.verify(secret, token)) {
|
|
return res.status(403).json({
|
|
error: "Invalid CSRF token",
|
|
code: "CSRF_TOKEN_INVALID",
|
|
});
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
// Middleware to generate and send CSRF token
|
|
const generateCSRFToken = (req, res, next) => {
|
|
const token = tokens.create(secret);
|
|
|
|
// Set token in cookie (httpOnly for security)
|
|
res.cookie("csrf-token", token, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV !== "dev",
|
|
sameSite: "strict",
|
|
maxAge: 60 * 60 * 1000, // 1 hour
|
|
});
|
|
|
|
// Also provide token in header for client to use
|
|
res.set("X-CSRF-Token", token);
|
|
|
|
// Make token available to response
|
|
res.locals.csrfToken = token;
|
|
|
|
next();
|
|
};
|
|
|
|
// Route to get CSRF token (for initial page loads)
|
|
const getCSRFToken = (req, res) => {
|
|
const token = tokens.create(secret);
|
|
|
|
res.cookie("csrf-token", token, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV !== "dev",
|
|
sameSite: "strict",
|
|
maxAge: 60 * 60 * 1000,
|
|
});
|
|
|
|
res.set("X-CSRF-Token", token);
|
|
res.status(204).send();
|
|
};
|
|
|
|
module.exports = {
|
|
csrfProtection,
|
|
generateCSRFToken,
|
|
getCSRFToken,
|
|
cookieParser: cookieParser(),
|
|
};
|