feat(auth): Self-Service-Admin-Password-Reset via CLI-Token

Operator hat Admin-Passwort vergessen aber SSH-Zugang zur Box →
schneller Reset ohne SMTP/Email-Setup.

Flow:
  1. `sudo edgeguard-ctl reset-password` auf der Box → 32-hex-Token
     + ISO-Expiry werden nach /var/lib/edgeguard/.reset-token (mode
     0600 edgeguard:edgeguard) geschrieben, Token kommt auf stdout.
     TTL: 30 min.
  2. Login-Seite hat „Passwort vergessen?"-Link → /reset-password.
  3. Reset-Page: Token + neues Passwort (min. 12). POST /auth/reset-
     password validiert Token (constant-time compare), prüft Expiry,
     löscht das File (single-use), hash't das Passwort + speichert
     in setup.json.

internal/services/setup/:
  - SetAdminPassword() — bcrypt-hash + save, fehler wenn setup nicht
    completed
  - GenerateResetToken() / ConsumeResetToken() — File-basiert,
    Format: "<token>|<RFC3339-expiry>"

internal/handlers/auth.go: POST /api/v1/auth/reset-password.
cmd/edgeguard-ctl/main.go: `reset-password` command.

UI: /reset-password Page mit Info-Alert für CLI-Snippet
(„sudo edgeguard-ctl reset-password" im dunklen Code-Block); Login-
Seite bekommt den „Passwort vergessen?"-Link.

Verifiziert auf 1.0.76: CLI druckt Token + schreibt File mit 0600
edgeguard:edgeguard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-13 19:04:25 +02:00
parent 27ac7b53fc
commit c79bfe84ec
13 changed files with 323 additions and 8 deletions

View File

@@ -172,7 +172,22 @@
"login": "Sign in",
"logout": "Sign out",
"loginFailed": "Sign-in failed",
"loggedInAs": "Signed in as"
"loggedInAs": "Signed in as",
"forgotPassword": "Forgot your password?"
},
"reset": {
"title": "Reset admin password",
"intro": "Self-service reset via CLI token. You need SSH access to the box.",
"cliTitle": "1. Run on the box:",
"token": "Reset token",
"tokenExtra": "32 hex chars from the CLI output. Token is valid for 30 minutes.",
"newPassword": "New password (min. 12 chars)",
"confirmPassword": "Confirm password",
"submit": "Set password",
"backToLogin": "← Back to sign-in",
"mismatch": "Passwords don't match",
"success": "Password set. Redirecting…",
"failed": "Reset failed"
},
"setup": {
"title": "First-time setup",