/** * TASKMATE - CSRF Schutz * ====================== * Cross-Site Request Forgery Schutz * * Vereinfachtes System: Token wird beim Login generiert und bleibt * für die gesamte Sitzung gültig (24 Stunden). */ const crypto = require('crypto'); const logger = require('../utils/logger'); // CSRF-Tokens speichern (in-memory, pro User) const csrfTokens = new Map(); // Token-Gültigkeit: 24 Stunden const TOKEN_VALIDITY = 24 * 60 * 60 * 1000; /** * CSRF-Token generieren */ function generateToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expires = Date.now() + TOKEN_VALIDITY; csrfTokens.set(userId, { token, expires }); cleanupExpiredTokens(); return token; } /** * CSRF-Token validieren */ function validateToken(userId, token) { const stored = csrfTokens.get(userId); if (!stored) { return false; } if (Date.now() > stored.expires) { csrfTokens.delete(userId); return false; } return stored.token === token; } /** * Abgelaufene Tokens aufräumen */ function cleanupExpiredTokens() { const now = Date.now(); for (const [userId, data] of csrfTokens.entries()) { if (now > data.expires) { csrfTokens.delete(userId); } } } // Regelmäßig aufräumen (alle 30 Minuten) setInterval(cleanupExpiredTokens, 30 * 60 * 1000); /** * Express Middleware */ function csrfProtection(req, res, next) { // GET-Anfragen sind sicher (lesen nur) if (req.method === 'GET') { return next(); } // User muss authentifiziert sein if (!req.user) { return next(); } const userId = req.user.id; // Token aus Header oder Body const token = req.headers['x-csrf-token'] || req.body?._csrf; // Wenn kein Token vom Client gesendet oder kein Token auf Server gespeichert if (!token || !csrfTokens.has(userId)) { const newToken = generateToken(userId); logger.info(`CSRF: Token missing or not stored for user ${userId}, generated new token`); return res.status(403).json({ error: 'CSRF-Token fehlt oder abgelaufen', csrfToken: newToken, code: 'CSRF_ERROR' }); } // Token validieren if (!validateToken(userId, token)) { const newToken = generateToken(userId); logger.info(`CSRF: Token mismatch for user ${userId}`); return res.status(403).json({ error: 'Ungültiges CSRF-Token', csrfToken: newToken, code: 'CSRF_ERROR' }); } // Token ist gültig - KEIN neuer Token generiert (bleibt für die Sitzung gleich) next(); } /** * Aktuellen Token für User abrufen (oder neuen generieren) */ function getTokenForUser(userId) { const stored = csrfTokens.get(userId); if (stored && Date.now() < stored.expires) { return stored.token; } return generateToken(userId); } module.exports = csrfProtection; module.exports.generateToken = generateToken; module.exports.getTokenForUser = getTokenForUser;