Files
rentall-app/backend/middleware/csrf.js
jackiettran cf97dffbfb MFA
2026-01-16 18:04:39 -05:00

97 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;
// 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(),
};