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"
|
"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) {
|
func (u *UIServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
csrfToken, err := u.setCSRFCookies(w)
|
csrfToken, err := u.setCSRFCookies(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -16,30 +17,35 @@ func (u *UIServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, err := u.db.ListAccounts()
|
claims := claimsFromContext(r.Context())
|
||||||
if err != nil {
|
isAdmin := claims != nil && claims.HasRole("admin")
|
||||||
u.renderError(w, r, http.StatusInternalServerError, "failed to load accounts")
|
|
||||||
return
|
data := DashboardData{
|
||||||
|
PageData: PageData{CSRFToken: csrfToken, ActorName: u.actorName(r)},
|
||||||
|
IsAdmin: isAdmin,
|
||||||
}
|
}
|
||||||
|
|
||||||
var total, active int
|
if isAdmin {
|
||||||
for _, a := range accounts {
|
accounts, err := u.db.ListAccounts()
|
||||||
total++
|
if err != nil {
|
||||||
if a.Status == model.AccountStatusActive {
|
u.renderError(w, r, http.StatusInternalServerError, "failed to load accounts")
|
||||||
active++
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, a := range accounts {
|
||||||
|
data.TotalAccounts++
|
||||||
|
if a.Status == model.AccountStatusActive {
|
||||||
|
data.ActiveAccounts++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
events, _, err := u.db.ListAuditEventsPaged(db.AuditQueryParams{Limit: 10, Offset: 0})
|
||||||
|
if err != nil {
|
||||||
|
u.logger.Warn("load recent audit events", "error", err)
|
||||||
|
events = nil
|
||||||
|
}
|
||||||
|
data.RecentEvents = events
|
||||||
}
|
}
|
||||||
|
|
||||||
events, _, err := u.db.ListAuditEventsPaged(db.AuditQueryParams{Limit: 10, Offset: 0})
|
u.render(w, "dashboard", data)
|
||||||
if err != nil {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ func (u *UIServer) Register(mux *http.ServeMux) {
|
|||||||
return authed(u.requireAdminRole(http.HandlerFunc(h)))
|
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("GET /accounts", adminGet(u.handleAccountsList))
|
||||||
uiMux.Handle("POST /accounts", admin(u.handleCreateAccount))
|
uiMux.Handle("POST /accounts", admin(u.handleCreateAccount))
|
||||||
uiMux.Handle("GET /accounts/{id}", adminGet(u.handleAccountDetail))
|
uiMux.Handle("GET /accounts/{id}", adminGet(u.handleAccountDetail))
|
||||||
@@ -609,6 +609,7 @@ type LoginData struct {
|
|||||||
// DashboardData is the view model for the dashboard page.
|
// DashboardData is the view model for the dashboard page.
|
||||||
type DashboardData struct {
|
type DashboardData struct {
|
||||||
PageData
|
PageData
|
||||||
|
IsAdmin bool
|
||||||
RecentEvents []*db.AuditEventView
|
RecentEvents []*db.AuditEventView
|
||||||
TotalAccounts int
|
TotalAccounts int
|
||||||
ActiveAccounts int
|
ActiveAccounts int
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .IsAdmin}}
|
||||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;margin-bottom:1.5rem">
|
<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 class="card" style="text-align:center">
|
||||||
<div style="font-size:2rem;font-weight:700;color:#2563eb">{{.TotalAccounts}}</div>
|
<div style="font-size:2rem;font-weight:700;color:#2563eb">{{.TotalAccounts}}</div>
|
||||||
@@ -33,4 +34,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<div class="card">
|
||||||
|
<p>Welcome, <strong>{{.ActorName}}</strong>. Use the navigation above to access your profile and credentials.</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user