Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
479
backend/routes/security-monitor.js
Normal file
479
backend/routes/security-monitor.js
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/rbac');
|
||||
const { readLimiter } = require('../middleware/rateLimiter');
|
||||
const { db } = require('../database/db');
|
||||
const logger = require('../utils/logger');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
/**
|
||||
* Security Monitoring & Dependency Management
|
||||
* Provides comprehensive security status and vulnerability tracking
|
||||
*/
|
||||
|
||||
// Get comprehensive security status
|
||||
router.get('/status', authenticate, requirePermission('security.view_audit'), readLimiter, async (req, res) => {
|
||||
try {
|
||||
const securityStatus = {
|
||||
timestamp: new Date().toISOString(),
|
||||
dependencies: await checkDependencies(),
|
||||
vulnerabilities: await checkVulnerabilities(),
|
||||
securityHeaders: await checkSecurityHeaders(),
|
||||
auditSummary: await getAuditSummary(),
|
||||
systemHealth: await getSystemHealth()
|
||||
};
|
||||
|
||||
res.json(securityStatus);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching security status:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch security status' });
|
||||
}
|
||||
});
|
||||
|
||||
// Check dependencies for updates
|
||||
async function checkDependencies() {
|
||||
try {
|
||||
const backendPackage = JSON.parse(
|
||||
await fs.readFile(path.join(__dirname, '../package.json'), 'utf8')
|
||||
);
|
||||
|
||||
const frontendPackage = JSON.parse(
|
||||
await fs.readFile(path.join(__dirname, '../../frontend/package.json'), 'utf8')
|
||||
);
|
||||
|
||||
return {
|
||||
backend: {
|
||||
dependencies: Object.keys(backendPackage.dependencies || {}).length,
|
||||
devDependencies: Object.keys(backendPackage.devDependencies || {}).length,
|
||||
lastChecked: new Date().toISOString()
|
||||
},
|
||||
frontend: {
|
||||
dependencies: Object.keys(frontendPackage.dependencies || {}).length,
|
||||
devDependencies: Object.keys(frontendPackage.devDependencies || {}).length,
|
||||
lastChecked: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error checking dependencies:', error);
|
||||
return { error: 'Unable to check dependencies' };
|
||||
}
|
||||
}
|
||||
|
||||
// Check for known vulnerabilities
|
||||
async function checkVulnerabilities() {
|
||||
try {
|
||||
// Run npm audit in both backend and frontend
|
||||
const backendAudit = await runNpmAudit('backend');
|
||||
const frontendAudit = await runNpmAudit('frontend');
|
||||
|
||||
return {
|
||||
backend: backendAudit,
|
||||
frontend: frontendAudit,
|
||||
lastScanned: new Date().toISOString()
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error checking vulnerabilities:', error);
|
||||
return { error: 'Unable to scan for vulnerabilities' };
|
||||
}
|
||||
}
|
||||
|
||||
async function runNpmAudit(project) {
|
||||
try {
|
||||
const projectPath = project === 'backend'
|
||||
? path.join(__dirname, '..')
|
||||
: path.join(__dirname, '../../frontend');
|
||||
|
||||
const { stdout } = await execPromise(`cd ${projectPath} && npm audit --json`, {
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
const auditData = JSON.parse(stdout);
|
||||
|
||||
return {
|
||||
total: auditData.metadata?.vulnerabilities?.total || 0,
|
||||
critical: auditData.metadata?.vulnerabilities?.critical || 0,
|
||||
high: auditData.metadata?.vulnerabilities?.high || 0,
|
||||
moderate: auditData.metadata?.vulnerabilities?.moderate || 0,
|
||||
low: auditData.metadata?.vulnerabilities?.low || 0,
|
||||
info: auditData.metadata?.vulnerabilities?.info || 0
|
||||
};
|
||||
} catch (error) {
|
||||
// npm audit returns non-zero exit code when vulnerabilities are found
|
||||
if (error.stdout) {
|
||||
try {
|
||||
const auditData = JSON.parse(error.stdout);
|
||||
return {
|
||||
total: auditData.metadata?.vulnerabilities?.total || 0,
|
||||
critical: auditData.metadata?.vulnerabilities?.critical || 0,
|
||||
high: auditData.metadata?.vulnerabilities?.high || 0,
|
||||
moderate: auditData.metadata?.vulnerabilities?.moderate || 0,
|
||||
low: auditData.metadata?.vulnerabilities?.low || 0,
|
||||
info: auditData.metadata?.vulnerabilities?.info || 0
|
||||
};
|
||||
} catch {
|
||||
return { error: 'Unable to parse audit results' };
|
||||
}
|
||||
}
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Check security headers configuration
|
||||
async function checkSecurityHeaders() {
|
||||
return {
|
||||
helmet: {
|
||||
enabled: true,
|
||||
features: [
|
||||
'Content-Security-Policy',
|
||||
'X-Content-Type-Options',
|
||||
'X-Frame-Options',
|
||||
'X-XSS-Protection',
|
||||
'Strict-Transport-Security',
|
||||
'Referrer-Policy'
|
||||
]
|
||||
},
|
||||
csp: {
|
||||
enabled: true,
|
||||
mode: process.env.NODE_ENV === 'production' ? 'enforcing' : 'report-only'
|
||||
},
|
||||
cors: {
|
||||
enabled: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get security audit summary
|
||||
async function getAuditSummary() {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT
|
||||
action,
|
||||
result,
|
||||
COUNT(*) as count,
|
||||
MAX(timestamp) as last_occurrence
|
||||
FROM security_audit_log
|
||||
WHERE timestamp > datetime('now', '-7 days')
|
||||
GROUP BY action, result
|
||||
ORDER BY count DESC
|
||||
LIMIT 20`,
|
||||
[],
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
logger.error('Error fetching audit summary:', err);
|
||||
resolve({ error: 'Unable to fetch audit summary' });
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Get system health metrics
|
||||
async function getSystemHealth() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Promise.all([
|
||||
// Active sessions count
|
||||
new Promise((res) => {
|
||||
db.get('SELECT COUNT(*) as count FROM sessions WHERE expires_at > ?',
|
||||
[new Date().toISOString()],
|
||||
(err, row) => res(row?.count || 0)
|
||||
);
|
||||
}),
|
||||
// Failed login attempts in last hour
|
||||
new Promise((res) => {
|
||||
db.get(
|
||||
`SELECT COUNT(*) as count FROM security_audit_log
|
||||
WHERE action = 'login' AND result = 'failed'
|
||||
AND timestamp > datetime('now', '-1 hour')`,
|
||||
[],
|
||||
(err, row) => res(row?.count || 0)
|
||||
);
|
||||
}),
|
||||
// Locked accounts
|
||||
new Promise((res) => {
|
||||
db.get(
|
||||
'SELECT COUNT(*) as count FROM users WHERE locked_until > ?',
|
||||
[new Date().toISOString()],
|
||||
(err, row) => res(row?.count || 0)
|
||||
);
|
||||
}),
|
||||
// Total users
|
||||
new Promise((res) => {
|
||||
db.get('SELECT COUNT(*) as count FROM users', [], (err, row) => res(row?.count || 0));
|
||||
})
|
||||
]).then(([activeSessions, failedLogins, lockedAccounts, totalUsers]) => {
|
||||
resolve({
|
||||
activeSessions,
|
||||
failedLogins,
|
||||
lockedAccounts,
|
||||
totalUsers,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get detailed vulnerability report
|
||||
router.get('/vulnerabilities/detailed', authenticate, requirePermission('security.view_audit'), readLimiter, async (req, res) => {
|
||||
try {
|
||||
const backendPath = path.join(__dirname, '..');
|
||||
const frontendPath = path.join(__dirname, '../../frontend');
|
||||
|
||||
const backendAudit = await execPromise(`cd ${backendPath} && npm audit --json`, {
|
||||
timeout: 30000
|
||||
}).catch(e => ({ stdout: e.stdout }));
|
||||
|
||||
const frontendAudit = await execPromise(`cd ${frontendPath} && npm audit --json`, {
|
||||
timeout: 30000
|
||||
}).catch(e => ({ stdout: e.stdout }));
|
||||
|
||||
const backendData = JSON.parse(backendAudit.stdout || '{}');
|
||||
const frontendData = JSON.parse(frontendAudit.stdout || '{}');
|
||||
|
||||
res.json({
|
||||
backend: {
|
||||
vulnerabilities: backendData.vulnerabilities || {},
|
||||
metadata: backendData.metadata || {}
|
||||
},
|
||||
frontend: {
|
||||
vulnerabilities: frontendData.vulnerabilities || {},
|
||||
metadata: frontendData.metadata || {}
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching detailed vulnerabilities:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch vulnerability details' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get security audit log with filtering
|
||||
router.get('/audit-log', authenticate, requirePermission('security.view_audit'), readLimiter, async (req, res) => {
|
||||
try {
|
||||
const { action, result, userId, startDate, endDate, limit = 100, offset = 0 } = req.query;
|
||||
|
||||
let query = 'SELECT * FROM security_audit_log WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (action) {
|
||||
query += ' AND action = ?';
|
||||
params.push(action);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
query += ' AND result = ?';
|
||||
params.push(result);
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
query += ' AND user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
query += ' AND timestamp >= ?';
|
||||
params.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
query += ' AND timestamp <= ?';
|
||||
params.push(endDate);
|
||||
}
|
||||
|
||||
query += ' ORDER BY timestamp DESC LIMIT ? OFFSET ?';
|
||||
params.push(parseInt(limit), parseInt(offset));
|
||||
|
||||
db.all(query, params, (err, rows) => {
|
||||
if (err) {
|
||||
logger.error('Error fetching audit log:', err);
|
||||
return res.status(500).json({ error: 'Failed to fetch audit log' });
|
||||
}
|
||||
|
||||
// Get total count for pagination
|
||||
let countQuery = 'SELECT COUNT(*) as total FROM security_audit_log WHERE 1=1';
|
||||
const countParams = params.slice(0, -2); // Remove limit and offset
|
||||
|
||||
db.get(countQuery, countParams, (err, countRow) => {
|
||||
if (err) {
|
||||
logger.error('Error counting audit log:', err);
|
||||
return res.json({ logs: rows || [], total: 0 });
|
||||
}
|
||||
|
||||
res.json({
|
||||
logs: rows || [],
|
||||
total: countRow?.total || 0,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching audit log:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch audit log' });
|
||||
}
|
||||
});
|
||||
|
||||
// Export audit log
|
||||
router.get('/audit-log/export', authenticate, requirePermission('security.view_audit'), readLimiter, async (req, res) => {
|
||||
try {
|
||||
const { format = 'json', startDate, endDate } = req.query;
|
||||
|
||||
let query = 'SELECT * FROM security_audit_log WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (startDate) {
|
||||
query += ' AND timestamp >= ?';
|
||||
params.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
query += ' AND timestamp <= ?';
|
||||
params.push(endDate);
|
||||
}
|
||||
|
||||
query += ' ORDER BY timestamp DESC';
|
||||
|
||||
db.all(query, params, (err, rows) => {
|
||||
if (err) {
|
||||
logger.error('Error exporting audit log:', err);
|
||||
return res.status(500).json({ error: 'Failed to export audit log' });
|
||||
}
|
||||
|
||||
if (format === 'csv') {
|
||||
const csv = convertToCSV(rows);
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=security-audit-${Date.now()}.csv`);
|
||||
res.send(csv);
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=security-audit-${Date.now()}.json`);
|
||||
res.json(rows);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error exporting audit log:', error);
|
||||
res.status(500).json({ error: 'Failed to export audit log' });
|
||||
}
|
||||
});
|
||||
|
||||
function convertToCSV(data) {
|
||||
if (!data || data.length === 0) return '';
|
||||
|
||||
const headers = Object.keys(data[0]);
|
||||
const csvRows = [headers.join(',')];
|
||||
|
||||
for (const row of data) {
|
||||
const values = headers.map(header => {
|
||||
const value = row[header];
|
||||
return typeof value === 'string' && value.includes(',')
|
||||
? `"${value}"`
|
||||
: value;
|
||||
});
|
||||
csvRows.push(values.join(','));
|
||||
}
|
||||
|
||||
return csvRows.join('\n');
|
||||
}
|
||||
|
||||
// Get security recommendations
|
||||
router.get('/recommendations', authenticate, requirePermission('security.view_audit'), readLimiter, async (req, res) => {
|
||||
try {
|
||||
const recommendations = [];
|
||||
|
||||
// Check for locked accounts
|
||||
const lockedAccounts = await new Promise((resolve) => {
|
||||
db.get(
|
||||
'SELECT COUNT(*) as count FROM users WHERE locked_until > ?',
|
||||
[new Date().toISOString()],
|
||||
(err, row) => resolve(row?.count || 0)
|
||||
);
|
||||
});
|
||||
|
||||
if (lockedAccounts > 0) {
|
||||
recommendations.push({
|
||||
severity: 'warning',
|
||||
category: 'account_security',
|
||||
title: 'Locked Accounts',
|
||||
description: `${lockedAccounts} account(s) are currently locked due to failed login attempts`,
|
||||
action: 'Review locked accounts and consider unlocking legitimate users'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for users with old passwords
|
||||
const oldPasswords = await new Promise((resolve) => {
|
||||
db.all(
|
||||
`SELECT username, password_changed_at FROM users
|
||||
WHERE password_changed_at < datetime('now', '-90 days')`,
|
||||
[],
|
||||
(err, rows) => resolve(rows || [])
|
||||
);
|
||||
});
|
||||
|
||||
if (oldPasswords.length > 0) {
|
||||
recommendations.push({
|
||||
severity: 'info',
|
||||
category: 'password_policy',
|
||||
title: 'Old Passwords',
|
||||
description: `${oldPasswords.length} user(s) haven't changed their password in over 90 days`,
|
||||
action: 'Encourage users to update their passwords regularly'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for recent failed logins
|
||||
const recentFailures = await new Promise((resolve) => {
|
||||
db.get(
|
||||
`SELECT COUNT(*) as count FROM security_audit_log
|
||||
WHERE action = 'login' AND result = 'failed'
|
||||
AND timestamp > datetime('now', '-1 hour')`,
|
||||
[],
|
||||
(err, row) => resolve(row?.count || 0)
|
||||
);
|
||||
});
|
||||
|
||||
if (recentFailures > 10) {
|
||||
recommendations.push({
|
||||
severity: 'high',
|
||||
category: 'threat_detection',
|
||||
title: 'High Failed Login Rate',
|
||||
description: `${recentFailures} failed login attempts in the last hour`,
|
||||
action: 'Investigate potential brute-force attack'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for users without 2FA
|
||||
const no2FA = await new Promise((resolve) => {
|
||||
db.get(
|
||||
'SELECT COUNT(*) as count FROM users WHERE two_factor_secret IS NULL',
|
||||
[],
|
||||
(err, row) => resolve(row?.count || 0)
|
||||
);
|
||||
});
|
||||
|
||||
if (no2FA > 0) {
|
||||
recommendations.push({
|
||||
severity: 'warning',
|
||||
category: 'authentication',
|
||||
title: 'Two-Factor Authentication',
|
||||
description: `${no2FA} user(s) don't have 2FA enabled`,
|
||||
action: 'Encourage users to enable two-factor authentication'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
recommendations,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error generating recommendations:', error);
|
||||
res.status(500).json({ error: 'Failed to generate recommendations' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Add table
Add a link
Reference in a new issue