126 Zeilen
2.8 KiB
JavaScript
126 Zeilen
2.8 KiB
JavaScript
/**
|
|
* 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;
|