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":""} and sets // Content-Type. Uses a flat envelope (not the nested api.errorBody shape) // because the spec for /api/admin/* errors defines {"error":""} 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}) }