Some checks failed
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
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
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
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
423 lines
14 KiB
TypeScript
423 lines
14 KiB
TypeScript
'use client'
|
|
|
|
// =============================================================================
|
|
// Step 5: Risk & Protection Level
|
|
// CIA assessment and protection level determination
|
|
// =============================================================================
|
|
|
|
import React, { useState, useEffect } from 'react'
|
|
import { useTOMGenerator } from '@/lib/sdk/tom-generator'
|
|
import {
|
|
RiskProfile,
|
|
CIARating,
|
|
ProtectionLevel,
|
|
calculateProtectionLevel,
|
|
isDSFARequired,
|
|
} from '@/lib/sdk/tom-generator/types'
|
|
|
|
// =============================================================================
|
|
// CONSTANTS
|
|
// =============================================================================
|
|
|
|
const CIA_LEVELS: { value: CIARating; label: string; description: string }[] = [
|
|
{ value: 1, label: 'Sehr gering', description: 'Kein nennenswerter Schaden bei Verletzung' },
|
|
{ value: 2, label: 'Gering', description: 'Begrenzter, beherrschbarer Schaden' },
|
|
{ value: 3, label: 'Mittel', description: 'Erheblicher Schaden, aber kompensierbar' },
|
|
{ value: 4, label: 'Hoch', description: 'Schwerwiegender Schaden, schwer kompensierbar' },
|
|
{ value: 5, label: 'Sehr hoch', description: 'Existenzbedrohender oder irreversibler Schaden' },
|
|
]
|
|
|
|
const REGULATORY_REQUIREMENTS = [
|
|
'DSGVO',
|
|
'BDSG',
|
|
'MaRisk (Finanz)',
|
|
'BAIT (Finanz)',
|
|
'PSD2 (Zahlungsdienste)',
|
|
'SGB (Gesundheit)',
|
|
'MDR (Medizinprodukte)',
|
|
'TISAX (Automotive)',
|
|
'KRITIS (Kritische Infrastruktur)',
|
|
'NIS2',
|
|
'ISO 27001',
|
|
'SOC 2',
|
|
]
|
|
|
|
// =============================================================================
|
|
// CIA SLIDER COMPONENT
|
|
// =============================================================================
|
|
|
|
interface CIASliderProps {
|
|
label: string
|
|
description: string
|
|
value: CIARating
|
|
onChange: (value: CIARating) => void
|
|
}
|
|
|
|
function CIASlider({ label, description, value, onChange }: CIASliderProps) {
|
|
const level = CIA_LEVELS.find((l) => l.value === value)
|
|
|
|
const getColor = (v: CIARating) => {
|
|
if (v <= 2) return 'bg-green-500'
|
|
if (v === 3) return 'bg-yellow-500'
|
|
return 'bg-red-500'
|
|
}
|
|
|
|
return (
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<div className="flex justify-between items-start mb-3">
|
|
<div>
|
|
<h4 className="font-medium text-gray-900">{label}</h4>
|
|
<p className="text-sm text-gray-500">{description}</p>
|
|
</div>
|
|
<span className={`px-3 py-1 rounded-full text-sm font-medium text-white ${getColor(value)}`}>
|
|
{level?.label}
|
|
</span>
|
|
</div>
|
|
|
|
<input
|
|
type="range"
|
|
min="1"
|
|
max="5"
|
|
value={value}
|
|
onChange={(e) => onChange(parseInt(e.target.value) as CIARating)}
|
|
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
|
|
/>
|
|
|
|
<div className="flex justify-between mt-1 text-xs text-gray-400">
|
|
<span>1</span>
|
|
<span>2</span>
|
|
<span>3</span>
|
|
<span>4</span>
|
|
<span>5</span>
|
|
</div>
|
|
|
|
<p className="text-sm text-gray-600 mt-2 italic">{level?.description}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// PROTECTION LEVEL DISPLAY
|
|
// =============================================================================
|
|
|
|
interface ProtectionLevelDisplayProps {
|
|
level: ProtectionLevel
|
|
}
|
|
|
|
function ProtectionLevelDisplay({ level }: ProtectionLevelDisplayProps) {
|
|
const config: Record<ProtectionLevel, { label: string; color: string; bg: string; description: string }> = {
|
|
NORMAL: {
|
|
label: 'Normal',
|
|
color: 'text-green-800',
|
|
bg: 'bg-green-100',
|
|
description: 'Standard-Schutzmaßnahmen ausreichend',
|
|
},
|
|
HIGH: {
|
|
label: 'Hoch',
|
|
color: 'text-yellow-800',
|
|
bg: 'bg-yellow-100',
|
|
description: 'Erweiterte Schutzmaßnahmen erforderlich',
|
|
},
|
|
VERY_HIGH: {
|
|
label: 'Sehr hoch',
|
|
color: 'text-red-800',
|
|
bg: 'bg-red-100',
|
|
description: 'Höchste Schutzmaßnahmen erforderlich',
|
|
},
|
|
}
|
|
|
|
const { label, color, bg, description } = config[level]
|
|
|
|
return (
|
|
<div className={`${bg} rounded-lg p-4 border`}>
|
|
<div className="flex items-center gap-3">
|
|
<div className={`text-2xl font-bold ${color}`}>{label}</div>
|
|
</div>
|
|
<p className={`text-sm ${color} mt-1`}>{description}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// COMPONENT
|
|
// =============================================================================
|
|
|
|
export function RiskProtectionStep() {
|
|
const { state, setRiskProfile, completeCurrentStep } = useTOMGenerator()
|
|
|
|
const [formData, setFormData] = useState<Partial<RiskProfile>>({
|
|
ciaAssessment: {
|
|
confidentiality: 3,
|
|
integrity: 3,
|
|
availability: 3,
|
|
justification: '',
|
|
},
|
|
protectionLevel: 'HIGH',
|
|
specialRisks: [],
|
|
regulatoryRequirements: ['DSGVO'],
|
|
hasHighRiskProcessing: false,
|
|
dsfaRequired: false,
|
|
})
|
|
|
|
const [specialRiskInput, setSpecialRiskInput] = useState('')
|
|
|
|
// Load existing data
|
|
useEffect(() => {
|
|
if (state.riskProfile) {
|
|
setFormData(state.riskProfile)
|
|
}
|
|
}, [state.riskProfile])
|
|
|
|
// Calculate protection level when CIA changes
|
|
useEffect(() => {
|
|
if (formData.ciaAssessment) {
|
|
const level = calculateProtectionLevel(formData.ciaAssessment)
|
|
const dsfaReq = isDSFARequired(state.dataProfile, {
|
|
...formData,
|
|
protectionLevel: level,
|
|
} as RiskProfile)
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
protectionLevel: level,
|
|
dsfaRequired: dsfaReq,
|
|
}))
|
|
}
|
|
}, [formData.ciaAssessment, state.dataProfile])
|
|
|
|
// Handle CIA changes
|
|
const handleCIAChange = (field: 'confidentiality' | 'integrity' | 'availability', value: CIARating) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
ciaAssessment: {
|
|
...prev.ciaAssessment!,
|
|
[field]: value,
|
|
},
|
|
}))
|
|
}
|
|
|
|
// Handle regulatory requirements toggle
|
|
const toggleRequirement = (req: string) => {
|
|
setFormData((prev) => {
|
|
const current = prev.regulatoryRequirements || []
|
|
const updated = current.includes(req)
|
|
? current.filter((r) => r !== req)
|
|
: [...current, req]
|
|
return { ...prev, regulatoryRequirements: updated }
|
|
})
|
|
}
|
|
|
|
// Handle special risk addition
|
|
const addSpecialRisk = () => {
|
|
if (specialRiskInput.trim()) {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
specialRisks: [...(prev.specialRisks || []), specialRiskInput.trim()],
|
|
}))
|
|
setSpecialRiskInput('')
|
|
}
|
|
}
|
|
|
|
const removeSpecialRisk = (index: number) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
specialRisks: (prev.specialRisks || []).filter((_, i) => i !== index),
|
|
}))
|
|
}
|
|
|
|
// Handle submit
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
const profile: RiskProfile = {
|
|
ciaAssessment: formData.ciaAssessment!,
|
|
protectionLevel: formData.protectionLevel || 'HIGH',
|
|
specialRisks: formData.specialRisks || [],
|
|
regulatoryRequirements: formData.regulatoryRequirements || [],
|
|
hasHighRiskProcessing: formData.hasHighRiskProcessing || false,
|
|
dsfaRequired: formData.dsfaRequired || false,
|
|
}
|
|
|
|
setRiskProfile(profile)
|
|
completeCurrentStep(profile)
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-8">
|
|
{/* CIA Assessment */}
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">CIA-Bewertung</h3>
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
Bewerten Sie die Schutzziele für Ihre Datenverarbeitung. Was passiert, wenn die Vertraulichkeit,
|
|
Integrität oder Verfügbarkeit der Daten beeinträchtigt wird?
|
|
</p>
|
|
|
|
<div className="space-y-4">
|
|
<CIASlider
|
|
label="Vertraulichkeit (Confidentiality)"
|
|
description="Schutz vor unbefugtem Zugriff auf Daten"
|
|
value={formData.ciaAssessment?.confidentiality || 3}
|
|
onChange={(v) => handleCIAChange('confidentiality', v)}
|
|
/>
|
|
|
|
<CIASlider
|
|
label="Integrität (Integrity)"
|
|
description="Schutz vor unbefugter Änderung von Daten"
|
|
value={formData.ciaAssessment?.integrity || 3}
|
|
onChange={(v) => handleCIAChange('integrity', v)}
|
|
/>
|
|
|
|
<CIASlider
|
|
label="Verfügbarkeit (Availability)"
|
|
description="Sicherstellung des Zugriffs auf Daten"
|
|
value={formData.ciaAssessment?.availability || 3}
|
|
onChange={(v) => handleCIAChange('availability', v)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Justification */}
|
|
<div className="mt-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Begründung der Bewertung
|
|
</label>
|
|
<textarea
|
|
value={formData.ciaAssessment?.justification || ''}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
ciaAssessment: {
|
|
...prev.ciaAssessment!,
|
|
justification: e.target.value,
|
|
},
|
|
}))
|
|
}
|
|
rows={3}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
placeholder="Beschreiben Sie kurz, warum Sie diese Bewertung gewählt haben..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Calculated Protection Level */}
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Ermittelter Schutzbedarf</h3>
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
Basierend auf Ihrer CIA-Bewertung ergibt sich folgender Schutzbedarf:
|
|
</p>
|
|
|
|
<ProtectionLevelDisplay level={formData.protectionLevel || 'HIGH'} />
|
|
</div>
|
|
|
|
{/* DSFA Indicator */}
|
|
{formData.dsfaRequired && (
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
|
<div className="flex gap-3">
|
|
<svg className="w-6 h-6 text-red-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
|
</svg>
|
|
<div>
|
|
<h4 className="font-medium text-red-900">DSFA erforderlich</h4>
|
|
<p className="text-sm text-red-700 mt-1">
|
|
Aufgrund Ihrer Datenverarbeitung (besondere Kategorien, Minderjährige oder sehr hoher Schutzbedarf)
|
|
ist eine Datenschutz-Folgenabschätzung nach Art. 35 DSGVO erforderlich.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* High Risk Processing */}
|
|
<div>
|
|
<label className="flex items-center gap-3 cursor-pointer p-3 border rounded-lg hover:bg-gray-50">
|
|
<input
|
|
type="checkbox"
|
|
checked={formData.hasHighRiskProcessing || false}
|
|
onChange={(e) => setFormData((prev) => ({ ...prev, hasHighRiskProcessing: e.target.checked }))}
|
|
className="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
/>
|
|
<div>
|
|
<span className="text-gray-700 font-medium">Hochrisiko-Verarbeitung</span>
|
|
<p className="text-sm text-gray-500">
|
|
z.B. Profiling, automatisierte Entscheidungen, systematische Überwachung
|
|
</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
{/* Special Risks */}
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Besondere Risiken</h3>
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
Identifizieren Sie spezifische Risiken Ihrer Datenverarbeitung.
|
|
</p>
|
|
|
|
<div className="flex gap-2 mb-3">
|
|
<input
|
|
type="text"
|
|
value={specialRiskInput}
|
|
onChange={(e) => setSpecialRiskInput(e.target.value)}
|
|
onKeyPress={(e) => e.key === 'Enter' && (e.preventDefault(), addSpecialRisk())}
|
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
placeholder="z.B. Cloud-Abhängigkeit, Insider-Bedrohungen"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={addSpecialRisk}
|
|
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200"
|
|
>
|
|
Hinzufügen
|
|
</button>
|
|
</div>
|
|
|
|
{formData.specialRisks && formData.specialRisks.length > 0 && (
|
|
<div className="flex flex-wrap gap-2">
|
|
{formData.specialRisks.map((risk, index) => (
|
|
<span
|
|
key={index}
|
|
className="inline-flex items-center gap-1 px-3 py-1 bg-red-100 text-red-800 rounded-full text-sm"
|
|
>
|
|
{risk}
|
|
<button
|
|
type="button"
|
|
onClick={() => removeSpecialRisk(index)}
|
|
className="hover:text-red-600"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Regulatory Requirements */}
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Regulatorische Anforderungen</h3>
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
Welche regulatorischen Anforderungen gelten für Ihre Datenverarbeitung?
|
|
</p>
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
{REGULATORY_REQUIREMENTS.map((req) => (
|
|
<button
|
|
key={req}
|
|
type="button"
|
|
onClick={() => toggleRequirement(req)}
|
|
className={`px-4 py-2 rounded-full border text-sm font-medium transition-all ${
|
|
formData.regulatoryRequirements?.includes(req)
|
|
? 'bg-blue-100 border-blue-300 text-blue-800'
|
|
: 'bg-white border-gray-300 text-gray-600 hover:border-gray-400'
|
|
}`}
|
|
>
|
|
{req}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</form>
|
|
)
|
|
}
|
|
|
|
export default RiskProtectionStep
|