diff --git a/backend/00-router.go b/backend/00-router.go index 3fcacd9..c505ae2 100644 --- a/backend/00-router.go +++ b/backend/00-router.go @@ -16,6 +16,10 @@ import ( func RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/", homeHandler) mux.HandleFunc("/login", loginHandler) + mux.HandleFunc("/logout", logoutHandler) + mux.HandleFunc("/users", usersHandler) + mux.HandleFunc("/users/save", userSaveHandler) + mux.HandleFunc("/users/delete", userDeleteHandler) // Umzug (Konten pflegen + starten/beobachten) mux.HandleFunc("/accounts", accountsHandler) // TODO Codex (02/07) @@ -59,16 +63,16 @@ func renderShell(w http.ResponseWriter, active, readingPane string) { fmt.Fprintf(w, ` Mail-Graveyard - + - +
Mail-Graveyard
@@ -81,7 +85,8 @@ func renderShell(w http.ResponseWriter, active, readingPane string) { Postfach <--> Archiv-MBox Archiv-MBox <--> Postfach Postfach <--> Postfach - Einstellungen + Benutzerverwaltung + Logout
@@ -104,6 +109,9 @@ func renderShell(w http.ResponseWriter, active, readingPane string) { } func accountsHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } accounts, err := ListAccounts() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -122,6 +130,9 @@ func accountsHandler(w http.ResponseWriter, r *http.Request) { } func accountSaveHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return @@ -151,6 +162,9 @@ func accountSaveHandler(w http.ResponseWriter, r *http.Request) { } func accountDeleteHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return @@ -168,6 +182,9 @@ func accountDeleteHandler(w http.ResponseWriter, r *http.Request) { } func accountTestHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return @@ -185,6 +202,9 @@ func accountTestHandler(w http.ResponseWriter, r *http.Request) { } func archivesHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } accounts, err := ListAccounts() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -194,6 +214,9 @@ func archivesHandler(w http.ResponseWriter, r *http.Request) { } func transferHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } mode := strings.TrimPrefix(r.URL.Path, "/transfer/") if mode == "" || mode == "/transfer" { http.Redirect(w, r, "/transfer/postfach-archiv", http.StatusSeeOther) @@ -225,6 +248,9 @@ func emailBoxHandler(w http.ResponseWriter, r *http.Request) { } func archiveCreateHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return @@ -242,6 +268,9 @@ func archiveCreateHandler(w http.ResponseWriter, r *http.Request) { } func archiveDeleteHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return @@ -277,6 +306,9 @@ func archiveDeleteHandler(w http.ResponseWriter, r *http.Request) { } func migrateRunHandler(w http.ResponseWriter, r *http.Request) { + if !requireAdmin(w, r) { + return + } if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return @@ -322,6 +354,105 @@ func migrateStatusHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `%s: %s %d/%d Fehler %d`, html.EscapeString(j.Account), html.EscapeString(label), j.Done, j.Total, j.Errors) } +func usersHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } + users, err := ListAppUsers() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + editName := strings.TrimSpace(r.URL.Query().Get("edit")) + var edit AppUser + if editName != "" { + edit, err = GetAppUser(editName) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + renderUsersPage(w, r, users, edit, r.URL.Query().Get("msg"), r.URL.Query().Get("err")) +} + +func userSaveHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + username := strings.TrimSpace(r.FormValue("username")) + password := r.FormValue("password") + role := strings.TrimSpace(r.FormValue("role")) + active := r.FormValue("active") == "1" + if username == "" { + redirectUsers(w, r, "", "Benutzername fehlt.") + return + } + existing, existingErr := GetAppUser(username) + if !IsAdmin(r) && existingErr == nil && existing.Role != roleUser { + redirectUsers(w, r, username, "Nur Admins duerfen Verwalter oder Admins bearbeiten.") + return + } + if !IsAdmin(r) { + role = roleUser + } + if errors.Is(existingErr, sql.ErrNoRows) && password == "" { + redirectUsers(w, r, username, "Passwort fuer neuen Benutzer fehlt.") + return + } + var hash string + if password != "" { + var err error + hash, err = HashPassword(password) + if err != nil { + redirectUsers(w, r, username, err.Error()) + return + } + } + if err := SaveAppUser(username, hash, role, active); err != nil { + redirectUsers(w, r, username, err.Error()) + return + } + redirectUsers(w, r, username, "Benutzer gespeichert.") +} + +func userDeleteHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + username := strings.TrimSpace(r.FormValue("username")) + if username == "" { + redirectUsers(w, r, "", "Kein Benutzer gewaehlt.") + return + } + current := CurrentUser(r) + if strings.EqualFold(username, current.Username) { + redirectUsers(w, r, username, "Eigenen Benutzer nicht loeschen.") + return + } + target, err := GetAppUser(username) + if err != nil { + redirectUsers(w, r, username, err.Error()) + return + } + if !IsAdmin(r) && target.Role != roleUser { + redirectUsers(w, r, username, "Nur Admins duerfen Verwalter oder Admins loeschen.") + return + } + if err := DeleteAppUser(username); err != nil { + redirectUsers(w, r, username, err.Error()) + return + } + redirectUsers(w, r, "", "Benutzer geloescht.") +} + func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account, msg, errMsg string) { if edit.Name == "" { edit = Account{SrcPort: 993, SrcSecurity: "tls", SrcProto: "imap", DstPort: 993, DstSecurity: "tls", Active: true} @@ -331,16 +462,16 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account, fmt.Fprintf(w, ` Konten-Verwaltung - Mail-Graveyard - + - +
Mail-Graveyard
@@ -353,7 +484,8 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account, Postfach <--> Archiv-MBox Archiv-MBox <--> Postfach Postfach <--> Postfach - Einstellungen + Benutzerverwaltung + Logout
`, renderRibbonEmailBoxMenu(""), renderRibbonTransferMenu("")) @@ -375,16 +507,16 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc fmt.Fprintf(w, ` Archiv-mbox Verwaltung - Mail-Graveyard - + - +
Mail-Graveyard
@@ -397,7 +529,8 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc Postfach <--> Archiv-MBox Archiv-MBox <--> Postfach Postfach <--> Postfach - Einstellungen + Benutzerverwaltung + Logout
`, renderRibbonEmailBoxMenu(""), renderRibbonTransferMenu("")) @@ -414,6 +547,56 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc fmt.Fprint(w, `
`) } +func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, edit AppUser, msg, errMsg string) { + if edit.Username == "" { + edit = AppUser{Role: roleUser, Active: true} + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprintf(w, ` + +Benutzerverwaltung - Mail-Graveyard + + + + +
+ + +
Mail-Graveyard
+
+ + +
`, renderRibbonEmailBoxMenu(""), renderRibbonTransferMenu("")) + if msg != "" { + fmt.Fprintf(w, `
%s
`, html.EscapeString(msg)) + } + if errMsg != "" { + fmt.Fprintf(w, `
%s
`, html.EscapeString(errMsg)) + } + fmt.Fprint(w, `
`) +} + type transferMode struct { Slug string Title string @@ -442,16 +625,16 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc fmt.Fprintf(w, ` %s - Mail-Graveyard - + - +
Mail-Graveyard
@@ -462,7 +645,8 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc Archiv-mbox Verwaltung Mail-Transfer %s - Einstellungen + Benutzerverwaltung + Logout
@@ -482,16 +666,16 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, accounts []Account) { fmt.Fprintf(w, ` Quell-Postfach - Mail-Graveyard - + - +
Mail-Graveyard
@@ -504,7 +688,8 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, accounts []Account) { Postfach <--> Archiv-MBox Archiv-MBox <--> Postfach Postfach <--> Postfach - Einstellungen + Benutzerverwaltung + Logout
@@ -523,16 +708,16 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, accounts []Account) { fmt.Fprintf(w, ` Ziel-Postfach - Mail-Graveyard - + - +
Mail-Graveyard
@@ -545,7 +730,8 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, accounts []Account) { Postfach <--> Archiv-MBox Archiv-MBox <--> Postfach Postfach <--> Postfach - Einstellungen + Benutzerverwaltung + Logout