Warn at startup when no user exists and no bootstrap admin is configured

Also document that Sec-Fetch-Site is only sent over HTTPS, so plain-HTTP
LAN access relies on SameSite=Lax alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-09-28 21:47:30 +02:00
parent 95bfba2f67
commit 728132e52a
2 changed files with 7 additions and 1 deletions

View file

@ -53,6 +53,7 @@ Verknüpfung mit einer Mitarbeiter-/Personen-Stammdatentabelle.
| Passwort-Raten (offline, DB-Diebstahl) | scrypt (N=16384, r=8, p=1), eigenes Salt je Hash | | Passwort-Raten (offline, DB-Diebstahl) | scrypt (N=16384, r=8, p=1), eigenes Salt je Hash |
| Nutzernamen ausspähen (Timing) | Unbekannte Namen werden gegen einen festen Dummy-Hash geprüft – immer genau ein scrypt-Lauf; einheitliche Fehlermeldung | | Nutzernamen ausspähen (Timing) | Unbekannte Namen werden gegen einen festen Dummy-Hash geprüft – immer genau ein scrypt-Lauf; einheitliche Fehlermeldung |
| CSRF | SameSite=Lax **plus** `requireSameOriginForWrites`: schreibende Anfragen nur mit `Sec-Fetch-Site: same-origin`. Nötig, weil andere Dienste auf demselben Host (andere Ports) als „same-site“ gelten | | CSRF | SameSite=Lax **plus** `requireSameOriginForWrites`: schreibende Anfragen nur mit `Sec-Fetch-Site: same-origin`. Nötig, weil andere Dienste auf demselben Host (andere Ports) als „same-site“ gelten |
| CSRF über reines HTTP | Browser senden `Sec-Fetch-Site` nur an HTTPS-Adressen. Beim Zugriff per `http://…` im LAN greift deshalb nur SameSite=Lax (schützt gegen fremde Domains, nicht gegen andere Ports desselben Hosts). |
| Session-Fixation | Neue Session-ID bei jedem Login und nach jedem Passwortwechsel | | Session-Fixation | Neue Session-ID bei jedem Login und nach jedem Passwortwechsel |
| Gestohlenes Cookie | httpOnly; gleitende Laufzeit (`AUTH_SESSION_HOURS`, 12 h) **und** harte Obergrenze (`AUTH_SESSION_MAX_DAYS`, 7 Tage) | | Gestohlenes Cookie | httpOnly; gleitende Laufzeit (`AUTH_SESSION_HOURS`, 12 h) **und** harte Obergrenze (`AUTH_SESSION_MAX_DAYS`, 7 Tage) |
| Kompromittiertes Konto | Passwort-Reset und Deaktivieren beenden sofort alle Sessions des Kontos; Passwortwechsel beendet alle anderen | | Kompromittiertes Konto | Passwort-Reset und Deaktivieren beenden sofort alle Sessions des Kontos; Passwortwechsel beendet alle anderen |

View file

@ -63,9 +63,14 @@ export class UserService {
// Warm the dummy hash at startup so even the first unknown-user login // Warm the dummy hash at startup so even the first unknown-user login
// costs one scrypt run, not two. // costs one scrypt run, not two.
await this.getDummyHash(); await this.getDummyHash();
if (!this.authConfig.bootstrap) return;
const existingCount = await this.repository.count(); const existingCount = await this.repository.count();
if (existingCount > 0) return; if (existingCount > 0) return;
if (!this.authConfig.bootstrap) {
logger.warn(
"no users exist and AUTH_BOOTSTRAP_USERNAME/AUTH_BOOTSTRAP_PASSWORT are not set - nobody can sign in"
);
return;
}
const passwordHash = await hashPassword(this.authConfig.bootstrap.password); const passwordHash = await hashPassword(this.authConfig.bootstrap.password);
await this.repository.create({ await this.repository.create({
username: this.authConfig.bootstrap.username, username: this.authConfig.bootstrap.username,