Authentifizierung & Sicherheit¶
Anmeldung und Sessions¶
- Teammitglieder melden sich mit E-Mail und Passwort gegen die Tabelle
usersan (/admin/login). - Passwörter werden mit scrypt gehasht (
lib/auth/password.ts). Es wird keine externe Auth-Bibliothek verwendet. - Die Session ist ein HMAC-signiertes httpOnly-Cookie (12 Stunden), signiert mit
SESSION_SECRET. Es enthält die User-ID und bei Datenbank-Usern zusätzlich einesession_version. - Die Rolle wird bei jedem Request neu aus der Datenbank gelesen (
getCurrentUser). Rollenänderungen wirken daher sofort. updateUserActionerhöhtusers.session_version, wenn sich Passwort oder Rolle ändern. Alle bestehenden Cookies dieses Nutzers werden ungültig, auch das des auslösenden Admins.
Startprüfung für SESSION_SECRET¶
Beim Serverstart prüft instrumentation.ts (register(), nur in der Node-Runtime) das Secret über checkSessionSecret (lib/env/session-secret.ts):
- Fehlt
SESSION_SECREToder ist es leer, wirftregister(), und der Server startet nicht. - Ist es kürzer als 32 Zeichen (
SESSION_SECRET_MIN_LENGTH), wird nur eine Warnung geloggt. next buildführt die Prüfung nicht aus. Der Worker braucht das Secret nicht.
SESSION_SECRET ist außerdem der Schlüssel für die Adress-Zähler des Formular-Rate-Limits (siehe unten). Wird es geändert, sind alle Sessions ungültig und die Adress-Zähler zurückgesetzt.
Env-Bootstrap-Admin¶
ADMIN_PASSWORD_HASH ermöglicht eine Bootstrap-Anmeldung mit der E-Mail aus ADMIN_EMAIL (Standard admin@status.local). Sie wird nur akzeptiert, solange kein Datenbank-Admin existiert. Solange sie genutzt wird, zeigt jede Admin-Seite einen dauerhaften Hinweis. Mit ADMIN_BOOTSTRAP_DISABLED_AFTER (ISO-Datum) wird diese Anmeldung ab einem Zeitpunkt abgeschaltet, sowohl für neue Anmeldungen als auch für laufende Sessions. Ein Datum in der Vergangenheit wirkt sofort. Ein leerer oder nicht lesbarer Wert schaltet nichts ab.
Warning
Legen Sie direkt nach der Installation einen Datenbank-Admin an und entfernen Sie danach ADMIN_PASSWORD_HASH aus der Umgebung.
Wiederherstellung bei Aussperrung¶
Ist ADMIN_BOOTSTRAP_DISABLED_AFTER gesetzt und das Datum vorbei, während kein Datenbank-Admin (oder keiner mit funktionierendem Zugang) existiert, sind Sie ausgesperrt. Zwei Wege zurück:
Regulärer Weg (Zugriff auf die Env vorhanden):
ADMIN_BOOTSTRAP_DISABLED_AFTERentfernen oder auf ein zukünftiges Datum setzen.- App neu starten und mit dem Bootstrap-Login (
ADMIN_PASSWORD_HASH) anmelden. - Unter Users einen Datenbank-Admin anlegen.
ADMIN_BOOTSTRAP_DISABLED_AFTERwieder setzen (undADMIN_PASSWORD_HASHentfernen) und die App neu starten.
Notweg (kein Zugriff auf die Env): Passwort-Hash mit node scripts/hash-admin-password.mjs erzeugen und einen Admin direkt per SQL anlegen. Spaltennamen und Admin-Rollenwert stammen aus users in lib/db/schema.ts:
INSERT INTO users (email, password_hash, name, role)
VALUES ('admin@example.com', '<erzeugter scrypt-Hash>', 'Admin', 'admin');
Warning
pnpm db:seed niemals in Produktion verwenden — es löscht bestehende Daten.
Autorisierung¶
Die Autorisierung besteht aus zwei Ebenen.
1. Rollenmatrix. Die Rollen und ihre Berechtigungen sind in lib/domain/users.ts definiert (ROLES_CAN_*). Die Tabelle beschreibt, was die Admin-Oberfläche jeder Rolle anbietet:
| Rolle | Zusammenfassung |
|---|---|
admin |
Alles, inklusive Benutzerverwaltung, Einstellungen, Benachrichtigungen und Audit-Log |
service_manager |
Betreibt alle Komponenten: Service-Status, Ausfälle, Incidents, Wartungen, interne Notizen und Komponentenkonfiguration. Bestätigt Ausfälle in SLA-Reports. |
service_owner |
Setzt den Status seiner eigenen Services, legt für sie Incidents und Wartungen an, pflegt deren Ausfälle, Incident-Updates, Reviews und Wartungen, schließt sie ab, trägt die exakten Ausfallzeiten ein, bestätigt deren Ausfälle in SLA-Reports und sieht SLA-Reports |
sla_manager |
Verwaltet SLA-Pläne, Feiertagslisten, Ziele und Reports (starten, generieren). Pflegt und bestätigt Ausfälle aller Services und pflegt Incident-Updates, -Reviews und Wartungen. Sonst nur Lesezugriff. |
2. Komponentenverantwortung. Operative Server Actions (Status setzen, Incident melden, Wartung planen, Incidents und Wartungen abschließen) sind erlaubt für admin, service_manager oder einen Nutzer, der für alle betroffenen Komponenten verantwortlich ist (components.responsible_user_id). Für Ausfälle, Incident-Updates, Reviews und Wartungs-Updates zählt zusätzlich sla_manager als voll berechtigt. Die Helfer userMayOperateOnComponents und userMayManageOutageComponents in lib/auth/components.ts setzen das um. Eine leere Komponentenmenge gilt nie als „verantwortlich“.
Info
Die Admin-Oberfläche spiegelt die Server-Actions wider (seit Issue #361): Die Anlege-Formulare für Incidents und Wartungen sind über ROLES_CAN_START_INCIDENTS_AND_MAINTENANCES für admin, service_manager und service_owner zugänglich; Serviceowner sehen in der Service-Auswahl nur ihre eigenen Services (filterGroupsToOwnedComponents). Interne Notizen bleiben admin und service_manager vorbehalten (ROLES_CAN_WRITE_INCIDENTS).
Info
Die Prüfungen laufen in jeder Seite und jeder Server Action, nicht nur in Layouts. Layouts werden parallel zur Seite gerendert und sind allein keine verlässliche Sicherheitsgrenze.
Öffentliche Endpunkte ohne Anmeldung¶
- Statusseite, Historie, Service-Detail, Badge, RSS, Rechtstexte.
- Abonnements laufen über Double Opt-in (Bestätigung per E-Mail).
- Abmeldung und Selbstauskunft verlangen einen Token-Link und danach eine POST-Bestätigung. Das Öffnen des Links allein ändert nichts. So sind Prüfdienste, die Links vorab abrufen, unschädlich.
- Die drei Anfrageformulare (Abonnieren, Abmelde-Link, Datenauskunft) sind gedrosselt und antworten neutral, siehe unten.
API-Tokens¶
Bearer-Tokens (Authorization: Bearer …) werden für /api/outage/*, /api/incidents und /api/maintenances verwendet. Tokens werden als SHA-256-Hash gespeichert. Jeder Token kann auf bestimmte Services beschränkt, mit einem Ablaufdatum versehen und widerrufen werden. Siehe Öffentliche API.
Rate-Limiting¶
Anmeldung und API-Tokens¶
- Fehlgeschlagene Anmeldungen und ungültige API-Tokens werden je Client-IP gezählt (Tabelle
auth_throttle). - Standardwerte: Anmeldung 5 Versuche in 15 Minuten, API-Tokens 20 in 5 Minuten, Sperre 15 Minuten. Alle Werte sind über
AUTH_RATELIMIT_*konfigurierbar,AUTH_RATELIMIT_ENABLED=falseschaltet die Drosselung ab. - Ein erfolgreicher Login oder eine gültige Token-Anfrage setzt den Zähler der IP zurück. Ein gültiger Token ohne Berechtigung für den Service (403) zählt nicht als Fehlversuch.
Öffentliche Formulare¶
- Abonnieren, Abmelde-Link anfordern und Datenauskunft anfordern sind gedrosselt (
lib/auth/public-form-throttle.ts). Hier zählt jede Anfrage, nicht nur fehlerhafte. - Zwei Zähler in
auth_throttle: je Client-IP über alle drei Formulare (publicform:ip:<ip>, Standard 10 in 60 Minuten, Sperre 60 Minuten) und je Adresse und Formular (publicform:<art>:email:<hmac>, Standard 1 in 10 Minuten, Sperre 10 Minuten). - Der Adress-Schlüssel ist ein HMAC-SHA-256 der getrimmten, kleingeschriebenen Adresse mit
SESSION_SECRET. Die Adresse selbst wird nicht gespeichert. - Ohne erkennbare Client-IP greift der Sammel-Zähler
unknown(Standard 100 in 60 Minuten, Sperre 60 Minuten). Er wird einmal pro Prozess als Warnung geloggt. - Eine gedrosselte Anfrage antwortet
{ ok: true }ohne Mail, genau wie bei einer unbekannten Adresse. Geloggt wird nur eine Warnung ohne Adresse. - Konfiguration über
PUBLIC_FORM_RATELIMIT_ENABLEDundPUBLIC_FORM_RATELIMIT_{IP,EMAIL,UNKNOWN_IP}_{MAX,WINDOW_MIN,LOCK_MIN}.
Client-IP und Reverse-Proxy¶
Beide Rate-Limits ermitteln die Client-IP über clientIp (lib/auth/rate-limit.ts), gesteuert durch TRUSTED_PROXY_IP_HEADER:
| Wert | Verhalten |
|---|---|
auto (Standard) |
x-real-ip, sonst der letzte Eintrag von x-forwarded-for |
x-real-ip |
Nur x-real-ip. x-forwarded-for wird ignoriert. |
x-forwarded-for |
Nur x-forwarded-for. Gezählt wird von rechts: Bei TRUSTED_PROXY_COUNT=N (Standard 1) gilt der N-te Eintrag von hinten. x-real-ip wird ignoriert. |
- Unbekannte Werte fallen auf
autozurück. Hinter einem Proxy, der nurx-forwarded-forsetzt, kann ein Client im Modusautoeinen eigenenX-Real-IP-Header einschleusen. Setzen Sie dannx-forwarded-for. - Ein Port-Suffix wird entfernt. IPv6-Adressen werden nach RFC 5952 kanonisiert (Zone-Index entfernt, IPv4-mapped als
::ffff:a.b.c.d), damit verschiedene Schreibweisen im selben Zähler landen. - Ist die erkannte IP in Produktion privat oder Loopback (IPv4 127/8, 10/8, 172.16/12, 192.168/16, 100.64/10, 169.254/16; IPv6 ::1, fc00::/7, fe80::/10, auch IPv4-mapped), loggt die App einmal pro Prozess eine Warnung. Meist setzt dann der Proxy keinen IP-Header, und alle Besucher teilen sich die Proxy-IP.
Ausfall des Speichers¶
Fällt der Speicher des Throttles aus, werden Anfragen durchgelassen (Fail-open) und der Fehler protokolliert (tolerateThrottleFailure). Das Rate-Limiting ist eine Schutzmaßnahme, kein hartes Gate.
Geheimnisse und sensible Daten¶
smtp_password,resend_api_key,twilio_auth_token,vapid_private_keyund Webhook-Secrets werden mit AES-256-GCM verschlüsselt, SchlüsselSETTINGS_ENCRYPTION_KEY. Verbraucher erhalten übergetSettings()immer Klartext.- Geheimnisse und Passwort-Hashes werden nie im Audit-Log gespeichert.
- Der Rechtstext zum Datenschutz wird vor der Ausgabe mit einer strikten Whitelist (
h1,h2,h3,b,i) bereinigt.
Audit-Log¶
Alle mutierenden Admin-Aktionen werden in audit_log festgehalten, mit Akteur, Aktion (<entity>.<verb>), Entität und Before/After-Snapshots. Dazu gehören Einstellungen, Benutzer, Komponenten und Gruppen, Statusänderungen, Ausfälle, Incidents, Wartungen, SLA-Pläne, Feiertagslisten, SLA-Report-Schritte, API-Tokens, Webhooks, SMS-Empfänger, Abonnenten, Templates, Resend und das Neuerzeugen der VAPID-Schlüssel.
Nicht protokolliert werden öffentliche Aktionen ohne Admin (Abonnieren, Bestätigen, Abmelden, Datenauskunft, Push-Abo), reine Test-Aktionen (Test-Mail, Test-SMS, Check-Test, Test-Webhook), Login und Logout sowie interne Incident-Notizen. Die Audit-Seite ist auf die Rolle admin beschränkt. Siehe Einstellungen, Benutzer, API-Tokens & Audit-Log.