149 lines
6.3 KiB
JavaScript
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 };
|