Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
218
backend/routes/csp.js
Normal file
218
backend/routes/csp.js
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
const logger = require('../utils/logger');
|
||||
const { db } = require('../database/db');
|
||||
|
||||
// Store CSP violations in database
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS csp_violations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
document_uri TEXT,
|
||||
violated_directive TEXT,
|
||||
blocked_uri TEXT,
|
||||
source_file TEXT,
|
||||
line_number INTEGER,
|
||||
column_number INTEGER,
|
||||
user_agent TEXT,
|
||||
ip_address TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// CSP violation reporting endpoint (no auth required)
|
||||
router.post('/report', express.json({ type: 'application/csp-report' }), (req, res) => {
|
||||
const report = req.body['csp-report'];
|
||||
const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
const userAgent = req.headers['user-agent'];
|
||||
|
||||
if (!report) {
|
||||
return res.status(400).json({ error: 'Invalid CSP report' });
|
||||
}
|
||||
|
||||
logger.warn('CSP Violation:', {
|
||||
documentUri: report['document-uri'],
|
||||
violatedDirective: report['violated-directive'],
|
||||
blockedUri: report['blocked-uri'],
|
||||
sourceFile: report['source-file'],
|
||||
lineNumber: report['line-number'],
|
||||
columnNumber: report['column-number'],
|
||||
ip,
|
||||
userAgent
|
||||
});
|
||||
|
||||
// Store in database
|
||||
db.run(
|
||||
`INSERT INTO csp_violations
|
||||
(document_uri, violated_directive, blocked_uri, source_file, line_number, column_number, user_agent, ip_address)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
report['document-uri'],
|
||||
report['violated-directive'],
|
||||
report['blocked-uri'],
|
||||
report['source-file'],
|
||||
report['line-number'],
|
||||
report['column-number'],
|
||||
userAgent,
|
||||
ip
|
||||
],
|
||||
(err) => {
|
||||
if (err) {
|
||||
logger.error('Failed to store CSP violation:', err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// Get CSP violations (admin only)
|
||||
router.get('/violations', authenticate, requireAdmin, (req, res) => {
|
||||
const { limit = 100, offset = 0 } = req.query;
|
||||
|
||||
db.all(
|
||||
`SELECT * FROM csp_violations
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[parseInt(limit), parseInt(offset)],
|
||||
(err, violations) => {
|
||||
if (err) {
|
||||
logger.error('Failed to fetch CSP violations:', err);
|
||||
return res.status(500).json({ error: 'Failed to fetch violations' });
|
||||
}
|
||||
|
||||
// Get total count
|
||||
db.get('SELECT COUNT(*) as total FROM csp_violations', (countErr, countResult) => {
|
||||
if (countErr) {
|
||||
logger.error('Failed to count CSP violations:', countErr);
|
||||
return res.status(500).json({ error: 'Failed to count violations' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
violations,
|
||||
total: countResult.total,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Get CSP violation statistics (admin only)
|
||||
router.get('/stats', authenticate, requireAdmin, (req, res) => {
|
||||
const { days = 7 } = req.query;
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - parseInt(days));
|
||||
|
||||
Promise.all([
|
||||
// Total violations
|
||||
new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
'SELECT COUNT(*) as total FROM csp_violations WHERE created_at >= ?',
|
||||
[cutoffDate.toISOString()],
|
||||
(err, row) => err ? reject(err) : resolve(row.total)
|
||||
);
|
||||
}),
|
||||
// By directive
|
||||
new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT violated_directive, COUNT(*) as count
|
||||
FROM csp_violations
|
||||
WHERE created_at >= ?
|
||||
GROUP BY violated_directive
|
||||
ORDER BY count DESC
|
||||
LIMIT 10`,
|
||||
[cutoffDate.toISOString()],
|
||||
(err, rows) => err ? reject(err) : resolve(rows)
|
||||
);
|
||||
}),
|
||||
// By blocked URI
|
||||
new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT blocked_uri, COUNT(*) as count
|
||||
FROM csp_violations
|
||||
WHERE created_at >= ?
|
||||
GROUP BY blocked_uri
|
||||
ORDER BY count DESC
|
||||
LIMIT 10`,
|
||||
[cutoffDate.toISOString()],
|
||||
(err, rows) => err ? reject(err) : resolve(rows)
|
||||
);
|
||||
}),
|
||||
// Recent violations
|
||||
new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT * FROM csp_violations
|
||||
WHERE created_at >= ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 20`,
|
||||
[cutoffDate.toISOString()],
|
||||
(err, rows) => err ? reject(err) : resolve(rows)
|
||||
);
|
||||
})
|
||||
])
|
||||
.then(([total, byDirective, byUri, recent]) => {
|
||||
res.json({
|
||||
total,
|
||||
byDirective,
|
||||
byUri,
|
||||
recent,
|
||||
days: parseInt(days)
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error('Failed to fetch CSP stats:', err);
|
||||
res.status(500).json({ error: 'Failed to fetch statistics' });
|
||||
});
|
||||
});
|
||||
|
||||
// Clear old CSP violations (admin only)
|
||||
router.delete('/violations', authenticate, requireAdmin, (req, res) => {
|
||||
const { days = 30 } = req.query;
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - parseInt(days));
|
||||
|
||||
db.run(
|
||||
'DELETE FROM csp_violations WHERE created_at < ?',
|
||||
[cutoffDate.toISOString()],
|
||||
function(err) {
|
||||
if (err) {
|
||||
logger.error('Failed to delete old CSP violations:', err);
|
||||
return res.status(500).json({ error: 'Failed to delete violations' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: 'Old violations cleared',
|
||||
deleted: this.changes
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Get current CSP policy (authenticated users)
|
||||
router.get('/policy', authenticate, (req, res) => {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
res.json({
|
||||
mode: isProduction ? 'enforce' : 'report-only',
|
||||
policy: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://www.gstatic.com"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
|
||||
fontSrc: ["'self'", "data:", "https://fonts.gstatic.com"],
|
||||
imgSrc: ["'self'", "data:", "blob:", "https:", "http:"],
|
||||
mediaSrc: ["'self'", "blob:", "data:", "mediastream:", "https:", "http:", "*"],
|
||||
connectSrc: ["'self'", "https:", "http:", "ws:", "wss:", "blob:", "*"],
|
||||
frameSrc: ["'self'", "https://www.youtube.com", "https://player.vimeo.com"],
|
||||
objectSrc: ["'none'"],
|
||||
baseUri: ["'self'"],
|
||||
formAction: ["'self'"],
|
||||
frameAncestors: ["'self'"],
|
||||
upgradeInsecureRequests: isProduction
|
||||
},
|
||||
reportUri: '/api/csp/report'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Add table
Add a link
Reference in a new issue