264 lines
7.8 KiB
JavaScript
264 lines
7.8 KiB
JavaScript
const AuditLog = require('../models/AuditLog');
|
|
const logger = require('../utils/logger');
|
|
|
|
/**
|
|
* Get all audit logs with pagination and filtering
|
|
*/
|
|
const getAuditLogs = async (req, res) => {
|
|
try {
|
|
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 50));
|
|
const skip = (page - 1) * limit;
|
|
|
|
// Build filter
|
|
const filter = {};
|
|
if (req.query.action) {
|
|
filter.action = req.query.action;
|
|
}
|
|
if (req.query.resource) {
|
|
filter.resource = req.query.resource;
|
|
}
|
|
if (req.query.adminUsername) {
|
|
filter.adminUsername = req.query.adminUsername;
|
|
}
|
|
if (req.query.success !== undefined) {
|
|
filter.success = req.query.success === 'true';
|
|
}
|
|
|
|
// Date range filter
|
|
if (req.query.startDate || req.query.endDate) {
|
|
filter.createdAt = {};
|
|
if (req.query.startDate) {
|
|
filter.createdAt.$gte = new Date(req.query.startDate);
|
|
}
|
|
if (req.query.endDate) {
|
|
filter.createdAt.$lte = new Date(req.query.endDate);
|
|
}
|
|
}
|
|
|
|
const [logs, total] = await Promise.all([
|
|
AuditLog.find(filter)
|
|
.sort({ createdAt: -1 })
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.populate('adminId', 'username')
|
|
.select('-__v'),
|
|
AuditLog.countDocuments(filter)
|
|
]);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: logs,
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
total,
|
|
pages: Math.ceil(total / limit)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Audit-Logs:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Audit-Logs'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get audit logs for a specific resource
|
|
*/
|
|
const getResourceAuditLogs = async (req, res) => {
|
|
try {
|
|
const { resource, resourceId } = req.params;
|
|
|
|
const logs = await AuditLog.find({ resource, resourceId })
|
|
.sort({ createdAt: -1 })
|
|
.limit(50)
|
|
.populate('adminId', 'username')
|
|
.select('-__v');
|
|
|
|
res.json({
|
|
success: true,
|
|
data: logs,
|
|
count: logs.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Resource-Audit-Logs:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Resource-Audit-Logs'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get admin activity summary
|
|
*/
|
|
const getAdminActivity = async (req, res) => {
|
|
try {
|
|
const { adminId } = req.params;
|
|
|
|
const stats = await AuditLog.aggregate([
|
|
{ $match: { adminId: require('mongoose').Types.ObjectId(adminId) } },
|
|
{
|
|
$group: {
|
|
_id: '$action',
|
|
count: { $sum: 1 }
|
|
}
|
|
},
|
|
{ $sort: { count: -1 } }
|
|
]);
|
|
|
|
const recentActivity = await AuditLog.find({ adminId })
|
|
.sort({ createdAt: -1 })
|
|
.limit(20)
|
|
.select('-__v');
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
stats,
|
|
recentActivity
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Admin-Aktivität:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Admin-Aktivität'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Aggregate statistics: counts by action, resource, admin, success/fail, daily activity
|
|
*/
|
|
const getAuditStats = async (req, res) => {
|
|
try {
|
|
const days = Math.min(365, Math.max(1, parseInt(req.query.days) || 30));
|
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
|
|
const [byAction, byAdmin, byResource, bySuccess, dailyActivity] = await Promise.all([
|
|
AuditLog.aggregate([
|
|
{ $match: { createdAt: { $gte: since } } },
|
|
{ $group: { _id: '$action', count: { $sum: 1 } } },
|
|
{ $sort: { count: -1 } }
|
|
]),
|
|
AuditLog.aggregate([
|
|
{ $match: { createdAt: { $gte: since } } },
|
|
{ $group: { _id: '$adminUsername', count: { $sum: 1 } } },
|
|
{ $sort: { count: -1 } },
|
|
{ $limit: 10 }
|
|
]),
|
|
AuditLog.aggregate([
|
|
{ $match: { createdAt: { $gte: since } } },
|
|
{ $group: { _id: '$resource', count: { $sum: 1 } } },
|
|
{ $sort: { count: -1 } }
|
|
]),
|
|
AuditLog.aggregate([
|
|
{ $match: { createdAt: { $gte: since } } },
|
|
{ $group: { _id: '$success', count: { $sum: 1 } } }
|
|
]),
|
|
AuditLog.aggregate([
|
|
{ $match: { createdAt: { $gte: since } } },
|
|
{ $group: {
|
|
_id: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt' } },
|
|
count: { $sum: 1 },
|
|
failed: { $sum: { $cond: [{ $eq: ['$success', false] }, 1, 0] } }
|
|
}},
|
|
{ $sort: { _id: 1 } }
|
|
])
|
|
]);
|
|
|
|
const total = byAction.reduce((s, a) => s + a.count, 0);
|
|
const failed = bySuccess.find(s => s._id === false)?.count || 0;
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
total,
|
|
failed,
|
|
days,
|
|
since,
|
|
byAction: byAction.map(a => ({ action: a._id, count: a.count })),
|
|
byAdmin: byAdmin.map(a => ({ admin: a._id || 'unknown', count: a.count })),
|
|
byResource: byResource.map(a => ({ resource: a._id, count: a.count })),
|
|
dailyActivity: dailyActivity.map(a => ({ date: a._id, count: a.count, failed: a.failed }))
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Audit-Statistiken:', error);
|
|
res.status(500).json({ success: false, message: 'Fehler beim Abrufen der Audit-Statistiken' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Export audit logs as CSV (respects same filters as getAuditLogs)
|
|
*/
|
|
const exportAuditLogs = async (req, res) => {
|
|
try {
|
|
const filter = {};
|
|
if (req.query.action) filter.action = req.query.action;
|
|
if (req.query.resource) filter.resource = req.query.resource;
|
|
if (req.query.adminUsername) filter.adminUsername = req.query.adminUsername;
|
|
if (req.query.success !== undefined) filter.success = req.query.success === 'true';
|
|
if (req.query.startDate || req.query.endDate) {
|
|
filter.createdAt = {};
|
|
if (req.query.startDate) filter.createdAt.$gte = new Date(req.query.startDate);
|
|
if (req.query.endDate) filter.createdAt.$lte = new Date(req.query.endDate);
|
|
}
|
|
|
|
const logs = await AuditLog.find(filter)
|
|
.sort({ createdAt: -1 })
|
|
.limit(10000)
|
|
.lean();
|
|
|
|
const escapeCell = (val) => {
|
|
if (val == null) return '';
|
|
const str = String(val);
|
|
return str.includes(',') || str.includes('"') || str.includes('\n')
|
|
? `"${str.replace(/"/g, '""')}"` : str;
|
|
};
|
|
|
|
const header = [
|
|
'Zeitstempel', 'Aktion', 'Ressource', 'Ressourcen-Name', 'Admin',
|
|
'IP-Adresse', 'Methode', 'Pfad', 'Status-Code', 'Dauer (ms)',
|
|
'Erfolgreich', 'Fehlermeldung', 'Geänderte Felder', 'Metadaten'
|
|
].join(',');
|
|
|
|
const rows = logs.map(log => [
|
|
escapeCell(new Date(log.createdAt).toLocaleString('de-DE')),
|
|
escapeCell(log.action),
|
|
escapeCell(log.resource),
|
|
escapeCell(log.resourceName),
|
|
escapeCell(log.adminUsername),
|
|
escapeCell(log.ipAddress),
|
|
escapeCell(log.requestMethod),
|
|
escapeCell(log.requestPath),
|
|
escapeCell(log.statusCode),
|
|
escapeCell(log.duration),
|
|
log.success ? 'Ja' : 'Nein',
|
|
escapeCell(log.errorMessage),
|
|
escapeCell(log.changes ? Object.keys(log.changes).join(', ') : ''),
|
|
escapeCell(log.metadata ? JSON.stringify(log.metadata) : '')
|
|
].join(','));
|
|
|
|
const filename = `audit-logs_${new Date().toISOString().slice(0, 10)}.csv`;
|
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
res.send('\uFEFF' + [header, ...rows].join('\n'));
|
|
} catch (error) {
|
|
logger.error('Fehler beim Exportieren der Audit-Logs:', error);
|
|
res.status(500).json({ success: false, message: 'Fehler beim Exportieren der Audit-Logs' });
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
getAuditLogs,
|
|
getResourceAuditLogs,
|
|
getAdminActivity,
|
|
getAuditStats,
|
|
exportAuditLogs
|
|
};
|