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
+41
View File
@@ -0,0 +1,41 @@
package main
import (
"errors"
"fmt"
"os"
"strings"
)
// runCommand führt einen Admin-Unterbefehl aus (kver <befehl> ...). Im
// Container: podman exec kver kver <befehl> ...
func runCommand(args []string) error {
switch args[0] {
case "reset-link":
if len(args) != 2 {
return errors.New("Verwendung: kver reset-link <nutzername>")
}
return cmdResetLink(args[1])
default:
return fmt.Errorf("unbekannter Befehl %q (bekannt: reset-link)", args[0])
}
}
// cmdResetLink erzeugt einen Passwort-Reset-Link (siehe reset.go) und gibt ihn
// aus. KVER_BASE_URL (z. B. https://example.org) macht daraus einen fertigen
// Link; ohne steht nur der Pfad da.
func cmdResetLink(username string) error {
username = sanitizeUsername(username)
token, expires, err := createResetToken(username)
if errors.Is(err, errUserNotFound) {
return fmt.Errorf("Nutzer %q nicht gefunden", username)
} else if err != nil {
return err
}
base := strings.TrimSuffix(os.Getenv("KVER_BASE_URL"), "/")
fmt.Printf("Reset-Link für %s (gültig bis %s, einmalig):\n\n %s/reset#%s\n\n",
username, expires.Format("02.01.2006 15:04 MST"), base, token)
fmt.Println("Ältere Reset-Links dieses Nutzers sind damit ungültig.")
return nil
}