0cf3dd6a7b
Defense-in-Depth für alles, was in den ersten 9 Runden nicht durch Code verhindert wurde: zumindest gesehen + alarmiert werden. 📊 SecurityEvent-Tabelle (Prisma) - Type/Severity/IP/User/Endpoint + Indexen für Filter+Threshold-Detection - Trennt sich vom AuditLog: AuditLog ist forensisch + hash-gekettet, SecurityEvent ist optimiert für Realtime-Alerting + Aggregation. 🪝 Hooks an kritischen Stellen - Login (Success/Failed) – auth.controller - Logout, Password-Reset (Request + Confirm) – auth.controller - Rate-Limit-Hit – middleware/rateLimit - IDOR-403 – utils/accessControl (canAccessCustomer / canAccessContract) - SSRF-Block – emailProvider.controller (test-connection + test-mail-access) - JWT-Reject (alg=none, expired, manipuliert) – middleware/auth 🚨 Threshold-Detection + Alerting (securityAlert.service.ts) - Cron jede Minute: prüft Brute-Force-Patterns je IP - 10× LOGIN_FAILED in 60 min → CRITICAL Brute-Force-Verdacht - 5× ACCESS_DENIED in 5 min → CRITICAL IDOR-Probing-Verdacht - 3× SSRF_BLOCKED in 60 min → CRITICAL SSRF-Probing - 3× TOKEN_REJECTED HIGH in 5 min → CRITICAL JWT-Manipulation - CRITICAL-Events: Sofort-Alert per E-Mail (debounced) - Cron stündlich: Digest mit HIGH+MEDIUM-Events (wenn aktiviert) - Sofort-Alert + Digest laufen über System-E-Mail-Provider (gleicher Pfad wie Geburtstagsgrüße, Passwort-Reset) 🖥 Frontend: Settings → "Sicherheits-Monitoring" - Alert-E-Mail-Adresse + Digest-Toggle - Test-Alert-Button + Digest-jetzt-Button - Stats-Cards pro Severity (CRITICAL/HIGH/MEDIUM/LOW/INFO) - Filter (Type/Severity/Search/IP) + Pagination - Auto-Refresh alle 30 s - Verlinkt aus Settings-Übersicht (settings:read Permission) 🧪 Live-verifiziert - Login-Fehlversuch → LOGIN_FAILED Event - Portal probt 4× fremde Customer-IDs → 4× ACCESS_DENIED - SSRF-Probe (169.254.169.254) → SSRF_BLOCKED Event - 12× LOGIN_FAILED simuliert → Cron erzeugt CRITICAL nach ≤60s - CRITICAL-Sofort-Alert binnen 30s zugestellt - Test-Alert-Button: E-Mail zugestellt - Hourly-Digest mit 5 Events: E-Mail mit Tabelle zugestellt Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
231 lines
9.7 KiB
TypeScript
231 lines
9.7 KiB
TypeScript
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useAuth } from './context/AuthContext';
|
|
import { gdprApi } from './services/api';
|
|
import { Shield } from 'lucide-react';
|
|
import ScrollToTop from './components/ScrollToTop';
|
|
import Layout from './components/layout/Layout';
|
|
import Login from './pages/Login';
|
|
import PasswordResetRequest from './pages/PasswordResetRequest';
|
|
import PasswordResetConfirm from './pages/PasswordResetConfirm';
|
|
import Dashboard from './pages/Dashboard';
|
|
import CustomerList from './pages/customers/CustomerList';
|
|
import CustomerDetail from './pages/customers/CustomerDetail';
|
|
import CustomerForm from './pages/customers/CustomerForm';
|
|
import ContractList from './pages/contracts/ContractList';
|
|
import ContractDetail from './pages/contracts/ContractDetail';
|
|
import ContractForm from './pages/contracts/ContractForm';
|
|
import ContractCockpit from './pages/contracts/ContractCockpit';
|
|
import TaskList from './pages/tasks/TaskList';
|
|
import PlatformList from './pages/platforms/PlatformList';
|
|
import CancellationPeriodList from './pages/settings/CancellationPeriodList';
|
|
import ContractDurationList from './pages/settings/ContractDurationList';
|
|
import ProviderList from './pages/settings/ProviderList';
|
|
import ContractCategoryList from './pages/settings/ContractCategoryList';
|
|
import ViewSettings from './pages/settings/ViewSettings';
|
|
import PortalSettings from './pages/settings/PortalSettings';
|
|
import DeadlineSettings from './pages/settings/DeadlineSettings';
|
|
import EmailProviders from './pages/settings/EmailProviders';
|
|
import DatabaseBackup from './pages/settings/DatabaseBackup';
|
|
import FactoryDefaults from './pages/settings/FactoryDefaults';
|
|
import AuditLogs from './pages/settings/AuditLogs';
|
|
import EmailLogPage from './pages/settings/EmailLogs';
|
|
import Monitoring from './pages/settings/Monitoring';
|
|
import GDPRDashboard from './pages/settings/GDPRDashboard';
|
|
import UserList from './pages/users/UserList';
|
|
import Settings from './pages/Settings';
|
|
import DatabaseStructure from './pages/developer/DatabaseStructure';
|
|
import ConsentPage from './pages/public/ConsentPage';
|
|
import PrivacyPolicyEditor from './pages/settings/PrivacyPolicyEditor';
|
|
import PortalPrivacy from './pages/portal/PortalPrivacy';
|
|
import AuthorizationTemplateEditor from './pages/settings/AuthorizationTemplateEditor';
|
|
import ImprintEditor from './pages/settings/ImprintEditor';
|
|
import PdfTemplates from './pages/settings/PdfTemplates';
|
|
import WebsitePrivacyPolicyEditor from './pages/settings/WebsitePrivacyPolicyEditor';
|
|
import PortalImprint from './pages/portal/PortalImprint';
|
|
import PortalWebsitePrivacy from './pages/portal/PortalWebsitePrivacy';
|
|
import PortalAuthorizations from './pages/portal/PortalAuthorizations';
|
|
import PortalProfile from './pages/portal/PortalProfile';
|
|
import PortalMeters from './pages/portal/PortalMeters';
|
|
|
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
const { isAuthenticated, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-gray-500">Laden...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return <Navigate to="/login" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function PortalConsentGate({ children }: { children: React.ReactNode }) {
|
|
const { isCustomerPortal } = useAuth();
|
|
|
|
// Nicht-Portal-Benutzer werden nicht eingeschränkt
|
|
if (!isCustomerPortal) return <>{children}</>;
|
|
|
|
// Portal-Kunden: Consent-Check läuft über Layout ConsentBanner + hier
|
|
return <PortalConsentCheck>{children}</PortalConsentCheck>;
|
|
}
|
|
|
|
function PortalConsentCheck({ children }: { children: React.ReactNode }) {
|
|
const { data, isLoading, isError } = useQuery({
|
|
queryKey: ['my-consent-status'],
|
|
queryFn: () => gdprApi.getMyConsentStatus(),
|
|
staleTime: 30_000,
|
|
retry: 1,
|
|
});
|
|
|
|
if (isLoading) return <div className="text-center py-8 text-gray-500">Laden...</div>;
|
|
|
|
// Bei Fehler oder fehlender Einwilligung: sperren
|
|
const hasConsent = (!isError && data?.data?.hasConsent) ?? false;
|
|
|
|
if (!hasConsent) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-16 px-6 text-center">
|
|
<div className="bg-amber-50 border border-amber-200 rounded-full p-4 mb-4">
|
|
<Shield className="w-8 h-8 text-amber-500" />
|
|
</div>
|
|
<h2 className="text-xl font-semibold text-gray-800 mb-2">
|
|
Datenschutz-Einwilligung erforderlich
|
|
</h2>
|
|
<p className="text-sm text-gray-600 mb-6 max-w-md">
|
|
Dieser Bereich ist erst verfügbar, wenn Sie unserer Datenschutzerklärung zugestimmt haben.
|
|
</p>
|
|
<a
|
|
href="/privacy"
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
|
>
|
|
<Shield className="w-4 h-4" />
|
|
Jetzt zur Datenschutzerklärung
|
|
</a>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function DeveloperRoute({ children }: { children: React.ReactNode }) {
|
|
const { hasPermission, developerMode } = useAuth();
|
|
|
|
// Require both developer permission AND developer mode enabled
|
|
if (!hasPermission('developer:access') || !developerMode) {
|
|
return <Navigate to="/" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function App() {
|
|
const { isAuthenticated, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-gray-500">Laden...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<ScrollToTop />
|
|
<Routes>
|
|
{/* Öffentliche Routes (OHNE Authentifizierung) */}
|
|
<Route path="/datenschutz/:hash" element={<ConsentPage />} />
|
|
|
|
<Route
|
|
path="/login"
|
|
element={isAuthenticated ? <Navigate to="/" replace /> : <Login />}
|
|
/>
|
|
|
|
{/* Passwort-Reset (öffentlich, kein Auth-Check) */}
|
|
<Route path="/password-reset/request" element={<PasswordResetRequest />} />
|
|
<Route path="/password-reset" element={<PasswordResetConfirm />} />
|
|
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<ProtectedRoute>
|
|
<Layout />
|
|
</ProtectedRoute>
|
|
}
|
|
>
|
|
<Route index element={<Dashboard />} />
|
|
|
|
{/* Customers */}
|
|
<Route path="customers" element={<CustomerList />} />
|
|
<Route path="customers/new" element={<CustomerForm />} />
|
|
<Route path="customers/:id" element={<CustomerDetail />} />
|
|
<Route path="customers/:id/edit" element={<CustomerForm />} />
|
|
|
|
{/* Contracts */}
|
|
<Route path="contracts" element={<PortalConsentGate><ContractList /></PortalConsentGate>} />
|
|
<Route path="contracts/cockpit" element={<ContractCockpit />} />
|
|
<Route path="contracts/new" element={<ContractForm />} />
|
|
<Route path="contracts/:id" element={<PortalConsentGate><ContractDetail /></PortalConsentGate>} />
|
|
<Route path="contracts/:id/edit" element={<ContractForm />} />
|
|
|
|
{/* Tasks / Support-Anfragen */}
|
|
<Route path="tasks" element={<TaskList />} />
|
|
|
|
{/* Portal: Meine Daten, Datenschutz, Vollmachten */}
|
|
<Route path="my-profile" element={<PortalProfile />} />
|
|
<Route path="my-meters" element={<PortalMeters />} />
|
|
<Route path="privacy" element={<PortalPrivacy />} />
|
|
<Route path="imprint" element={<PortalImprint />} />
|
|
<Route path="website-privacy" element={<PortalWebsitePrivacy />} />
|
|
<Route path="authorizations" element={<PortalAuthorizations />} />
|
|
|
|
{/* Settings */}
|
|
<Route path="settings" element={<Settings />} />
|
|
<Route path="settings/users" element={<UserList />} />
|
|
<Route path="settings/platforms" element={<PlatformList />} />
|
|
<Route path="settings/cancellation-periods" element={<CancellationPeriodList />} />
|
|
<Route path="settings/contract-durations" element={<ContractDurationList />} />
|
|
<Route path="settings/providers" element={<ProviderList />} />
|
|
<Route path="settings/contract-categories" element={<ContractCategoryList />} />
|
|
<Route path="settings/view" element={<ViewSettings />} />
|
|
<Route path="settings/portal" element={<PortalSettings />} />
|
|
<Route path="settings/deadlines" element={<DeadlineSettings />} />
|
|
<Route path="settings/email-providers" element={<EmailProviders />} />
|
|
<Route path="settings/database-backup" element={<DatabaseBackup />} />
|
|
<Route path="settings/factory-defaults" element={<FactoryDefaults />} />
|
|
<Route path="settings/audit-logs" element={<AuditLogs />} />
|
|
<Route path="settings/email-logs" element={<EmailLogPage />} />
|
|
<Route path="settings/monitoring" element={<Monitoring />} />
|
|
<Route path="settings/gdpr" element={<GDPRDashboard />} />
|
|
<Route path="settings/privacy-policy" element={<PrivacyPolicyEditor />} />
|
|
<Route path="settings/authorization-template" element={<AuthorizationTemplateEditor />} />
|
|
<Route path="settings/pdf-templates" element={<PdfTemplates />} />
|
|
<Route path="settings/imprint" element={<ImprintEditor />} />
|
|
<Route path="settings/website-privacy-policy" element={<WebsitePrivacyPolicyEditor />} />
|
|
|
|
{/* Redirect old users route */}
|
|
<Route path="users" element={<Navigate to="/settings/users" replace />} />
|
|
|
|
{/* Redirect old platforms route */}
|
|
<Route path="platforms" element={<Navigate to="/settings/platforms" replace />} />
|
|
|
|
{/* Developer */}
|
|
<Route path="developer/database" element={<DeveloperRoute><DatabaseStructure /></DeveloperRoute>} />
|
|
</Route>
|
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default App;
|