My-Weekly-ToDo-List/docs/secret-rotation.md
mARTin-B78 230a4034e8
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run
feat: rate limiting, token hashing, remove mock login route
Erste Abarbeitung des Repository-Audits (siehe TODO.md).

Sicherheit:
- Rate-Limiting für alle vier Auth-Endpunkte. Sie waren unbegrenzt aufrufbar,
  also beliebig viele Mails über den eigenen SMTP-Zugang und beliebig viele
  Rateversuche auf Verifizierungscodes.
- Passwort-Reset-Token nur noch als SHA-256-Hash in der Datenbank. Bisher
  Klartext: Lesezugriff auf die DB genuegte zur Kontouebernahme.
- Tote Mock-Login-Route entfernt. Sie verglich gegen ein hartcodiertes
  Fake-Passwort und war ueber die Middleware-Ausnahme fuer /api/auth/*
  ohne Anmeldung erreichbar.
- Passwort-Reset auf bcrypt-Kosten 12, wie beim Signup.

Reparaturen:
- npm test rief jest auf, obwohl es weder Jest-Config noch Jest-Tests gibt.
  Zeigt jetzt auf Playwright; jest/ts-jest/@types/jest entfernt.
- Falscher sonar.projectKey im Workflow.

Verifiziert per tsc gegen die geaenderten Bereiche: keine neuen Typfehler.

v1.117.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:01:30 +02:00

5.6 KiB
Raw Blame History

Secrets rotieren

Anleitung zum Austausch der Zugangsdaten, die versehentlich in .env.production.save im Git gelandet sind.

Grundregel: Kein Befehl in dieser Anleitung gibt einen Geheimwert auf dem Bildschirm aus. Die neuen Werte werden direkt in die Datei geschrieben. Du siehst sie nie, ich sehe sie nie, und sie landen nicht in der Shell-History (dort steht nur der Befehl, nicht sein Ergebnis).


Vorab: Was passiert mit den Daten?

Kurz: Es geht nichts verloren. Im Einzelnen:

Was du änderst Folge für die Daten Folge für die Nutzer
NEXTAUTH_SECRET keine Alle werden ausgeloggt, Login danach normal möglich
Datenbank-Passwort keine keine
SMTP_PASSWORD keine keine

Wichtig zu verstehen: Die Passwörter der Nutzer hängen nicht am NEXTAUTH_SECRET. Sie liegen als bcrypt-Hashes in der Datenbank und bleiben davon völlig unberührt. NEXTAUTH_SECRET signiert nur die Sitzungs-Cookies. Wird es getauscht, sind die ausgestellten Cookies ungültig — jeder muss sich einmal neu anmelden, mit dem gewohnten Passwort. Danach ist alles wie vorher.

Beim Datenbank-Passwort änderst du nur die Zugangsdaten, nicht die Datenbank. Tabellen und Inhalte bleiben unangetastet.


Schritt 0 — Sicherheitsnetz

Auf dem Container:

cd /root/My-Weekly-ToDo-List
cp .env .env.backup-$(date +%F)
chmod 600 .env.backup-*
ls -l .env.backup-*

Falls etwas schiefgeht, kommst du damit zurück. Die Sicherung liegt außerhalb von Git (.env* ist ignoriert) — bitte trotzdem später aufräumen.

Zusätzlich ein Datenbank-Backup, bevor du am DB-Zugang drehst:

bash /root/My-Weekly-ToDo-List/scripts/backup-db.sh

Schritt 1 — NEXTAUTH_SECRET neu erzeugen

Ein Befehl, der den neuen Wert erzeugt und direkt in die Datei schreibt:

cd /root/My-Weekly-ToDo-List
sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=\"$(openssl rand -base64 32)\"|" .env

Kontrolle, ohne den Wert zu zeigen — es wird nur die Länge ausgegeben:

awk -F= '/^NEXTAUTH_SECRET=/{v=substr($0,index($0,"=")+1); gsub(/"/,"",v); print "Länge:", length(v), "Zeichen"}' .env

Erwartet: 44 Zeichen (32 Byte in Base64). Der alte Wert hatte 20 — also deutlich zu kurz.


Schritt 2 — Datenbank-Passwort rotieren

Das passiert bei Supabase, nicht auf dem Server:

  1. Supabase-Projekt öffnen → Settings → Database
  2. Reset database password — neues Passwort erzeugen lassen
  3. Passwort in deinen Passwort-Manager, nicht in eine Datei kopieren
  4. Supabase zeigt dir die fertigen Connection-Strings für DATABASE_URL (Pooler, Port 6543) und DIRECT_URL (direkt, Port 5432)

Auf dem Container einsetzen — dieser Weg zeigt die Eingabe nicht an:

cd /root/My-Weekly-ToDo-List

read -rsp "Neue DATABASE_URL einfügen: " V && printf '\n' && \
  sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\"$V\"|" .env && unset V

read -rsp "Neue DIRECT_URL einfügen: " V && printf '\n' && \
  sed -i "s|^DIRECT_URL=.*|DIRECT_URL=\"$V\"|" .env && unset V

read -rs unterdrückt die Anzeige beim Tippen, unset räumt die Variable weg.

Verbindung prüfen, ohne Zugangsdaten auszugeben:

npx prisma db execute --stdin <<< "SELECT 1;" && echo "Datenbank erreichbar"

Schritt 3 — SMTP-Passwort rotieren

  1. Beim Mail-Anbieter ein neues Passwort setzen (idealerweise ein App-Passwort)
  2. Auf dem Container eintragen:
cd /root/My-Weekly-ToDo-List
read -rsp "Neues SMTP-Passwort: " V && printf '\n' && \
  sed -i "s|^SMTP_PASSWORD=.*|SMTP_PASSWORD=\"$V\"|" .env && unset V

Schritt 4 — Übernehmen

cd /root/My-Weekly-ToDo-List
cp .env .next/standalone/.env
pm2 restart my-weekly-todo --update-env
pm2 save

Der Kopierschritt ist nötig, weil der Standalone-Build seine eigene .env neben server.js liest.

Danach: einmal im Browser neu anmelden. Dass du ausgeloggt wurdest, ist der Beweis, dass das neue NEXTAUTH_SECRET greift.


Schritt 5 — Die Datei aus dem Git entfernen

cd /root/My-Weekly-ToDo-List   # oder im Arbeitsrepo
git rm --cached .env.production.save
rm .env.production.save

.gitignore ergänzen:

.env*
!.env.example

Committen, pushen, fertig.


Schritt 6 — Historie: umschreiben oder nicht?

Nach Schritt 5 ist die Datei aus dem aktuellen Stand verschwunden — in der Historie steht sie weiterhin.

Meine Empfehlung: Historie so lassen. Begründung:

  • Das Repository ist privat (geprüft).
  • Nach den Schritten 1–3 sind die enthaltenen Werte wertlos, weil überall ersetzt.
  • Historie umschreiben ändert jeden Commit-Hash. Alle Klone (dein Rechner, der Container, Gitea) müssten neu geholt werden, alte Tags zeigen ins Leere. Das Risiko übersteigt den Nutzen deutlich.

Rotieren schlägt Umschreiben: Ein rotiertes Geheimnis in der Historie ist harmlos, ein nicht rotiertes bleibt auch nach dem Umschreiben in jedem existierenden Klon.

Falls du es trotzdem willst — nur bei Bedarf, mit vorherigem Voll-Backup:

git filter-repo --path .env.production.save --invert-paths

Danach git push --force auf beide Remotes, und jeder Klon muss neu erstellt werden.


Kontrolle zum Schluss

cd /root/My-Weekly-ToDo-List
git ls-files | grep -E "^\.env" || echo "OK: keine .env-Datei mehr im Git"
grep -cE "^(NEXTAUTH_SECRET|DATABASE_URL|DIRECT_URL|SMTP_PASSWORD)=" .env

Die zweite Zeile muss 4 ergeben — vier gesetzte Schlüssel, ohne einen davon zu zeigen.

Zum Schluss die Sicherungen aufräumen:

shred -u .env.backup-* 2>/dev/null || rm -f .env.backup-*