Allow non-admin users to access dashboard
- Change dashboard route from adminGet to authed middleware
- Show account counts and audit events only for admin users
- Show welcome message for non-admin authenticated users
Security: non-admin users cannot access account lists or audit
events; admin-only data is gated by claims.HasRole("admin") in
the handler, not just at the route level.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,8 @@ import (
|
||||
"git.wntrmute.dev/kyle/mcias/internal/model"
|
||||
)
|
||||
|
||||
// handleDashboard renders the main dashboard page with account counts and recent events.
|
||||
// handleDashboard renders the main dashboard page. Admin users see account
|
||||
// counts and recent audit events; non-admin users see a welcome page.
|
||||
func (u *UIServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
csrfToken, err := u.setCSRFCookies(w)
|
||||
if err != nil {
|
||||
@@ -16,17 +17,25 @@ func (u *UIServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
claims := claimsFromContext(r.Context())
|
||||
isAdmin := claims != nil && claims.HasRole("admin")
|
||||
|
||||
data := DashboardData{
|
||||
PageData: PageData{CSRFToken: csrfToken, ActorName: u.actorName(r)},
|
||||
IsAdmin: isAdmin,
|
||||
}
|
||||
|
||||
if isAdmin {
|
||||
accounts, err := u.db.ListAccounts()
|
||||
if err != nil {
|
||||
u.renderError(w, r, http.StatusInternalServerError, "failed to load accounts")
|
||||
return
|
||||
}
|
||||
|
||||
var total, active int
|
||||
for _, a := range accounts {
|
||||
total++
|
||||
data.TotalAccounts++
|
||||
if a.Status == model.AccountStatusActive {
|
||||
active++
|
||||
data.ActiveAccounts++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +44,8 @@ func (u *UIServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
u.logger.Warn("load recent audit events", "error", err)
|
||||
events = nil
|
||||
}
|
||||
|
||||
u.render(w, "dashboard", DashboardData{
|
||||
PageData: PageData{CSRFToken: csrfToken, ActorName: u.actorName(r)},
|
||||
TotalAccounts: total,
|
||||
ActiveAccounts: active,
|
||||
RecentEvents: events,
|
||||
})
|
||||
data.RecentEvents = events
|
||||
}
|
||||
|
||||
u.render(w, "dashboard", data)
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ func (u *UIServer) Register(mux *http.ServeMux) {
|
||||
return authed(u.requireAdminRole(http.HandlerFunc(h)))
|
||||
}
|
||||
|
||||
uiMux.Handle("GET /dashboard", adminGet(u.handleDashboard))
|
||||
uiMux.Handle("GET /dashboard", authed(http.HandlerFunc(u.handleDashboard)))
|
||||
uiMux.Handle("GET /accounts", adminGet(u.handleAccountsList))
|
||||
uiMux.Handle("POST /accounts", admin(u.handleCreateAccount))
|
||||
uiMux.Handle("GET /accounts/{id}", adminGet(u.handleAccountDetail))
|
||||
@@ -609,6 +609,7 @@ type LoginData struct {
|
||||
// DashboardData is the view model for the dashboard page.
|
||||
type DashboardData struct {
|
||||
PageData
|
||||
IsAdmin bool
|
||||
RecentEvents []*db.AuditEventView
|
||||
TotalAccounts int
|
||||
ActiveAccounts int
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<div class="page-header">
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
{{if .IsAdmin}}
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;margin-bottom:1.5rem">
|
||||
<div class="card" style="text-align:center">
|
||||
<div style="font-size:2rem;font-weight:700;color:#2563eb">{{.TotalAccounts}}</div>
|
||||
@@ -33,4 +34,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="card">
|
||||
<p>Welcome, <strong>{{.ActorName}}</strong>. Use the navigation above to access your profile and credentials.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user