From ca5d240cf6a45f8e02cbe84c4ea735c6ac804511 Mon Sep 17 00:00:00 2001 From: Fidelis Huber Date: Fri, 21 Aug 2026 18:33:36 +0200 Subject: [PATCH] Make admin seed idempotent-upsert so a corrupted password can be fixed by restart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grund: $ in SEED_ADMIN_PASSWORD wird von Docker-Compose/Portainer als Variablen-Referenz interpretiert und kann dadurch verstümmelt beim Container ankommen. Seed synct das Passwort jetzt bei jedem Start neu, solange die Variable gesetzt ist, statt nur einmalig anzulegen. README um Warnhinweis ergänzt. Co-Authored-By: Claude Sonnet 5 --- README.md | 19 +++++++++++++------ server/src/index.ts | 17 ++++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index cea8ef9..63737bb 100644 --- a/README.md +++ b/README.md @@ -102,14 +102,21 @@ Benutzername anmelden". Damit das funktioniert, muss im Portainer-Stack `JWT_SEC gesetzt sein (Schritt 4). Ein erstes Konto legt der Server **automatisch beim Start** an, wenn -`SEED_ADMIN_USERNAME` und `SEED_ADMIN_PASSWORD` als Stack-Env gesetzt sind (idempotent – -läuft bei jedem Neustart mit, legt aber nur an, was noch nicht existiert). Das -Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration, nie im Git-Repo – -der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank. +`SEED_ADMIN_USERNAME` und `SEED_ADMIN_PASSWORD` als Stack-Env gesetzt sind (Upsert – +läuft bei jedem Neustart mit und synchronisiert das Passwort neu, solange die Variable +gesetzt ist – ein falsch übernommenes Passwort lässt sich also einfach per Neustart +korrigieren). Das Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration, +nie im Git-Repo – der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank. + +**Vorsicht bei `$` im Passwort:** Docker-Compose/Portainer interpretieren `$` in +Env-Werten als Beginn einer Variablen-Referenz (`$xyz` wird als `${xyz}` gelesen und bei +fehlender Definition oft still zu leer aufgelöst) – ein Passwort mit `$` kann dadurch +unbemerkt anders ankommen, als eingetippt. Entweder jedes `$` im Passwortwert als `$$` +escapen, oder – einfacher – ein Passwort ohne `$` verwenden. Optional danach `SEED_ADMIN_PASSWORD` aus dem Stack wieder entfernen (Härtung), sobald -das Konto einmal erfolgreich angelegt wurde – `SEED_ADMIN_USERNAME` kann stehen bleiben, -ohne Passwort passiert dann nichts mehr. Weitere lokale Konten aktuell nur direkt in der +das Konto wie gewünscht funktioniert – `SEED_ADMIN_USERNAME` kann stehen bleiben, ohne +Passwort-Variable wird nichts mehr verändert. Weitere lokale Konten aktuell nur direkt in der Datenbank anlegbar (`INSERT INTO users …` mit einem bcrypt-Hash) – ein Verwaltungs-UI dafür gibt es noch nicht. diff --git a/server/src/index.ts b/server/src/index.ts index b4ab84a..cdd55fa 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -31,18 +31,21 @@ app.get(/^(?!\/api\/).*/, (_req, res) => { res.sendFile(path.join(PUBLIC_DIR, 'index.html')); }); -// Legt beim ersten Start ein lokales Fallback-Konto an, falls SEED_ADMIN_USERNAME/ -// SEED_ADMIN_PASSWORD gesetzt sind und noch kein Konto mit diesem Namen existiert. -// Idempotent – kann bei jedem Neustart stehen bleiben, legt nichts doppelt an. +// Legt beim Start ein lokales Fallback-Konto an bzw. hält es synchron, solange +// SEED_ADMIN_USERNAME/SEED_ADMIN_PASSWORD gesetzt sind (Upsert – so lässt sich ein +// falsch übernommenes Passwort einfach per Neustart korrigieren). Danach die +// SEED_ADMIN_PASSWORD-Variable aus dem Stack entfernen, um das Konto "einzufrieren". async function seedAdminIfConfigured() { const username = process.env.SEED_ADMIN_USERNAME; const password = process.env.SEED_ADMIN_PASSWORD; if (!username || !password) return; - const existing = await prisma.user.findUnique({ where: { username } }); - if (existing) return; const passwordHash = await bcrypt.hash(password, 12); - await prisma.user.create({ data: { username, passwordHash, name: username } }); - console.log(`Lokales Konto "${username}" angelegt.`); + await prisma.user.upsert({ + where: { username }, + update: { passwordHash }, + create: { username, passwordHash, name: username } + }); + console.log(`Lokales Konto "${username}" angelegt/aktualisiert.`); } seedAdminIfConfigured()