feat: Nutzerverwaltung für Ebene 4 (Mandanten-Verwaltung)
Ein admin kann jetzt weitere Logins im eigenen Mandanten anlegen (GET /verwaltung/nutzer, GET/POST /verwaltung/nutzer/neu) mit einer der vier Mandanten-Rollen — betreiber bleibt Ebene 5 vorbehalten und kann von keinem Mandanten-Admin vergeben werden. Schließt die Lücke, dass die Fachebene (verantwortlicher/pruefer) bisher nur per manuellem SQL-Insert erreichbar war, weil die Firma-Registrierung ausschließlich einen admin-Nutzer erzeugt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
108
internal/web/admin_handlers.go
Normal file
108
internal/web/admin_handlers.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Ebene 4 (Mandanten-Verwaltung) — Nutzer/Rollen für GENAU EINEN
|
||||
// Mandanten, siehe CLAUDE.md, Fünf Ebenen. Aktuell nur Nutzerverwaltung
|
||||
// (Abteilungen, Anmeldeverfahren, Werkzeug-Freigaben, Rechnungsdaten
|
||||
// sind spätere Ausbaustufen). Es gibt noch keinen Einladungslink/CSV-
|
||||
// Import/SSO (siehe CLAUDE.md, Onboarding) — ein Admin setzt hier
|
||||
// direkt ein Initialpasswort, wie schon bei der Firma-Registrierung.
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/auth"
|
||||
)
|
||||
|
||||
// mandantenRollen sind die Rollen, die ein Admin innerhalb des eigenen
|
||||
// Mandanten vergeben darf — "betreiber" ist Ebene 5 (Plattform) und
|
||||
// kann von keinem Mandanten-Admin vergeben werden.
|
||||
var mandantenRollen = []string{"mitarbeiter", "verantwortlicher", "pruefer", "admin"}
|
||||
|
||||
func istMandantenRolle(role string) bool {
|
||||
for _, r := range mandantenRollen {
|
||||
if r == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type nutzerListItem struct {
|
||||
Email string
|
||||
Role string
|
||||
}
|
||||
|
||||
type nutzerListeData struct {
|
||||
Title string
|
||||
Nav navData
|
||||
Nutzer []nutzerListItem
|
||||
}
|
||||
|
||||
// handleNutzerListe zeigt alle Logins des eigenen Mandanten.
|
||||
func (s *Server) handleNutzerListe(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := s.store.ListUsersForAccount(r.Context(), currentUser(r).AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, "Nutzer konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := nutzerListeData{Title: "Nutzerverwaltung", Nav: navFor(r)}
|
||||
for _, u := range users {
|
||||
data.Nutzer = append(data.Nutzer, nutzerListItem{Email: u.Email, Role: u.Role})
|
||||
}
|
||||
if err := s.templates.ExecuteTemplate(w, "nutzer-liste", data); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
type nutzerNeuData struct {
|
||||
Title string
|
||||
Nav navData
|
||||
Rollen []string
|
||||
Error string
|
||||
}
|
||||
|
||||
func (s *Server) handleNutzerNeuForm(w http.ResponseWriter, r *http.Request) {
|
||||
data := nutzerNeuData{Title: "Nutzer anlegen", Nav: navFor(r), Rollen: mandantenRollen}
|
||||
if err := s.templates.ExecuteTemplate(w, "nutzer-neu", data); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// handleNutzerCreate legt einen weiteren Login im eigenen Mandanten an.
|
||||
// Es gibt keine Einladungsmail (siehe CLAUDE.md, Onboarding — noch
|
||||
// nicht gebaut) — der Admin setzt das Initialpasswort direkt und gibt
|
||||
// es dem/der neuen Nutzer:in auf einem anderen Weg weiter.
|
||||
func (s *Server) handleNutzerCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "ungültiges Formular", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
role := r.FormValue("role")
|
||||
if email == "" || password == "" || !istMandantenRolle(role) {
|
||||
data := nutzerNeuData{Title: "Nutzer anlegen", Nav: navFor(r), Rollen: mandantenRollen, Error: "E-Mail, Passwort und eine gültige Rolle sind Pflicht"}
|
||||
if err := s.templates.ExecuteTemplate(w, "nutzer-neu", data); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
passwordHash, err := auth.HashPassword(password)
|
||||
if err != nil {
|
||||
data := nutzerNeuData{Title: "Nutzer anlegen", Nav: navFor(r), Rollen: mandantenRollen, Error: err.Error()}
|
||||
if err := s.templates.ExecuteTemplate(w, "nutzer-neu", data); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := s.store.CreateUser(r.Context(), currentUser(r).AccountID, email, passwordHash, role); err != nil {
|
||||
data := nutzerNeuData{Title: "Nutzer anlegen", Nav: navFor(r), Rollen: mandantenRollen, Error: "Nutzer konnte nicht angelegt werden — E-Mail evtl. schon vergeben"}
|
||||
if err := s.templates.ExecuteTemplate(w, "nutzer-neu", data); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/verwaltung/nutzer", http.StatusSeeOther)
|
||||
}
|
||||
Reference in New Issue
Block a user