Files
IT-Nexus/backend/src/controllers/securityReport.controller.js

149 lines
6.3 KiB
JavaScript

const path = require('path');
const fs = require('fs');
const { getDatabase } = require('../config/database');
const multer = require('multer');
const pdfParse = require('pdf-parse');
const Anthropic = require('@anthropic-ai/sdk');
const UPLOAD_DIR = path.join(__dirname, '../../uploads/security-reports');
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, UPLOAD_DIR),
filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`),
});
const upload = multer({
storage,
limits: { fileSize: 50 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (file.mimetype === 'application/pdf') cb(null, true);
else cb(new Error('Nur PDF-Dateien erlaubt'));
},
});
const ANALYSIS_PROMPT = `Du analysierst einen wöchentlichen Microsoft 365 Security Report von Arctic Wolf für Cereda Systems GmbH.
Extrahiere alle relevanten Informationen und gib sie als JSON zurück. Antworte NUR mit validem JSON, kein Text darum herum.
JSON-Struktur:
{
"report_period": "DD.MM.YYYY - DD.MM.YYYY",
"risk_level": "niedrig|mittel|hoch",
"summary": "Kurze 2-3 Satz Zusammenfassung auf Deutsch",
"login_stats": {
"total": 0,
"successful": 0,
"failed": 0,
"success_rate": 0.0
},
"top_login_failures": [
{ "user": "email@cereda-systems.de", "count": 0, "type": "Interactive|Non-Interactive" }
],
"non_european_logins": [
{ "user": "email", "country": "Land", "ip": "IP", "count": 0, "flagged": true }
],
"sharepoint_anonymous_links": 0,
"identity_protection_events": 0,
"new_groups_created": [],
"key_findings": ["Befund 1", "Befund 2"],
"recommendations": ["Empfehlung 1", "Empfehlung 2"],
"all_clear": true
}
Wichtig:
- risk_level "hoch" wenn: nicht-europäische Logins von unbekannten IPs, viele Login-Fehler bei kritischen Accounts (server@, admin@)
- risk_level "mittel" wenn: einige Auffälligkeiten aber keine aktiven Bedrohungen
- risk_level "niedrig" wenn: alles normal
- flagged: true bei nicht-europäischen Logins die verdächtig aussehen
- all_clear: false wenn risk_level hoch oder mittel`;
const analyzeWithClaude = async (text) => {
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 2000,
messages: [{
role: 'user',
content: `${ANALYSIS_PROMPT}\n\nReport-Inhalt:\n${text.slice(0, 15000)}`,
}],
});
const content = response.content[0].text.trim();
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (!jsonMatch) throw new Error('Claude hat kein gültiges JSON zurückgegeben');
return JSON.parse(jsonMatch[0]);
};
// ── Upload + Analysieren ──────────────────────────────────────────────────────
const uploadReport = [
upload.single('pdf'),
async (req, res) => {
if (!req.file) return res.status(400).json({ error: 'Keine PDF-Datei' });
try {
const pdfBuffer = fs.readFileSync(req.file.path);
const pdfData = await pdfParse(pdfBuffer);
const text = pdfData.text;
if (text.length < 100) {
fs.unlinkSync(req.file.path);
return res.status(400).json({ error: 'PDF konnte nicht gelesen werden oder ist leer' });
}
const analysis = await analyzeWithClaude(text);
const db = getDatabase();
const result = db.prepare(`
INSERT INTO security_reports
(filename, original_filename, report_period, risk_level, summary_json, created_by_user_id)
VALUES (?, ?, ?, ?, ?, ?)
`).run(
req.file.filename,
req.file.originalname,
analysis.report_period || 'Unbekannt',
analysis.risk_level || 'unbekannt',
JSON.stringify(analysis),
req.user.id
);
const report = db.prepare('SELECT * FROM security_reports WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json({ ...report, analysis });
} catch (e) {
if (req.file?.path && fs.existsSync(req.file.path)) fs.unlinkSync(req.file.path);
console.error('[SecurityReport] Upload error:', e.message);
res.status(500).json({ error: `Analyse fehlgeschlagen: ${e.message}` });
}
}
];
// ── Alle Reports ──────────────────────────────────────────────────────────────
const getAllReports = (req, res) => {
const db = getDatabase();
const rows = db.prepare(`
SELECT sr.*, u.username as created_by_username
FROM security_reports sr
LEFT JOIN users u ON sr.created_by_user_id = u.id
ORDER BY sr.created_at DESC
`).all();
res.json(rows.map(r => ({ ...r, analysis: JSON.parse(r.summary_json || '{}') })));
};
// ── Einzelner Report ──────────────────────────────────────────────────────────
const getReport = (req, res) => {
const db = getDatabase();
const report = db.prepare('SELECT * FROM security_reports WHERE id = ?').get(req.params.id);
if (!report) return res.status(404).json({ error: 'Nicht gefunden' });
res.json({ ...report, analysis: JSON.parse(report.summary_json || '{}') });
};
// ── Report löschen ────────────────────────────────────────────────────────────
const deleteReport = (req, res) => {
const db = getDatabase();
const report = db.prepare('SELECT * FROM security_reports WHERE id = ?').get(req.params.id);
if (!report) return res.status(404).json({ error: 'Nicht gefunden' });
const filePath = path.join(UPLOAD_DIR, report.filename);
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
db.prepare('DELETE FROM security_reports WHERE id = ?').run(req.params.id);
res.json({ success: true });
};
module.exports = { uploadReport, getAllReports, getReport, deleteReport };