diff --git a/internal/web/admin_handlers.go b/internal/web/admin_handlers.go index 42afda1..a4cf1ff 100644 --- a/internal/web/admin_handlers.go +++ b/internal/web/admin_handlers.go @@ -34,6 +34,7 @@ func (s *Server) handlePublicKanzleiList(w http.ResponseWriter, r *http.Request) type adminDashboardData struct { Title string + Nav navData AccountCount int UnverifiedCount int RecentAuditCount int @@ -61,7 +62,7 @@ func (s *Server) handleAdminDashboard(w http.ResponseWriter, r *http.Request) { } data := adminDashboardData{ - Title: "Admin", AccountCount: len(accounts), UnverifiedCount: unverified, RecentAuditCount: len(auditLog), + Title: "Admin", Nav: navFor(r), AccountCount: len(accounts), UnverifiedCount: unverified, RecentAuditCount: len(auditLog), } if err := s.templates.ExecuteTemplate(w, "admin-dashboard", data); err != nil { http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError) @@ -77,6 +78,7 @@ type adminAccountListItem struct { type adminAccountListData struct { Title string + Nav navData Accounts []adminAccountListItem } @@ -87,7 +89,7 @@ func (s *Server) handleAdminAccountList(w http.ResponseWriter, r *http.Request) http.Error(w, "Accounts konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError) return } - data := adminAccountListData{Title: "Accounts"} + data := adminAccountListData{Title: "Accounts", Nav: navFor(r)} for _, a := range accounts { data.Accounts = append(data.Accounts, adminAccountListItem{ ID: a.ID, Name: a.Name, Verified: a.Verified, CreatedAt: a.CreatedAt.Format("02.01.2006 15:04"), @@ -105,6 +107,7 @@ type adminUserView struct { type adminAccountDetailData struct { Title string + Nav navData AccountID string Name string Verified bool @@ -130,7 +133,7 @@ func (s *Server) handleAdminAccountDetail(w http.ResponseWriter, r *http.Request } data := adminAccountDetailData{ - Title: "Account", AccountID: acc.ID, Name: acc.Name, Verified: acc.Verified, + Title: "Account", Nav: navFor(r), AccountID: acc.ID, Name: acc.Name, Verified: acc.Verified, CreatedAt: acc.CreatedAt.Format("02.01.2006 15:04"), } for _, u := range users { @@ -190,6 +193,7 @@ type adminAuditEntryView struct { type adminAuditLogData struct { Title string + Nav navData Entries []adminAuditEntryView } @@ -200,7 +204,7 @@ func (s *Server) handleAdminAuditLog(w http.ResponseWriter, r *http.Request) { http.Error(w, "Audit-Log konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError) return } - data := adminAuditLogData{Title: "Audit-Log"} + data := adminAuditLogData{Title: "Audit-Log", Nav: navFor(r)} for _, e := range entries { data.Entries = append(data.Entries, adminAuditEntryView{ CreatedAt: e.CreatedAt.Format("02.01.2006 15:04:05"), Action: e.Action, diff --git a/internal/web/admin_handlers_test.go b/internal/web/admin_handlers_test.go index 4d3e577..b831d20 100644 --- a/internal/web/admin_handlers_test.go +++ b/internal/web/admin_handlers_test.go @@ -40,6 +40,28 @@ func TestAdminDashboardAccessibleForAdmin(t *testing.T) { } } +// TestNavShowsAdminLinkOnlyForAdmins deckt genau den gemeldeten Fall ab: +// nach der Anmeldung als Admin landet man auf der normalen Startseite +// (jeder Nutzer hat einen Account+Login, auch ein Admin) — ohne einen +// sichtbaren Weg zu /admin wäre der Admin-Bereich für einen Admin, der +// die URL nicht auswendig kennt, praktisch unerreichbar. +func TestNavShowsAdminLinkOnlyForAdmins(t *testing.T) { + fs := newFakeStore() + s := newServer(t, fakeExtractor{}, fs) + adminCookie := seedAccountWithRole(t, fs, "Deklarix Admin", "admin@example.com", "admin") + tenantCookie := seedAccount(t, fs, "Mandant", "mandant@example.com") + + adminResp := getWithCookie(t, s, adminCookie, "/") + if !strings.Contains(adminResp.Body.String(), `href="/admin"`) { + t.Errorf("expected an /admin nav link for an admin user, got: %s", adminResp.Body.String()) + } + + tenantResp := getWithCookie(t, s, tenantCookie, "/") + if strings.Contains(tenantResp.Body.String(), `href="/admin"`) { + t.Errorf("expected no /admin nav link for a non-admin user, got: %s", tenantResp.Body.String()) + } +} + func TestAdminAccountListShowsAllAccountsAcrossTenants(t *testing.T) { fs := newFakeStore() s := newServer(t, fakeExtractor{}, fs) diff --git a/internal/web/archive_handlers.go b/internal/web/archive_handlers.go index 8bb0131..b7e85a7 100644 --- a/internal/web/archive_handlers.go +++ b/internal/web/archive_handlers.go @@ -18,6 +18,7 @@ type submissionListItem struct { type submissionListData struct { Title string + Nav navData Submissions []submissionListItem } @@ -31,7 +32,7 @@ func (s *Server) handleSubmissionList(w http.ResponseWriter, r *http.Request) { return } - data := submissionListData{Title: "Beiträge"} + data := submissionListData{Title: "Beiträge", Nav: navFor(r)} for _, sum := range summaries { data.Submissions = append(data.Submissions, submissionListItem{ ID: sum.ID, Platform: sum.Platform, PostType: sum.PostType, Status: sum.Status, @@ -56,6 +57,7 @@ type participantView struct { type submissionDetailData struct { Title string + Nav navData SubmissionID string Platform string PostType string @@ -125,7 +127,7 @@ func (s *Server) handleSubmissionDetail(w http.ResponseWriter, r *http.Request) } data := submissionDetailData{ - Title: "Beitrag", SubmissionID: sub.ID, Platform: sub.Platform, PostType: sub.PostType, + Title: "Beitrag", Nav: navFor(r), SubmissionID: sub.ID, Platform: sub.Platform, PostType: sub.PostType, Caption: sub.Caption, Status: sub.Status, CreatedAt: sub.CreatedAt.Format("02.01.2006 15:04"), CanArchive: sub.Status == "checked", IsPublished: sub.Status == "published", DossierURL: "/dossier/" + sub.ID, Findings: findings, Participants: toParticipantViews(participants), diff --git a/internal/web/handlers.go b/internal/web/handlers.go index af1ba67..5da9f7b 100644 --- a/internal/web/handlers.go +++ b/internal/web/handlers.go @@ -21,10 +21,12 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { type indexData struct { Title string + Nav navData } func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) { - if err := s.templates.ExecuteTemplate(w, "index", indexData{Title: "Pre-Publish-Prüfung"}); err != nil { + data := indexData{Title: "Pre-Publish-Prüfung", Nav: navFor(r)} + if err := s.templates.ExecuteTemplate(w, "index", data); err != nil { http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError) } } diff --git a/internal/web/middleware.go b/internal/web/middleware.go index 07a0545..7119461 100644 --- a/internal/web/middleware.go +++ b/internal/web/middleware.go @@ -88,6 +88,21 @@ func (s *Server) requireAdmin(next http.HandlerFunc) http.HandlerFunc { } } +// 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 Admin-Link +// gezeigt werden soll. +type navData struct { + IsAdmin bool +} + +// navFor liefert die Nav-Daten für den angemeldeten Nutzer der Anfrage. +// Nur für Seiten hinter requirePage/requireAdmin aufrufbar (braucht +// currentUser). +func navFor(r *http.Request) navData { + return navData{IsAdmin: currentUser(r).Role == "admin"} +} + // currentUser liest den Nutzer, den requirePage/requireAPI in den // Kontext gelegt haben. Panics, wenn es aufgerufen wird, ohne dass eine // dieser Middlewares vorgeschaltet war — das ist ein Programmierfehler, diff --git a/internal/web/templates/admin_account_detail.html b/internal/web/templates/admin_account_detail.html index e193115..73a5aae 100644 --- a/internal/web/templates/admin_account_detail.html +++ b/internal/web/templates/admin_account_detail.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

← Alle Accounts

{{.Name}}

diff --git a/internal/web/templates/admin_accounts.html b/internal/web/templates/admin_accounts.html index f119d9b..31b7b66 100644 --- a/internal/web/templates/admin_accounts.html +++ b/internal/web/templates/admin_accounts.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

← Admin

Accounts

diff --git a/internal/web/templates/admin_audit_log.html b/internal/web/templates/admin_audit_log.html index 1e3d9d9..5da5afc 100644 --- a/internal/web/templates/admin_audit_log.html +++ b/internal/web/templates/admin_audit_log.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

← Admin

Audit-Log

diff --git a/internal/web/templates/admin_dashboard.html b/internal/web/templates/admin_dashboard.html index 42bf93d..59a3044 100644 --- a/internal/web/templates/admin_dashboard.html +++ b/internal/web/templates/admin_dashboard.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

Admin

    diff --git a/internal/web/templates/beitraege.html b/internal/web/templates/beitraege.html index 7aa1cbd..deb2c2a 100644 --- a/internal/web/templates/beitraege.html +++ b/internal/web/templates/beitraege.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

    Beiträge

    diff --git a/internal/web/templates/beitrag.html b/internal/web/templates/beitrag.html index 039cb41..5059812 100644 --- a/internal/web/templates/beitrag.html +++ b/internal/web/templates/beitrag.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

    ← Alle Beiträge

    {{.Platform}} · {{.PostType}}

    diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html index 8be8031..d5d4ca7 100644 --- a/internal/web/templates/index.html +++ b/internal/web/templates/index.html @@ -2,7 +2,7 @@ {{template "head" .}} -{{template "nav" .}} +{{template "nav" .Nav}}

    Pre-Publish-Prüfung

    Caption und Plattform eingeben, um auf Kennzeichnungsrisiken zu prüfen.

    diff --git a/internal/web/templates/layout.html b/internal/web/templates/layout.html index d44595b..187d775 100644 --- a/internal/web/templates/layout.html +++ b/internal/web/templates/layout.html @@ -10,6 +10,7 @@