4492826354
Replaces the old X-API-Token-based RequireAdmin in middleware.go with a
context-aware RequireAdmin() that runs after RequireUser, checks
user.IsAdmin, and returns 403 {"error":"not_authorized"} for non-admins
or 500 {"error":"internal_error"} if RequireUser was bypassed. Updates
server.go to mount RequireUser then RequireAdmin on the /api/admin group.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
46 lines
1.5 KiB
Go
46 lines
1.5 KiB
Go
package auth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
)
|
|
|
|
// RequireAdmin is a middleware that MUST run after RequireUser. It reads the
|
|
// authenticated user from request context and rejects non-admin callers with
|
|
// 403. If no user is in context (RequireUser was bypassed), it returns 500 —
|
|
// that is a programmer error in the routing setup, not a client error.
|
|
//
|
|
// Mount this on /api/admin/* after auth.RequireUser:
|
|
//
|
|
// r.Route("/api/admin", func(admin chi.Router) {
|
|
// admin.Use(auth.RequireUser(pool))
|
|
// admin.Use(auth.RequireAdmin())
|
|
// ...
|
|
// })
|
|
func RequireAdmin() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := UserFromContext(r.Context())
|
|
if !ok {
|
|
// Programmer error: RequireUser was not mounted before RequireAdmin.
|
|
writeAdminErr(w, http.StatusInternalServerError, "internal_error")
|
|
return
|
|
}
|
|
if !user.IsAdmin {
|
|
writeAdminErr(w, http.StatusForbidden, "not_authorized")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// writeAdminErr writes a flat JSON error envelope {"error":"<code>"} and sets
|
|
// Content-Type. Uses a flat envelope (not the nested api.errorBody shape)
|
|
// because the spec for /api/admin/* errors defines {"error":"<code>"} directly.
|
|
func writeAdminErr(w http.ResponseWriter, status int, code string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"error": code})
|
|
}
|