Initial commit
Dieser Commit ist enthalten in:
125
backend/middleware/csrf.js
Normale Datei
125
backend/middleware/csrf.js
Normale Datei
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren