Skip to content

Feature: Password Reset + Change Password #47

Description

@felixboehm

Summary

Platform hat aktuell keinen Weg für User, ihr Passwort zurückzusetzen (wenn vergessen) oder zu ändern (wenn eingeloggt). Dieses Issue umfasst beide Flows in einem PR, da sie sich den Dex-UpdatePassword-gRPC-Wrapper und die Validierungslogik teilen.

Scope:

  1. Forgot Password — Email-basierter Reset-Flow für nicht-eingeloggte User
  2. Change Password — In-Session-Änderung für eingeloggte User (altes Passwort als Bestätigung)

Current state

  • actions/register.ts erstellt User in Dex via createDexUser() + TinyBase + Email-Verification (file-token pattern)
  • lib/email-verification.ts — file-based token store (data/verification-tokens/<token>.json, 24h TTL, single-use, crypto.randomBytes(32))
  • lib/dex/client.ts — wrappt CreatePassword und VerifyPassword, aber noch nicht UpdatePassword (existiert in api.proto Zeile 278)
  • app/(auth)/ hat nur register/ und signin/ — kein forgot-password/ oder reset-password/

Open design questions

Q1 — Token TTL für Reset-Link

  • A) 1 Stunde (Industry-Standard für Password-Reset, kurzlebig)
  • B) 24 Stunden (konsistent mit Email-Verification)
  • Empfehlung: A — Reset-Tokens sind sensibler als Verification-Tokens, kürzere TTL reduziert Risiko bei Email-Kompromittierung.

Q2 — Email-Enumeration-Schutz

  • A) Immer gleiche Antwort: "Wenn die Email existiert, haben wir einen Link geschickt"
  • B) Direktes Feedback: "Kein Account für diese Email gefunden"
  • Empfehlung: A — verhindert User-Enumeration über den Reset-Endpoint. Standard-Security-Pattern.

Q3 — Sessions nach Reset invalidieren

  • A) Ja — alle bestehenden Sessions ungültig, User muss überall neu einloggen
  • B) Nein — nur Passwort tauschen, Sessions bleiben
  • Empfehlung: A — wichtig falls Account kompromittiert ist. Technisch: NextAuth-Session-Tokens + Dex offline-sessions für den User löschen.

Q4 — Rate-Limiting

  • A) Jetzt implementieren (z.B. max 3 Requests/Stunde pro Email+IP)
  • B) Später, als eigenes Cross-Cutting-Issue (betrifft auch Register, Login, Verify)
  • Empfehlung: B — Rate-Limiting ist ein separates Concern, gehört nicht isoliert in diesen PR. Als Interim-Schutz: Reset-Endpoint sendet keinen neuen Token, solange ein noch gültiger existiert (dämpft Email-Spam automatisch).

Proposed design (pending Q1–Q4)

New files

  • app/(auth)/forgot-password/page.tsx — Email-Input, triggert Reset-Request
  • app/(auth)/reset-password/page.tsx — Passwort-Formular mit Token-Param
  • app/settings/password/page.tsx — Change-Password (eingeloggt, alt+neu)
  • actions/password-reset.ts — requestPasswordReset, confirmPasswordReset
  • actions/password-change.ts — changePassword (nutzt verifyDexPassword für alt)
  • lib/password-reset.ts — Token-Management analog email-verification.ts, separater Ordner data/password-reset-tokens/

Modified files

  • lib/dex/client.ts — neue Funktion updateDexPassword(email, newPassword)
  • components/form/SignInForm.tsx (oder equivalent) — Link "Passwort vergessen?"
  • Sidebar/Profile-Menu — Link zu /settings/password

Email-Template

  • Neue Template-Funktion sendPasswordResetEmail() in lib/password-reset.ts — spiegelt sendVerificationEmail()
  • Subject: "Reset your Enopax password", Link: \${BASE_URL}/reset-password?token=\${token}

Flow — Forgot Password

  1. User → /forgot-password, trägt Email ein
  2. Action requestPasswordReset(email):
    • TinyBase-User per Email suchen
    • Wenn existiert: Token erzeugen, Email senden
    • Wenn nicht: trotzdem Success-Response (Q2=A)
  3. User klickt Email-Link → /reset-password?token=...
  4. Neues Passwort + Bestätigung
  5. Action confirmPasswordReset(token, newPassword):
    • Token verifizieren + löschen
    • updateDexPassword(email, newPassword) (Q1=A: 1h TTL)
    • NextAuth-Session des Users invalidieren (Q3=A)
    • Redirect → /signin mit Success-Flash

Flow — Change Password

  1. Eingeloggter User → /settings/password
  2. Formular: aktuelles Passwort, neues Passwort, Bestätigung
  3. Action changePassword(current, new):
    • verifyDexPassword(session.email, current) — wenn falsch → Fehler
    • updateDexPassword(session.email, new)
    • Optional (Q3 extension): andere Sessions des Users invalidieren
  4. Success-Flash, Seite reloaden

Security decisions baked in

  • Password-Hash via bcrypt-ts (konsistent mit createDexUser)
  • Min-Length 8 Zeichen (konsistent mit Register)
  • Tokens: crypto.randomBytes(32).toString('hex') (konsistent mit email-verification)
  • Single-use-Tokens, file-based persistence in data/password-reset-tokens/ mit mode 0o600

Testing

  • Jest-Tests für lib/password-reset.ts (Token-Lifecycle: create, verify, expire, single-use)
  • Jest-Tests für actions/password-reset.ts und actions/password-change.ts (Mock Dex-Client)
  • Manueller E2E-Test gegen Dev-Dex
  • Security-Smoke: Token-Reuse blocked, Expired-Token blocked, Falscher-Alt-Passwort blocked

Acceptance criteria

  • User kann via /forgot-password einen Reset-Link anfordern
  • Email mit gültigem Link wird versandt (wenn EMAIL_SERVER konfiguriert)
  • Reset-Link führt zu Formular, setzt neues Passwort, invalidiert Sessions, redirected zu Signin
  • Eingeloggter User kann via /settings/password sein Passwort ändern (braucht aktuelles Passwort)
  • Abgelaufene oder bereits genutzte Tokens werden abgewiesen
  • Enumeration-Response: identische UI-Response unabhängig davon ob Email existiert
  • Jest-Tests grün, TypeScript-Check grün

Out of scope

  • Rate-Limiting (separates Issue, Q4=B)
  • 2FA/MFA (getrennte Initiative)
  • Password-Complexity-Rules jenseits der 8-Zeichen-Minimum-Regel
  • Password-Breach-Check (z.B. HaveIBeenPwned)

Next steps

  1. User beantwortet Q1–Q4 (oder bestätigt Empfehlungen)
  2. Spec-Doc in `docs/specs/YYYY-MM-DD-password-reset-design.md` schreiben + committen
  3. Implementation plan via `superpowers:writing-plans` erstellen
  4. PR in kleinen Schritten (per User-Preference: "NEVER do all steps at once")

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions