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:
noroot
2026-08-29 12:34:04 +02:00
parent d1e80dd19f
commit aad517ce23
8 changed files with 276 additions and 1 deletions

View File

@@ -112,6 +112,24 @@ func (s *Server) requireFachebene(next http.HandlerFunc) http.HandlerFunc {
}
}
// requireAdmin schützt die Mandanten-Verwaltung (Ebene 4 — Nutzer/
// Rollen für GENAU EINEN Mandanten, siehe CLAUDE.md). Wie bei
// requireBetreiber/requireFachebene: 404 statt 403 bei falscher Rolle.
func (s *Server) requireAdmin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, ok := s.authenticate(r)
if !ok {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if user.Role != "admin" {
http.Error(w, "nicht gefunden", http.StatusNotFound)
return
}
next(w, r.WithContext(context.WithValue(r.Context(), userContextKey, user)))
}
}
// navData steuert die gemeinsame Navigation (layout.html, "nav"-Block).
// Eigenes, kleines Struct statt jeder Seite Zugriff auf den vollen
// currentUser zu geben — die Navigation braucht nur, ob ein
@@ -119,6 +137,7 @@ func (s *Server) requireFachebene(next http.HandlerFunc) http.HandlerFunc {
type navData struct {
IsBetreiber bool
IsFachebene bool
IsAdmin bool
}
// navFor liefert die Nav-Daten für den angemeldeten Nutzer der Anfrage.
@@ -126,7 +145,11 @@ type navData struct {
// currentUser).
func navFor(r *http.Request) navData {
role := currentUser(r).Role
return navData{IsBetreiber: role == "betreiber", IsFachebene: role == "verantwortlicher" || role == "pruefer"}
return navData{
IsBetreiber: role == "betreiber",
IsFachebene: role == "verantwortlicher" || role == "pruefer",
IsAdmin: role == "admin",
}
}
// currentUser liest den Nutzer, den requirePage/requireAPI in den