feat: Add Academy, Whistleblower, Incidents SDK modules, pitch-deck, blog and CI/CD config
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
- Academy, Whistleblower, Incidents frontend pages with API proxies and types - Vendor compliance API proxy route - Go backend handlers and models for all new SDK modules - Investor pitch-deck app with interactive slides - Blog section with DSGVO, AI Act, NIS2, glossary articles - MkDocs documentation site - CI/CD pipelines (Woodpecker, GitHub Actions), security scanning config - Planning and implementation documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
845
admin-v2/lib/sdk/incidents/api.ts
Normal file
845
admin-v2/lib/sdk/incidents/api.ts
Normal file
@@ -0,0 +1,845 @@
|
||||
/**
|
||||
* Incident/Breach Management API Client
|
||||
*
|
||||
* API client for DSGVO Art. 33/34 Incident & Data Breach Management
|
||||
* Connects via Next.js proxy to the ai-compliance-sdk backend
|
||||
*/
|
||||
|
||||
import {
|
||||
Incident,
|
||||
IncidentListResponse,
|
||||
IncidentFilters,
|
||||
IncidentCreateRequest,
|
||||
IncidentUpdateRequest,
|
||||
IncidentStatistics,
|
||||
IncidentMeasure,
|
||||
TimelineEntry,
|
||||
RiskAssessmentRequest,
|
||||
RiskAssessment,
|
||||
AuthorityNotification,
|
||||
DataSubjectNotification,
|
||||
IncidentSeverity,
|
||||
IncidentStatus,
|
||||
IncidentCategory,
|
||||
calculateRiskLevel,
|
||||
isNotificationRequired,
|
||||
get72hDeadline
|
||||
} from './types'
|
||||
|
||||
// =============================================================================
|
||||
// CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
const INCIDENTS_API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8093'
|
||||
const API_TIMEOUT = 30000 // 30 seconds
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function getTenantId(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('bp_tenant_id') || 'default-tenant'
|
||||
}
|
||||
return 'default-tenant'
|
||||
}
|
||||
|
||||
function getAuthHeaders(): HeadersInit {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Tenant-ID': getTenantId()
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('authToken')
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
const userId = localStorage.getItem('bp_user_id')
|
||||
if (userId) {
|
||||
headers['X-User-ID'] = userId
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
async function fetchWithTimeout<T>(
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
timeout: number = API_TIMEOUT
|
||||
): Promise<T> {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
...getAuthHeaders(),
|
||||
...options.headers
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text()
|
||||
let errorMessage = `HTTP ${response.status}: ${response.statusText}`
|
||||
try {
|
||||
const errorJson = JSON.parse(errorBody)
|
||||
errorMessage = errorJson.error || errorJson.message || errorMessage
|
||||
} catch {
|
||||
// Keep the HTTP status message
|
||||
}
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
// Handle empty responses
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
return {} as T
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// INCIDENT LIST & CRUD
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Alle Vorfaelle abrufen mit optionalen Filtern
|
||||
*/
|
||||
export async function fetchIncidents(filters?: IncidentFilters): Promise<IncidentListResponse> {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (filters) {
|
||||
if (filters.status) {
|
||||
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status]
|
||||
statuses.forEach(s => params.append('status', s))
|
||||
}
|
||||
if (filters.severity) {
|
||||
const severities = Array.isArray(filters.severity) ? filters.severity : [filters.severity]
|
||||
severities.forEach(s => params.append('severity', s))
|
||||
}
|
||||
if (filters.category) {
|
||||
const categories = Array.isArray(filters.category) ? filters.category : [filters.category]
|
||||
categories.forEach(c => params.append('category', c))
|
||||
}
|
||||
if (filters.assignedTo) params.set('assignedTo', filters.assignedTo)
|
||||
if (filters.overdue !== undefined) params.set('overdue', String(filters.overdue))
|
||||
if (filters.search) params.set('search', filters.search)
|
||||
if (filters.dateFrom) params.set('dateFrom', filters.dateFrom)
|
||||
if (filters.dateTo) params.set('dateTo', filters.dateTo)
|
||||
}
|
||||
|
||||
const queryString = params.toString()
|
||||
const url = `${INCIDENTS_API_BASE}/api/v1/incidents${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return fetchWithTimeout<IncidentListResponse>(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Einzelnen Vorfall per ID abrufen
|
||||
*/
|
||||
export async function fetchIncident(id: string): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(`${INCIDENTS_API_BASE}/api/v1/incidents/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Neuen Vorfall erstellen
|
||||
*/
|
||||
export async function createIncident(request: IncidentCreateRequest): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(`${INCIDENTS_API_BASE}/api/v1/incidents`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Vorfall aktualisieren
|
||||
*/
|
||||
export async function updateIncident(id: string, update: IncidentUpdateRequest): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(`${INCIDENTS_API_BASE}/api/v1/incidents/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(update)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Vorfall loeschen (Soft Delete)
|
||||
*/
|
||||
export async function deleteIncident(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(`${INCIDENTS_API_BASE}/api/v1/incidents/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RISK ASSESSMENT
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Risikobewertung fuer einen Vorfall durchfuehren (Art. 33 DSGVO)
|
||||
*/
|
||||
export async function submitRiskAssessment(
|
||||
incidentId: string,
|
||||
assessment: RiskAssessmentRequest
|
||||
): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/risk-assessment`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(assessment)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AUTHORITY NOTIFICATION (Art. 33 DSGVO)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Meldeformular fuer die Aufsichtsbehoerde generieren
|
||||
*/
|
||||
export async function generateAuthorityForm(incidentId: string): Promise<Blob> {
|
||||
const response = await fetch(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/authority-form/pdf`,
|
||||
{
|
||||
headers: getAuthHeaders()
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`PDF-Generierung fehlgeschlagen: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.blob()
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung an die Aufsichtsbehoerde einreichen (Art. 33 DSGVO)
|
||||
*/
|
||||
export async function submitAuthorityNotification(
|
||||
incidentId: string,
|
||||
data: Partial<AuthorityNotification>
|
||||
): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/authority-notification`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DATA SUBJECT NOTIFICATION (Art. 34 DSGVO)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Betroffene Personen benachrichtigen (Art. 34 DSGVO)
|
||||
*/
|
||||
export async function sendDataSubjectNotification(
|
||||
incidentId: string,
|
||||
data: Partial<DataSubjectNotification>
|
||||
): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/data-subject-notification`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MEASURES (Massnahmen)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Massnahme hinzufuegen (Sofort-, Korrektur- oder Praeventionsmassnahme)
|
||||
*/
|
||||
export async function addMeasure(
|
||||
incidentId: string,
|
||||
measure: Omit<IncidentMeasure, 'id' | 'incidentId'>
|
||||
): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/measures`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(measure)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Massnahme aktualisieren
|
||||
*/
|
||||
export async function updateMeasure(
|
||||
measureId: string,
|
||||
update: Partial<IncidentMeasure>
|
||||
): Promise<IncidentMeasure> {
|
||||
return fetchWithTimeout<IncidentMeasure>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/measures/${measureId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(update)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Massnahme als abgeschlossen markieren
|
||||
*/
|
||||
export async function completeMeasure(measureId: string): Promise<IncidentMeasure> {
|
||||
return fetchWithTimeout<IncidentMeasure>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/measures/${measureId}/complete`,
|
||||
{
|
||||
method: 'POST'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TIMELINE
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Zeitleisteneintrag hinzufuegen
|
||||
*/
|
||||
export async function addTimelineEntry(
|
||||
incidentId: string,
|
||||
entry: Omit<TimelineEntry, 'id' | 'incidentId'>
|
||||
): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/timeline`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(entry)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CLOSE INCIDENT
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Vorfall abschliessen mit Lessons Learned
|
||||
*/
|
||||
export async function closeIncident(
|
||||
incidentId: string,
|
||||
lessonsLearned: string
|
||||
): Promise<Incident> {
|
||||
return fetchWithTimeout<Incident>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/close`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ lessonsLearned })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Vorfall-Statistiken abrufen
|
||||
*/
|
||||
export async function fetchIncidentStatistics(): Promise<IncidentStatistics> {
|
||||
return fetchWithTimeout<IncidentStatistics>(
|
||||
`${INCIDENTS_API_BASE}/api/v1/incidents/statistics`
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SDK PROXY FUNCTION (mit Fallback auf Mock-Daten)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Fetch Incident-Liste via SDK-Proxy mit Fallback auf Mock-Daten
|
||||
*/
|
||||
export async function fetchSDKIncidentList(): Promise<{ incidents: Incident[]; statistics: IncidentStatistics }> {
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/incidents', {
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
const data = await res.json()
|
||||
const incidents: Incident[] = data.incidents || []
|
||||
|
||||
// Statistiken lokal berechnen
|
||||
const statistics = computeStatistics(incidents)
|
||||
return { incidents, statistics }
|
||||
} catch (error) {
|
||||
console.warn('SDK-Backend nicht erreichbar, verwende Mock-Daten:', error)
|
||||
const incidents = createMockIncidents()
|
||||
const statistics = createMockStatistics()
|
||||
return { incidents, statistics }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiken lokal aus Incident-Liste berechnen
|
||||
*/
|
||||
function computeStatistics(incidents: Incident[]): IncidentStatistics {
|
||||
const countBy = <K extends string>(items: { [key: string]: unknown }[], field: string): Record<K, number> => {
|
||||
const result: Record<string, number> = {}
|
||||
items.forEach(item => {
|
||||
const key = String(item[field])
|
||||
result[key] = (result[key] || 0) + 1
|
||||
})
|
||||
return result as Record<K, number>
|
||||
}
|
||||
|
||||
const statusCounts = countBy<IncidentStatus>(incidents as unknown as { [key: string]: unknown }[], 'status')
|
||||
const severityCounts = countBy<IncidentSeverity>(incidents as unknown as { [key: string]: unknown }[], 'severity')
|
||||
const categoryCounts = countBy<IncidentCategory>(incidents as unknown as { [key: string]: unknown }[], 'category')
|
||||
|
||||
const openIncidents = incidents.filter(i => i.status !== 'closed').length
|
||||
const notificationsPending = incidents.filter(i =>
|
||||
i.authorityNotification !== null &&
|
||||
i.authorityNotification.status === 'pending' &&
|
||||
i.status !== 'closed'
|
||||
).length
|
||||
|
||||
// Durchschnittliche Reaktionszeit berechnen
|
||||
let totalResponseHours = 0
|
||||
let respondedCount = 0
|
||||
incidents.forEach(i => {
|
||||
if (i.riskAssessment && i.riskAssessment.assessedAt) {
|
||||
const detected = new Date(i.detectedAt).getTime()
|
||||
const assessed = new Date(i.riskAssessment.assessedAt).getTime()
|
||||
totalResponseHours += (assessed - detected) / (1000 * 60 * 60)
|
||||
respondedCount++
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
totalIncidents: incidents.length,
|
||||
openIncidents,
|
||||
notificationsPending,
|
||||
averageResponseTimeHours: respondedCount > 0 ? Math.round(totalResponseHours / respondedCount * 10) / 10 : 0,
|
||||
bySeverity: {
|
||||
low: severityCounts['low'] || 0,
|
||||
medium: severityCounts['medium'] || 0,
|
||||
high: severityCounts['high'] || 0,
|
||||
critical: severityCounts['critical'] || 0
|
||||
},
|
||||
byCategory: {
|
||||
data_breach: categoryCounts['data_breach'] || 0,
|
||||
unauthorized_access: categoryCounts['unauthorized_access'] || 0,
|
||||
data_loss: categoryCounts['data_loss'] || 0,
|
||||
system_compromise: categoryCounts['system_compromise'] || 0,
|
||||
phishing: categoryCounts['phishing'] || 0,
|
||||
ransomware: categoryCounts['ransomware'] || 0,
|
||||
insider_threat: categoryCounts['insider_threat'] || 0,
|
||||
physical_breach: categoryCounts['physical_breach'] || 0,
|
||||
other: categoryCounts['other'] || 0
|
||||
},
|
||||
byStatus: {
|
||||
detected: statusCounts['detected'] || 0,
|
||||
assessment: statusCounts['assessment'] || 0,
|
||||
containment: statusCounts['containment'] || 0,
|
||||
notification_required: statusCounts['notification_required'] || 0,
|
||||
notification_sent: statusCounts['notification_sent'] || 0,
|
||||
remediation: statusCounts['remediation'] || 0,
|
||||
closed: statusCounts['closed'] || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MOCK DATA (Demo-Daten fuer Entwicklung und Tests)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Erstellt Demo-Vorfaelle fuer die Entwicklung
|
||||
*/
|
||||
export function createMockIncidents(): Incident[] {
|
||||
const now = new Date()
|
||||
|
||||
return [
|
||||
// 1. Gerade erkannt - noch nicht bewertet (detected/new)
|
||||
{
|
||||
id: 'inc-001',
|
||||
referenceNumber: 'INC-2026-000001',
|
||||
title: 'Unbefugter Zugriff auf Schuelerdatenbank',
|
||||
description: 'Ein ehemaliger Mitarbeiter hat sich mit noch aktiven Zugangsdaten in die Schuelerdatenbank eingeloggt. Der Zugriff wurde durch die Log-Analyse entdeckt.',
|
||||
category: 'unauthorized_access',
|
||||
severity: 'high',
|
||||
status: 'detected',
|
||||
detectedAt: new Date(now.getTime() - 3 * 60 * 60 * 1000).toISOString(), // 3 Stunden her
|
||||
detectedBy: 'Log-Analyse (automatisiert)',
|
||||
affectedSystems: ['Schuelerdatenbank', 'Schulverwaltungssystem'],
|
||||
affectedDataCategories: ['Personenbezogene Daten', 'Daten von Kindern', 'Gesundheitsdaten'],
|
||||
estimatedAffectedPersons: 800,
|
||||
riskAssessment: null,
|
||||
authorityNotification: null,
|
||||
dataSubjectNotification: null,
|
||||
measures: [],
|
||||
timeline: [
|
||||
{
|
||||
id: 'tl-001',
|
||||
incidentId: 'inc-001',
|
||||
timestamp: new Date(now.getTime() - 3 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Vorfall erkannt',
|
||||
description: 'Automatische Log-Analyse meldet verdaechtigen Login eines deaktivierten Kontos',
|
||||
performedBy: 'SIEM-System'
|
||||
}
|
||||
],
|
||||
assignedTo: undefined
|
||||
},
|
||||
|
||||
// 2. In Bewertung (assessment) - Risikobewertung laeuft
|
||||
{
|
||||
id: 'inc-002',
|
||||
referenceNumber: 'INC-2026-000002',
|
||||
title: 'E-Mail mit Kundendaten an falschen Empfaenger',
|
||||
description: 'Ein Mitarbeiter hat eine Excel-Datei mit Kundendaten (Name, Adresse, Vertragsnummer) an einen falschen E-Mail-Empfaenger gesendet. Der Empfaenger wurde kontaktiert und hat die Loeschung bestaetigt.',
|
||||
category: 'data_breach',
|
||||
severity: 'medium',
|
||||
status: 'assessment',
|
||||
detectedAt: new Date(now.getTime() - 18 * 60 * 60 * 1000).toISOString(), // 18 Stunden her
|
||||
detectedBy: 'Vertriebsabteilung',
|
||||
affectedSystems: ['E-Mail-System (Exchange)'],
|
||||
affectedDataCategories: ['Personenbezogene Daten', 'Kundendaten'],
|
||||
estimatedAffectedPersons: 150,
|
||||
riskAssessment: {
|
||||
id: 'ra-002',
|
||||
assessedBy: 'DSB Mueller',
|
||||
assessedAt: new Date(now.getTime() - 12 * 60 * 60 * 1000).toISOString(),
|
||||
likelihoodScore: 3,
|
||||
impactScore: 2,
|
||||
overallRisk: 'medium',
|
||||
notificationRequired: false,
|
||||
reasoning: 'Empfaenger hat Loeschung bestaetigt. Datenkategorie: allgemeine Kontaktdaten und Vertragsnummern. Geringes Risiko fuer betroffene Personen.'
|
||||
},
|
||||
authorityNotification: {
|
||||
id: 'an-002',
|
||||
authority: 'LfD Niedersachsen',
|
||||
deadline72h: new Date(new Date(now.getTime() - 18 * 60 * 60 * 1000).getTime() + 72 * 60 * 60 * 1000).toISOString(),
|
||||
status: 'pending',
|
||||
formData: {}
|
||||
},
|
||||
dataSubjectNotification: null,
|
||||
measures: [
|
||||
{
|
||||
id: 'meas-001',
|
||||
incidentId: 'inc-002',
|
||||
title: 'Empfaenger kontaktiert',
|
||||
description: 'Falscher Empfaenger kontaktiert mit Bitte um Loeschung',
|
||||
type: 'immediate',
|
||||
status: 'completed',
|
||||
responsible: 'Vertriebsleitung',
|
||||
dueDate: new Date(now.getTime() - 16 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(now.getTime() - 15 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
],
|
||||
timeline: [
|
||||
{
|
||||
id: 'tl-002',
|
||||
incidentId: 'inc-002',
|
||||
timestamp: new Date(now.getTime() - 18 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Vorfall gemeldet',
|
||||
description: 'Mitarbeiter meldet versehentlichen E-Mail-Versand',
|
||||
performedBy: 'M. Schmidt (Vertrieb)'
|
||||
},
|
||||
{
|
||||
id: 'tl-003',
|
||||
incidentId: 'inc-002',
|
||||
timestamp: new Date(now.getTime() - 15 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Sofortmassnahme',
|
||||
description: 'Empfaenger kontaktiert und Loeschung bestaetigt',
|
||||
performedBy: 'Vertriebsleitung'
|
||||
},
|
||||
{
|
||||
id: 'tl-004',
|
||||
incidentId: 'inc-002',
|
||||
timestamp: new Date(now.getTime() - 12 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Risikobewertung',
|
||||
description: 'Bewertung durchgefuehrt - mittleres Risiko, keine Meldepflicht',
|
||||
performedBy: 'DSB Mueller'
|
||||
}
|
||||
],
|
||||
assignedTo: 'DSB Mueller'
|
||||
},
|
||||
|
||||
// 3. Gemeldet (notification_sent) - Ransomware-Angriff
|
||||
{
|
||||
id: 'inc-003',
|
||||
referenceNumber: 'INC-2026-000003',
|
||||
title: 'Ransomware-Angriff auf Dateiserver',
|
||||
description: 'Am Montagmorgen wurde ein Ransomware-Angriff auf den zentralen Dateiserver erkannt. Mehrere verschluesselte Dateien wurden identifiziert. Der Angriffsvektor war eine Phishing-E-Mail an einen Mitarbeiter.',
|
||||
category: 'ransomware',
|
||||
severity: 'critical',
|
||||
status: 'notification_sent',
|
||||
detectedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
detectedBy: 'IT-Sicherheitsteam',
|
||||
affectedSystems: ['Dateiserver (FS-01)', 'E-Mail-System', 'Backup-Server'],
|
||||
affectedDataCategories: ['Personenbezogene Daten', 'Beschaeftigtendaten', 'Kundendaten', 'Finanzdaten'],
|
||||
estimatedAffectedPersons: 2500,
|
||||
riskAssessment: {
|
||||
id: 'ra-003',
|
||||
assessedBy: 'DSB Mueller',
|
||||
assessedAt: new Date(now.getTime() - 4.5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
likelihoodScore: 5,
|
||||
impactScore: 5,
|
||||
overallRisk: 'critical',
|
||||
notificationRequired: true,
|
||||
reasoning: 'Hohes Risiko fuer Rechte und Freiheiten der betroffenen Personen durch potentiellen Zugriff auf personenbezogene Daten und Finanzdaten. Verschluesselung betrifft Verfuegbarkeit, Exfiltration nicht auszuschliessen.'
|
||||
},
|
||||
authorityNotification: {
|
||||
id: 'an-003',
|
||||
authority: 'LfD Niedersachsen',
|
||||
deadline72h: new Date(new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).getTime() + 72 * 60 * 60 * 1000).toISOString(),
|
||||
submittedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
status: 'submitted',
|
||||
formData: {
|
||||
referenceNumber: 'LfD-NI-2026-04821',
|
||||
incidentType: 'Ransomware',
|
||||
affectedPersons: 2500
|
||||
},
|
||||
pdfUrl: '/api/sdk/v1/incidents/inc-003/authority-form.pdf'
|
||||
},
|
||||
dataSubjectNotification: {
|
||||
id: 'dsn-003',
|
||||
notificationRequired: true,
|
||||
templateText: 'Sehr geehrte Damen und Herren, wir informieren Sie ueber einen Sicherheitsvorfall, bei dem moeglicherweise Ihre personenbezogenen Daten betroffen sind...',
|
||||
sentAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
recipientCount: 2500,
|
||||
method: 'email'
|
||||
},
|
||||
measures: [
|
||||
{
|
||||
id: 'meas-002',
|
||||
incidentId: 'inc-003',
|
||||
title: 'Netzwerksegmentierung',
|
||||
description: 'Betroffene Systeme vom Netzwerk isoliert',
|
||||
type: 'immediate',
|
||||
status: 'completed',
|
||||
responsible: 'IT-Sicherheitsteam',
|
||||
dueDate: new Date(now.getTime() - 4.8 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(now.getTime() - 4.9 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'meas-003',
|
||||
incidentId: 'inc-003',
|
||||
title: 'Passwoerter zuruecksetzen',
|
||||
description: 'Alle Benutzerpasswoerter zurueckgesetzt',
|
||||
type: 'immediate',
|
||||
status: 'completed',
|
||||
responsible: 'IT-Administration',
|
||||
dueDate: new Date(now.getTime() - 4.5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(now.getTime() - 4.5 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'meas-004',
|
||||
incidentId: 'inc-003',
|
||||
title: 'E-Mail-Security Gateway implementieren',
|
||||
description: 'Implementierung eines fortgeschrittenen E-Mail-Sicherheitsgateways mit Sandboxing',
|
||||
type: 'preventive',
|
||||
status: 'in_progress',
|
||||
responsible: 'IT-Sicherheitsteam',
|
||||
dueDate: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'meas-005',
|
||||
incidentId: 'inc-003',
|
||||
title: 'Mitarbeiterschulung Phishing',
|
||||
description: 'Verpflichtende Schulung fuer alle Mitarbeiter zum Thema Phishing-Erkennung',
|
||||
type: 'preventive',
|
||||
status: 'planned',
|
||||
responsible: 'Personalwesen',
|
||||
dueDate: new Date(now.getTime() + 60 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
],
|
||||
timeline: [
|
||||
{
|
||||
id: 'tl-005',
|
||||
incidentId: 'inc-003',
|
||||
timestamp: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Vorfall erkannt',
|
||||
description: 'IT-Sicherheitsteam erkennt ungewoehnliche Verschluesselungsaktivitaet',
|
||||
performedBy: 'IT-Sicherheitsteam'
|
||||
},
|
||||
{
|
||||
id: 'tl-006',
|
||||
incidentId: 'inc-003',
|
||||
timestamp: new Date(now.getTime() - 4.9 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Eindaemmung gestartet',
|
||||
description: 'Netzwerksegmentierung und Isolation betroffener Systeme',
|
||||
performedBy: 'IT-Sicherheitsteam'
|
||||
},
|
||||
{
|
||||
id: 'tl-007',
|
||||
incidentId: 'inc-003',
|
||||
timestamp: new Date(now.getTime() - 4.5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Risikobewertung abgeschlossen',
|
||||
description: 'Kritisches Risiko festgestellt - Meldepflicht ausgeloest',
|
||||
performedBy: 'DSB Mueller'
|
||||
},
|
||||
{
|
||||
id: 'tl-008',
|
||||
incidentId: 'inc-003',
|
||||
timestamp: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Behoerdenbenachrichtigung',
|
||||
description: 'Meldung an LfD Niedersachsen eingereicht',
|
||||
performedBy: 'DSB Mueller'
|
||||
},
|
||||
{
|
||||
id: 'tl-009',
|
||||
incidentId: 'inc-003',
|
||||
timestamp: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Betroffene benachrichtigt',
|
||||
description: '2.500 betroffene Personen per E-Mail informiert',
|
||||
performedBy: 'Kommunikationsabteilung'
|
||||
}
|
||||
],
|
||||
assignedTo: 'DSB Mueller'
|
||||
},
|
||||
|
||||
// 4. Abgeschlossener Vorfall (closed) - Phishing
|
||||
{
|
||||
id: 'inc-004',
|
||||
referenceNumber: 'INC-2026-000004',
|
||||
title: 'Phishing-Angriff auf Personalabteilung',
|
||||
description: 'Gezielter Phishing-Angriff auf die Personalabteilung. Ein Mitarbeiter hat Zugangsdaten auf einer gefaelschten Login-Seite eingegeben. Das Konto wurde sofort gesperrt. Keine Datenexfiltration festgestellt.',
|
||||
category: 'phishing',
|
||||
severity: 'high',
|
||||
status: 'closed',
|
||||
detectedAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
detectedBy: 'IT-Sicherheitsteam (SIEM-Alert)',
|
||||
affectedSystems: ['Active Directory', 'HR-Portal'],
|
||||
affectedDataCategories: ['Beschaeftigtendaten', 'Personenbezogene Daten'],
|
||||
estimatedAffectedPersons: 0,
|
||||
riskAssessment: {
|
||||
id: 'ra-004',
|
||||
assessedBy: 'DSB Mueller',
|
||||
assessedAt: new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
likelihoodScore: 4,
|
||||
impactScore: 3,
|
||||
overallRisk: 'high',
|
||||
notificationRequired: true,
|
||||
reasoning: 'Zugangsdaten kompromittiert, potentieller Zugriff auf Personaldaten. Keine Exfiltration festgestellt, dennoch Meldung wegen Kompromittierung der Zugangsdaten.'
|
||||
},
|
||||
authorityNotification: {
|
||||
id: 'an-004',
|
||||
authority: 'LfD Niedersachsen',
|
||||
deadline72h: new Date(new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).getTime() + 72 * 60 * 60 * 1000).toISOString(),
|
||||
submittedAt: new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
status: 'acknowledged',
|
||||
formData: {
|
||||
referenceNumber: 'LfD-NI-2026-03912',
|
||||
incidentType: 'Phishing',
|
||||
affectedPersons: 0
|
||||
}
|
||||
},
|
||||
dataSubjectNotification: {
|
||||
id: 'dsn-004',
|
||||
notificationRequired: false,
|
||||
templateText: '',
|
||||
recipientCount: 0,
|
||||
method: 'email'
|
||||
},
|
||||
measures: [
|
||||
{
|
||||
id: 'meas-006',
|
||||
incidentId: 'inc-004',
|
||||
title: 'Konto gesperrt',
|
||||
description: 'Kompromittiertes Benutzerkonto sofort gesperrt',
|
||||
type: 'immediate',
|
||||
status: 'completed',
|
||||
responsible: 'IT-Administration',
|
||||
dueDate: new Date(now.getTime() - 29.8 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(now.getTime() - 29.9 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'meas-007',
|
||||
incidentId: 'inc-004',
|
||||
title: 'MFA fuer alle Mitarbeiter',
|
||||
description: 'Einfuehrung von Multi-Faktor-Authentifizierung fuer alle Konten',
|
||||
type: 'preventive',
|
||||
status: 'completed',
|
||||
responsible: 'IT-Sicherheitsteam',
|
||||
dueDate: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(now.getTime() - 12 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
],
|
||||
timeline: [
|
||||
{
|
||||
id: 'tl-010',
|
||||
incidentId: 'inc-004',
|
||||
timestamp: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'SIEM-Alert',
|
||||
description: 'Verdaechtiger Login-Versuch aus unbekannter Region erkannt',
|
||||
performedBy: 'IT-Sicherheitsteam'
|
||||
},
|
||||
{
|
||||
id: 'tl-011',
|
||||
incidentId: 'inc-004',
|
||||
timestamp: new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Behoerdenbenachrichtigung',
|
||||
description: 'Meldung an LfD Niedersachsen',
|
||||
performedBy: 'DSB Mueller'
|
||||
},
|
||||
{
|
||||
id: 'tl-012',
|
||||
incidentId: 'inc-004',
|
||||
timestamp: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
action: 'Vorfall abgeschlossen',
|
||||
description: 'Alle Massnahmen umgesetzt, keine Datenexfiltration festgestellt',
|
||||
performedBy: 'DSB Mueller'
|
||||
}
|
||||
],
|
||||
assignedTo: 'DSB Mueller',
|
||||
closedAt: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
lessonsLearned: '1. MFA haette den Zugriff verhindert (jetzt implementiert). 2. E-Mail-Security-Gateway muss verbesserte Phishing-Erkennung erhalten. 3. Regelmaessige Phishing-Simulationen fuer alle Mitarbeiter einfuehren.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt Mock-Statistiken fuer die Entwicklung
|
||||
*/
|
||||
export function createMockStatistics(): IncidentStatistics {
|
||||
return {
|
||||
totalIncidents: 4,
|
||||
openIncidents: 3,
|
||||
notificationsPending: 1,
|
||||
averageResponseTimeHours: 8.5,
|
||||
bySeverity: {
|
||||
low: 0,
|
||||
medium: 1,
|
||||
high: 2,
|
||||
critical: 1
|
||||
},
|
||||
byCategory: {
|
||||
data_breach: 1,
|
||||
unauthorized_access: 1,
|
||||
data_loss: 0,
|
||||
system_compromise: 0,
|
||||
phishing: 1,
|
||||
ransomware: 1,
|
||||
insider_threat: 0,
|
||||
physical_breach: 0,
|
||||
other: 0
|
||||
},
|
||||
byStatus: {
|
||||
detected: 1,
|
||||
assessment: 1,
|
||||
containment: 0,
|
||||
notification_required: 0,
|
||||
notification_sent: 1,
|
||||
remediation: 0,
|
||||
closed: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
447
admin-v2/lib/sdk/incidents/types.ts
Normal file
447
admin-v2/lib/sdk/incidents/types.ts
Normal file
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* Incident/Breach Management Types (Datenpannen-Management)
|
||||
*
|
||||
* TypeScript definitions for DSGVO Art. 33/34 Incident & Data Breach Management
|
||||
* 72-Stunden-Meldefrist an die Aufsichtsbehoerde
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// ENUMS & CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
export type IncidentSeverity = 'low' | 'medium' | 'high' | 'critical'
|
||||
|
||||
export type IncidentStatus =
|
||||
| 'detected' // Erkannt
|
||||
| 'assessment' // Bewertung laeuft
|
||||
| 'containment' // Eindaemmung
|
||||
| 'notification_required' // Meldepflichtig - Meldung steht aus
|
||||
| 'notification_sent' // Gemeldet an Aufsichtsbehoerde
|
||||
| 'remediation' // Behebung laeuft
|
||||
| 'closed' // Abgeschlossen
|
||||
|
||||
export type IncidentCategory =
|
||||
| 'data_breach' // Datenpanne / Datenschutzverletzung
|
||||
| 'unauthorized_access' // Unbefugter Zugriff
|
||||
| 'data_loss' // Datenverlust
|
||||
| 'system_compromise' // Systemkompromittierung
|
||||
| 'phishing' // Phishing-Angriff
|
||||
| 'ransomware' // Ransomware
|
||||
| 'insider_threat' // Insider-Bedrohung
|
||||
| 'physical_breach' // Physischer Sicherheitsvorfall
|
||||
| 'other' // Sonstiges
|
||||
|
||||
// =============================================================================
|
||||
// SEVERITY METADATA
|
||||
// =============================================================================
|
||||
|
||||
export interface IncidentSeverityInfo {
|
||||
label: string
|
||||
description: string
|
||||
color: string
|
||||
bgColor: string
|
||||
}
|
||||
|
||||
export const INCIDENT_SEVERITY_INFO: Record<IncidentSeverity, IncidentSeverityInfo> = {
|
||||
low: {
|
||||
label: 'Niedrig',
|
||||
description: 'Geringes Risiko fuer betroffene Personen, keine Meldepflicht erwartet',
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-100'
|
||||
},
|
||||
medium: {
|
||||
label: 'Mittel',
|
||||
description: 'Moderates Risiko, Meldepflicht an Aufsichtsbehoerde moeglich',
|
||||
color: 'text-yellow-700',
|
||||
bgColor: 'bg-yellow-100'
|
||||
},
|
||||
high: {
|
||||
label: 'Hoch',
|
||||
description: 'Hohes Risiko, Meldepflicht an Aufsichtsbehoerde wahrscheinlich',
|
||||
color: 'text-orange-700',
|
||||
bgColor: 'bg-orange-100'
|
||||
},
|
||||
critical: {
|
||||
label: 'Kritisch',
|
||||
description: 'Sehr hohes Risiko, Meldepflicht an Aufsichtsbehoerde und Betroffene',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100'
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATUS METADATA
|
||||
// =============================================================================
|
||||
|
||||
export interface IncidentStatusInfo {
|
||||
label: string
|
||||
description: string
|
||||
color: string
|
||||
bgColor: string
|
||||
}
|
||||
|
||||
export const INCIDENT_STATUS_INFO: Record<IncidentStatus, IncidentStatusInfo> = {
|
||||
detected: {
|
||||
label: 'Erkannt',
|
||||
description: 'Vorfall wurde erkannt und dokumentiert',
|
||||
color: 'text-blue-700',
|
||||
bgColor: 'bg-blue-100'
|
||||
},
|
||||
assessment: {
|
||||
label: 'Bewertung',
|
||||
description: 'Risikobewertung und Einschaetzung der Meldepflicht',
|
||||
color: 'text-yellow-700',
|
||||
bgColor: 'bg-yellow-100'
|
||||
},
|
||||
containment: {
|
||||
label: 'Eindaemmung',
|
||||
description: 'Sofortmassnahmen zur Eindaemmung werden durchgefuehrt',
|
||||
color: 'text-orange-700',
|
||||
bgColor: 'bg-orange-100'
|
||||
},
|
||||
notification_required: {
|
||||
label: 'Meldepflichtig',
|
||||
description: 'Meldung an Aufsichtsbehoerde erforderlich (Art. 33 DSGVO)',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100'
|
||||
},
|
||||
notification_sent: {
|
||||
label: 'Gemeldet',
|
||||
description: 'Meldung an die Aufsichtsbehoerde wurde eingereicht',
|
||||
color: 'text-purple-700',
|
||||
bgColor: 'bg-purple-100'
|
||||
},
|
||||
remediation: {
|
||||
label: 'Behebung',
|
||||
description: 'Langfristige Behebungs- und Praeventionsmassnahmen',
|
||||
color: 'text-indigo-700',
|
||||
bgColor: 'bg-indigo-100'
|
||||
},
|
||||
closed: {
|
||||
label: 'Abgeschlossen',
|
||||
description: 'Vorfall vollstaendig bearbeitet und dokumentiert',
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-100'
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CATEGORY METADATA
|
||||
// =============================================================================
|
||||
|
||||
export interface IncidentCategoryInfo {
|
||||
label: string
|
||||
description: string
|
||||
icon: string
|
||||
color: string
|
||||
bgColor: string
|
||||
}
|
||||
|
||||
export const INCIDENT_CATEGORY_INFO: Record<IncidentCategory, IncidentCategoryInfo> = {
|
||||
data_breach: {
|
||||
label: 'Datenpanne',
|
||||
description: 'Allgemeine Datenschutzverletzung mit Offenlegung personenbezogener Daten',
|
||||
icon: '\u{1F4C4}',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100'
|
||||
},
|
||||
unauthorized_access: {
|
||||
label: 'Unbefugter Zugriff',
|
||||
description: 'Unberechtigter Zugriff auf Systeme oder Daten',
|
||||
icon: '\u{1F6AB}',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100'
|
||||
},
|
||||
data_loss: {
|
||||
label: 'Datenverlust',
|
||||
description: 'Verlust von Daten durch technischen Fehler oder Versehen',
|
||||
icon: '\u{1F4BE}',
|
||||
color: 'text-orange-700',
|
||||
bgColor: 'bg-orange-100'
|
||||
},
|
||||
system_compromise: {
|
||||
label: 'Systemkompromittierung',
|
||||
description: 'System wurde durch Angreifer kompromittiert',
|
||||
icon: '\u{1F4BB}',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100'
|
||||
},
|
||||
phishing: {
|
||||
label: 'Phishing-Angriff',
|
||||
description: 'Taeuschendes Abfangen von Zugangsdaten oder Daten',
|
||||
icon: '\u{1F3A3}',
|
||||
color: 'text-orange-700',
|
||||
bgColor: 'bg-orange-100'
|
||||
},
|
||||
ransomware: {
|
||||
label: 'Ransomware',
|
||||
description: 'Verschluesselung von Daten durch Schadsoftware',
|
||||
icon: '\u{1F512}',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100'
|
||||
},
|
||||
insider_threat: {
|
||||
label: 'Insider-Bedrohung',
|
||||
description: 'Vorsaetzlicher oder fahrlaessiger Verstoss durch Mitarbeiter',
|
||||
icon: '\u{1F464}',
|
||||
color: 'text-purple-700',
|
||||
bgColor: 'bg-purple-100'
|
||||
},
|
||||
physical_breach: {
|
||||
label: 'Physischer Sicherheitsvorfall',
|
||||
description: 'Einbruch, Diebstahl von Geraeten oder physische Zugriffe',
|
||||
icon: '\u{1F3E2}',
|
||||
color: 'text-gray-700',
|
||||
bgColor: 'bg-gray-100'
|
||||
},
|
||||
other: {
|
||||
label: 'Sonstiges',
|
||||
description: 'Sonstiger Datenschutzvorfall',
|
||||
icon: '\u{2753}',
|
||||
color: 'text-gray-700',
|
||||
bgColor: 'bg-gray-100'
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN INTERFACES
|
||||
// =============================================================================
|
||||
|
||||
export interface RiskAssessment {
|
||||
id: string
|
||||
assessedBy: string
|
||||
assessedAt: string
|
||||
likelihoodScore: number // 1-5 (1 = sehr unwahrscheinlich, 5 = sehr wahrscheinlich)
|
||||
impactScore: number // 1-5 (1 = gering, 5 = katastrophal)
|
||||
overallRisk: IncidentSeverity // Berechnetes Gesamtrisiko
|
||||
notificationRequired: boolean // Art. 33 Bewertung
|
||||
reasoning: string // Begruendung der Bewertung
|
||||
}
|
||||
|
||||
export interface AuthorityNotification {
|
||||
id: string
|
||||
authority: string // z.B. "LfD Niedersachsen"
|
||||
deadline72h: string // 72 Stunden nach Erkennung (Art. 33)
|
||||
submittedAt?: string
|
||||
status: 'pending' | 'submitted' | 'acknowledged'
|
||||
formData: Record<string, unknown>
|
||||
pdfUrl?: string
|
||||
}
|
||||
|
||||
export interface DataSubjectNotification {
|
||||
id: string
|
||||
notificationRequired: boolean // Art. 34 Bewertung
|
||||
templateText: string
|
||||
sentAt?: string
|
||||
recipientCount: number
|
||||
method: 'email' | 'letter' | 'portal' | 'public'
|
||||
}
|
||||
|
||||
export interface IncidentMeasure {
|
||||
id: string
|
||||
incidentId: string
|
||||
title: string
|
||||
description: string
|
||||
type: 'immediate' | 'corrective' | 'preventive'
|
||||
status: 'planned' | 'in_progress' | 'completed'
|
||||
responsible: string
|
||||
dueDate: string
|
||||
completedAt?: string
|
||||
}
|
||||
|
||||
export interface TimelineEntry {
|
||||
id: string
|
||||
incidentId: string
|
||||
timestamp: string
|
||||
action: string
|
||||
description: string
|
||||
performedBy: string
|
||||
}
|
||||
|
||||
export interface Incident {
|
||||
id: string
|
||||
referenceNumber: string // z.B. "INC-2025-000001"
|
||||
title: string
|
||||
description: string
|
||||
category: IncidentCategory
|
||||
severity: IncidentSeverity
|
||||
status: IncidentStatus
|
||||
|
||||
// Erkennung
|
||||
detectedAt: string
|
||||
detectedBy: string
|
||||
|
||||
// Betroffene Systeme & Daten
|
||||
affectedSystems: string[]
|
||||
affectedDataCategories: string[]
|
||||
estimatedAffectedPersons: number
|
||||
|
||||
// Risikobewertung
|
||||
riskAssessment: RiskAssessment | null
|
||||
|
||||
// Meldungen
|
||||
authorityNotification: AuthorityNotification | null
|
||||
dataSubjectNotification: DataSubjectNotification | null
|
||||
|
||||
// Massnahmen & Verlauf
|
||||
measures: IncidentMeasure[]
|
||||
timeline: TimelineEntry[]
|
||||
|
||||
// Zuweisung
|
||||
assignedTo?: string
|
||||
|
||||
// Abschluss
|
||||
closedAt?: string
|
||||
lessonsLearned?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
export interface IncidentStatistics {
|
||||
totalIncidents: number
|
||||
openIncidents: number
|
||||
notificationsPending: number
|
||||
averageResponseTimeHours: number
|
||||
bySeverity: Record<IncidentSeverity, number>
|
||||
byCategory: Record<IncidentCategory, number>
|
||||
byStatus: Record<IncidentStatus, number>
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// API TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface IncidentFilters {
|
||||
status?: IncidentStatus | IncidentStatus[]
|
||||
severity?: IncidentSeverity | IncidentSeverity[]
|
||||
category?: IncidentCategory | IncidentCategory[]
|
||||
assignedTo?: string
|
||||
overdue?: boolean
|
||||
search?: string
|
||||
dateFrom?: string
|
||||
dateTo?: string
|
||||
}
|
||||
|
||||
export interface IncidentListResponse {
|
||||
incidents: Incident[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export interface IncidentCreateRequest {
|
||||
title: string
|
||||
description: string
|
||||
category: IncidentCategory
|
||||
severity: IncidentSeverity
|
||||
detectedAt: string
|
||||
detectedBy: string
|
||||
affectedSystems: string[]
|
||||
affectedDataCategories: string[]
|
||||
estimatedAffectedPersons: number
|
||||
assignedTo?: string
|
||||
}
|
||||
|
||||
export interface IncidentUpdateRequest {
|
||||
title?: string
|
||||
description?: string
|
||||
category?: IncidentCategory
|
||||
severity?: IncidentSeverity
|
||||
status?: IncidentStatus
|
||||
affectedSystems?: string[]
|
||||
affectedDataCategories?: string[]
|
||||
estimatedAffectedPersons?: number
|
||||
assignedTo?: string
|
||||
}
|
||||
|
||||
export interface RiskAssessmentRequest {
|
||||
likelihoodScore: number // 1-5
|
||||
impactScore: number // 1-5
|
||||
reasoning: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Berechnet die verbleibenden Stunden bis zur 72h-Meldefrist (Art. 33 DSGVO)
|
||||
*/
|
||||
export function getHoursUntil72hDeadline(detectedAt: string): number {
|
||||
const detected = new Date(detectedAt)
|
||||
const deadline = new Date(detected.getTime() + 72 * 60 * 60 * 1000)
|
||||
const now = new Date()
|
||||
const diff = deadline.getTime() - now.getTime()
|
||||
return Math.round(diff / (1000 * 60 * 60) * 10) / 10
|
||||
}
|
||||
|
||||
/**
|
||||
* Prueft ob die 72-Stunden-Meldefrist abgelaufen ist
|
||||
*/
|
||||
export function is72hDeadlineExpired(detectedAt: string): boolean {
|
||||
const detected = new Date(detectedAt)
|
||||
const deadline = new Date(detected.getTime() + 72 * 60 * 60 * 1000)
|
||||
return new Date() > deadline
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Risikostufe basierend auf Eintrittswahrscheinlichkeit und Auswirkung
|
||||
* Risiko-Matrix:
|
||||
* likelihood x impact >= 20 -> critical
|
||||
* likelihood x impact >= 12 -> high
|
||||
* likelihood x impact >= 6 -> medium
|
||||
* sonst -> low
|
||||
*/
|
||||
export function calculateRiskLevel(likelihood: number, impact: number): IncidentSeverity {
|
||||
const riskScore = likelihood * impact
|
||||
if (riskScore >= 20) return 'critical'
|
||||
if (riskScore >= 12) return 'high'
|
||||
if (riskScore >= 6) return 'medium'
|
||||
return 'low'
|
||||
}
|
||||
|
||||
/**
|
||||
* Prueft ob eine Meldung an die Aufsichtsbehoerde erforderlich ist
|
||||
* Bei hohem oder kritischem Risiko ist eine Meldung gemaess Art. 33 DSGVO erforderlich
|
||||
*/
|
||||
export function isNotificationRequired(riskAssessment: RiskAssessment): boolean {
|
||||
return riskAssessment.overallRisk === 'high' || riskAssessment.overallRisk === 'critical'
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert eine Referenznummer fuer einen Vorfall
|
||||
*/
|
||||
export function generateIncidentReferenceNumber(year: number, sequence: number): string {
|
||||
return `INC-${year}-${String(sequence).padStart(6, '0')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die 72h-Deadline als Date zurueck
|
||||
*/
|
||||
export function get72hDeadline(detectedAt: string): Date {
|
||||
const detected = new Date(detectedAt)
|
||||
return new Date(detected.getTime() + 72 * 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Severity-Info zurueck
|
||||
*/
|
||||
export function getSeverityInfo(severity: IncidentSeverity): IncidentSeverityInfo {
|
||||
return INCIDENT_SEVERITY_INFO[severity]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Status-Info zurueck
|
||||
*/
|
||||
export function getStatusInfo(status: IncidentStatus): IncidentStatusInfo {
|
||||
return INCIDENT_STATUS_INFO[status]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Kategorie-Info zurueck
|
||||
*/
|
||||
export function getCategoryInfo(category: IncidentCategory): IncidentCategoryInfo {
|
||||
return INCIDENT_CATEGORY_INFO[category]
|
||||
}
|
||||
Reference in New Issue
Block a user