Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
364 lines
9.4 KiB
JavaScript
364 lines
9.4 KiB
JavaScript
import express from 'express';
|
|
import { AuditLog } from '../models/AuditLog.js';
|
|
import { AuditService } from '../services/auditService.js';
|
|
import { auth } from '../utils/auth.js';
|
|
import { metrics } from '../utils/metrics.js';
|
|
import { logger } from '../utils/logger.js';
|
|
|
|
const router = express.Router();
|
|
const auditService = new AuditService();
|
|
|
|
/**
|
|
* Search audit logs
|
|
*/
|
|
router.get('/audit-logs', auth(), async (req, res) => {
|
|
try {
|
|
const {
|
|
accountId,
|
|
userId,
|
|
category,
|
|
action,
|
|
resource,
|
|
startDate,
|
|
endDate,
|
|
page = 1,
|
|
limit = 50
|
|
} = req.query;
|
|
|
|
const criteria = {
|
|
accountId: accountId || req.user.accountId,
|
|
userId,
|
|
category,
|
|
action,
|
|
resource,
|
|
startDate: startDate ? new Date(startDate) : undefined,
|
|
endDate: endDate ? new Date(endDate) : undefined,
|
|
page: parseInt(page),
|
|
limit: parseInt(limit)
|
|
};
|
|
|
|
const logs = await auditService.searchLogs(criteria);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: logs,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
hasMore: logs.length === parseInt(limit)
|
|
}
|
|
});
|
|
|
|
metrics.recordApiCall('audit.search', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to search audit logs:', error);
|
|
metrics.recordApiCall('audit.search', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to search audit logs'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get audit trail for a resource
|
|
*/
|
|
router.get('/audit-trail/:resourceType/:resourceId', auth(), async (req, res) => {
|
|
try {
|
|
const { resourceType, resourceId } = req.params;
|
|
|
|
const trail = await auditService.getAuditTrail(resourceId, resourceType);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: trail,
|
|
count: trail.length
|
|
});
|
|
|
|
metrics.recordApiCall('audit.trail', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to get audit trail:', error);
|
|
metrics.recordApiCall('audit.trail', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get audit trail'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Generate compliance report
|
|
*/
|
|
router.post('/audit-reports/compliance', auth(), async (req, res) => {
|
|
try {
|
|
const { startDate, endDate } = req.body;
|
|
|
|
if (!startDate || !endDate) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Start date and end date are required'
|
|
});
|
|
}
|
|
|
|
const report = await auditService.generateComplianceReport(
|
|
req.user.accountId,
|
|
new Date(startDate),
|
|
new Date(endDate)
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: report
|
|
});
|
|
|
|
metrics.recordApiCall('audit.compliance_report', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to generate compliance report:', error);
|
|
metrics.recordApiCall('audit.compliance_report', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to generate compliance report'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Detect anomalies
|
|
*/
|
|
router.get('/audit-anomalies', auth(), async (req, res) => {
|
|
try {
|
|
const { window = 3600000 } = req.query; // Default 1 hour
|
|
|
|
const anomalies = await auditService.detectAnomalies(
|
|
req.user.accountId,
|
|
parseInt(window)
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: anomalies,
|
|
count: anomalies.length,
|
|
window: parseInt(window)
|
|
});
|
|
|
|
metrics.recordApiCall('audit.anomalies', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to detect anomalies:', error);
|
|
metrics.recordApiCall('audit.anomalies', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to detect anomalies'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get activity summary
|
|
*/
|
|
router.get('/audit-summary', auth(), async (req, res) => {
|
|
try {
|
|
const { days = 7 } = req.query;
|
|
|
|
const summary = await AuditLog.getActivitySummary(
|
|
req.user.accountId,
|
|
parseInt(days)
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: summary,
|
|
period: {
|
|
days: parseInt(days),
|
|
startDate: new Date(Date.now() - parseInt(days) * 24 * 60 * 60 * 1000),
|
|
endDate: new Date()
|
|
}
|
|
});
|
|
|
|
metrics.recordApiCall('audit.summary', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to get activity summary:', error);
|
|
metrics.recordApiCall('audit.summary', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get activity summary'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get audit log by ID
|
|
*/
|
|
router.get('/audit-logs/:auditId', auth(), async (req, res) => {
|
|
try {
|
|
const { auditId } = req.params;
|
|
|
|
const log = await AuditLog.findOne({ auditId });
|
|
if (!log) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Audit log not found'
|
|
});
|
|
}
|
|
|
|
// Check access permissions
|
|
if (log.accountId !== req.user.accountId) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: log
|
|
});
|
|
|
|
metrics.recordApiCall('audit.get', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to get audit log:', error);
|
|
metrics.recordApiCall('audit.get', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get audit log'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Export audit logs
|
|
*/
|
|
router.post('/audit-export', auth(), async (req, res) => {
|
|
try {
|
|
const {
|
|
format = 'json',
|
|
startDate,
|
|
endDate,
|
|
category,
|
|
compress = false
|
|
} = req.body;
|
|
|
|
const criteria = {
|
|
accountId: req.user.accountId,
|
|
startDate: startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
|
endDate: endDate ? new Date(endDate) : new Date(),
|
|
category
|
|
};
|
|
|
|
const logs = await auditService.searchLogs(criteria);
|
|
|
|
let exportData;
|
|
let contentType;
|
|
let filename = `audit_export_${Date.now()}`;
|
|
|
|
switch (format) {
|
|
case 'csv':
|
|
exportData = convertToCSV(logs);
|
|
contentType = 'text/csv';
|
|
filename += '.csv';
|
|
break;
|
|
case 'json':
|
|
default:
|
|
exportData = JSON.stringify(logs, null, 2);
|
|
contentType = 'application/json';
|
|
filename += '.json';
|
|
break;
|
|
}
|
|
|
|
if (compress) {
|
|
// In production, implement compression
|
|
filename += '.gz';
|
|
contentType = 'application/gzip';
|
|
}
|
|
|
|
res.setHeader('Content-Type', contentType);
|
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
res.send(exportData);
|
|
|
|
metrics.recordApiCall('audit.export', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to export audit logs:', error);
|
|
metrics.recordApiCall('audit.export', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to export audit logs'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Create manual audit log entry
|
|
*/
|
|
router.post('/audit-logs', auth(), async (req, res) => {
|
|
try {
|
|
const eventData = {
|
|
...req.body,
|
|
accountId: req.user.accountId,
|
|
actor: {
|
|
type: 'user',
|
|
id: req.user.id,
|
|
ip: req.ip,
|
|
userAgent: req.get('user-agent')
|
|
}
|
|
};
|
|
|
|
const auditLog = await auditService.logEvent(eventData);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: auditLog
|
|
});
|
|
|
|
metrics.recordApiCall('audit.create', 'success');
|
|
} catch (error) {
|
|
logger.error('Failed to create audit log:', error);
|
|
metrics.recordApiCall('audit.create', 'error');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to create audit log'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Helper function to convert to CSV
|
|
*/
|
|
function convertToCSV(logs) {
|
|
if (logs.length === 0) return '';
|
|
|
|
const headers = [
|
|
'Timestamp',
|
|
'Audit ID',
|
|
'Action',
|
|
'Category',
|
|
'Resource',
|
|
'Resource ID',
|
|
'Actor Type',
|
|
'Actor ID',
|
|
'Result',
|
|
'IP Address'
|
|
];
|
|
|
|
const rows = logs.map(log => [
|
|
log.timestamp,
|
|
log.auditId,
|
|
log.action,
|
|
log.category,
|
|
log.resource || '',
|
|
log.resourceId || '',
|
|
log.actor.type,
|
|
log.actor.id,
|
|
log.result.status,
|
|
log.actor.ip || ''
|
|
]);
|
|
|
|
const csvContent = [
|
|
headers.join(','),
|
|
...rows.map(row => row.map(cell =>
|
|
typeof cell === 'string' && cell.includes(',') ? `"${cell}"` : cell
|
|
).join(','))
|
|
].join('\n');
|
|
|
|
return csvContent;
|
|
}
|
|
|
|
export default router; |