Add login rename and password reset flow

This commit is contained in:
DonVoo 2026-07-13 01:19:47 +02:00
parent b625c95c29
commit e7611b2d49
4 changed files with 362 additions and 28 deletions

View file

@ -17,6 +17,8 @@ func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/login", loginHandler)
mux.HandleFunc("/logout", logoutHandler)
mux.HandleFunc("/password/forgot", forgotPasswordHandler)
mux.HandleFunc("/password/reset", resetPasswordHandler)
mux.HandleFunc("/users", usersHandler)
mux.HandleFunc("/users/save", userSaveHandler)
mux.HandleFunc("/users/toggle-active", userToggleActiveHandler)
@ -64,9 +66,9 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -384,6 +386,7 @@ func userSaveHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
oldUsername := strings.TrimSpace(r.FormValue("old_username"))
username := strings.TrimSpace(r.FormValue("username"))
displayName := strings.TrimSpace(r.FormValue("display_name"))
password := r.FormValue("password")
@ -393,9 +396,13 @@ func userSaveHandler(w http.ResponseWriter, r *http.Request) {
redirectUsers(w, r, "", "Benutzername fehlt.")
return
}
existing, existingErr := GetAppUser(username)
lookupUsername := username
if oldUsername != "" {
lookupUsername = oldUsername
}
existing, existingErr := GetAppUser(lookupUsername)
if !IsAdmin(r) && existingErr == nil && existing.Role != roleUser {
redirectUsers(w, r, username, "Nur Admins duerfen Verwalter oder Admins bearbeiten.")
redirectUsers(w, r, oldUsername, "Nur Admins duerfen Verwalter oder Admins bearbeiten.")
return
}
if !IsAdmin(r) {
@ -414,8 +421,8 @@ func userSaveHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
if err := SaveAppUser(username, displayName, hash, role, active); err != nil {
redirectUsers(w, r, username, err.Error())
if err := UpdateAppUser(oldUsername, username, displayName, hash, role, active); err != nil {
redirectUsers(w, r, oldUsername, err.Error())
return
}
redirectUsers(w, r, username, "Benutzer gespeichert.")
@ -504,9 +511,9 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Konten-Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -549,9 +556,9 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -597,9 +604,9 @@ func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, ed
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Benutzerverwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -667,9 +674,9 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>%s - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -708,9 +715,9 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, accounts []Account) {
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Quell-Postfach - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -750,9 +757,9 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, accounts []Account) {
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Ziel-Postfach - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-17">
<link rel="stylesheet" href="/static/style.css?v=20260712-18">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-17" defer></script></head>
<script src="/static/app.js?v=20260712-18" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -1034,6 +1041,7 @@ func renderUserForm(w http.ResponseWriter, r *http.Request, u AppUser) {
}
fmt.Fprintf(w, `<form class="account-form user-form" method="post" action="/users/save">
<div class="form-section">
<input type="hidden" name="old_username" value="%s">
<label class="label">Login/E-Mail<input class="input" name="username" value="%s" required></label>
<label class="check-row"><input type="checkbox" name="active" value="1" %s> Aktiv</label>
</div>
@ -1050,7 +1058,7 @@ func renderUserForm(w http.ResponseWriter, r *http.Request, u AppUser) {
<a class="btn secondary" href="/users">Neu</a>
</div>
</form>`,
html.EscapeString(u.Username), checked(u.Active), html.EscapeString(u.DisplayName), passwordHint(u.Username), passwordPlaceholder(u.Username), roleField)
html.EscapeString(u.Username), html.EscapeString(u.Username), checked(u.Active), html.EscapeString(u.DisplayName), passwordHint(u.Username), passwordPlaceholder(u.Username), roleField)
}
func userDisplayName(u AppUser) string {