Passwort-Reset per Einmal-Link (kver reset-link)

Ohne E-Mail-System erzeugt der Admin per CLI einen Link (/reset#<token>),
gültig 72 h und einmal. Gespeichert wird nur der SHA-256 des Tokens; es
steht im Fragment und geht nur im POST-Body an die API. Der Reset meldet
alle alten Sessions ab und loggt direkt neu ein. Passwortregeln jetzt
gemeinsam in checkNewPassword.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
irrlicht
2026-10-01 21:56:05 +02:00
co-authored by Claude Opus 5.5
parent b6cf8c7fa0
commit 81a74375b7
12 changed files with 408 additions and 14 deletions
+15
View File
@@ -37,6 +37,16 @@ func main() {
}
defer db.Close()
// Unterbefehle (z. B. reset-link) statt Server: laufen gegen dieselbe DB
// und beenden sich danach, siehe command.go.
if len(os.Args) > 1 {
if err := runCommand(os.Args[1:]); err != nil {
db.Close()
log.Fatal(err)
}
return
}
// Optionale GeoLite2-ASN-Datenbank für die netzwerktopologische Einordnung
// der Impressions. Inaktiv, wenn KVER_GEOIP_ASN nicht gesetzt ist.
initGeoASN()
@@ -124,6 +134,8 @@ func routes() http.Handler {
r.Use(limitByIP(20, time.Minute))
r.Post("/auth/login", api(handleLogin))
r.Post("/auth/newuser", api(handleNewUser))
r.Post("/auth/reset/check", api(handleResetCheck))
r.Post("/auth/reset", api(handleReset))
})
// Logout als POST: SameSite=Lax schickt Cookies bei Top-Level-GET-
// Navigationen mit -- als GET könnte eine fremde Seite Nutzer per Link
@@ -169,6 +181,9 @@ func routes() http.Handler {
r.With(trackImpression).Get("/e/{pid}", serveApp)
r.With(trackImpression).Get("/u/{username}", serveApp)
r.Get("/stats", serveApp)
// Reset-Seite ohne Tracking; das Token steht im Fragment (#...) und
// erreicht den Server über diese Route nie, siehe reset.go.
r.Get("/reset", serveApp)
// Alte Adresse der Statistik-Seite (Lesezeichen, Links von außen).
r.Get("/stats.html", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/stats", http.StatusMovedPermanently)