- Implemented a comprehensive health check utility to monitor system dependencies including NocoDB, SMTP, Represent API, disk space, and memory usage. - Created a logger utility using Winston for structured logging with daily rotation and various log levels. - Developed a metrics utility using Prometheus client to track application performance metrics such as email sends, HTTP requests, and user activity. - Added a backup script for automated backups of NocoDB data, uploaded files, and environment configurations with optional S3 support. - Introduced a toggle script to switch between development (MailHog) and production (ProtonMail) SMTP configurations.
105 lines
2.6 KiB
JavaScript
105 lines
2.6 KiB
JavaScript
const csrf = require('csurf');
|
|
const logger = require('../utils/logger');
|
|
|
|
// Create CSRF protection middleware
|
|
const csrfProtection = csrf({
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production' && process.env.HTTPS === 'true',
|
|
sameSite: 'strict',
|
|
maxAge: 3600000 // 1 hour
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Middleware to handle CSRF token errors
|
|
*/
|
|
const csrfErrorHandler = (err, req, res, next) => {
|
|
if (err.code === 'EBADCSRFTOKEN') {
|
|
logger.warn('CSRF token validation failed', {
|
|
ip: req.ip,
|
|
path: req.path,
|
|
method: req.method,
|
|
userAgent: req.get('user-agent')
|
|
});
|
|
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Invalid CSRF token',
|
|
message: 'Your session has expired or the request is invalid. Please refresh the page and try again.'
|
|
});
|
|
}
|
|
|
|
next(err);
|
|
};
|
|
|
|
/**
|
|
* Middleware to inject CSRF token into response
|
|
* Adds csrfToken to all JSON responses and as a header
|
|
*/
|
|
const injectCsrfToken = (req, res, next) => {
|
|
// Add token to response locals for template rendering
|
|
res.locals.csrfToken = req.csrfToken();
|
|
|
|
// Override json method to automatically include CSRF token
|
|
const originalJson = res.json.bind(res);
|
|
res.json = function(data) {
|
|
if (data && typeof data === 'object' && !data.csrfToken) {
|
|
data.csrfToken = res.locals.csrfToken;
|
|
}
|
|
return originalJson(data);
|
|
};
|
|
|
|
next();
|
|
};
|
|
|
|
/**
|
|
* Skip CSRF protection for specific routes (e.g., webhooks, public APIs)
|
|
*/
|
|
const csrfExemptRoutes = [
|
|
'/api/health',
|
|
'/api/metrics',
|
|
'/api/config',
|
|
'/api/auth/login', // Login uses credentials for authentication
|
|
'/api/auth/session', // Session check is read-only
|
|
'/api/representatives/postal/', // Read-only operation
|
|
'/api/campaigns/public' // Public read operations
|
|
];
|
|
|
|
const conditionalCsrfProtection = (req, res, next) => {
|
|
// Skip CSRF for exempt routes
|
|
const isExempt = csrfExemptRoutes.some(route => req.path.startsWith(route));
|
|
|
|
// Skip CSRF for GET, HEAD, OPTIONS (safe methods)
|
|
const isSafeMethod = ['GET', 'HEAD', 'OPTIONS'].includes(req.method);
|
|
|
|
if (isExempt || isSafeMethod) {
|
|
return next();
|
|
}
|
|
|
|
// Apply CSRF protection for state-changing operations
|
|
csrfProtection(req, res, (err) => {
|
|
if (err) {
|
|
return csrfErrorHandler(err, req, res, next);
|
|
}
|
|
injectCsrfToken(req, res, next);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Helper to get CSRF token for client-side use
|
|
*/
|
|
const getCsrfToken = (req, res) => {
|
|
res.json({
|
|
csrfToken: req.csrfToken()
|
|
});
|
|
};
|
|
|
|
module.exports = {
|
|
csrfProtection,
|
|
csrfErrorHandler,
|
|
injectCsrfToken,
|
|
conditionalCsrfProtection,
|
|
getCsrfToken
|
|
};
|