package httpauth import ( "encoding/json" "net/http" "code.nochebuena.dev/go/rbac" "code.nochebuena.dev/go/xerrors" ) // AuthzMiddleware reads the rbac.Identity from context (set by EnrichmentMiddleware) // and gates the request against the required permission on resource. // Uses rbac.PermissionProvider directly — no local redefinition of the interface. // Returns 401 if no identity is in context. // Returns 403 if the identity lacks the required permission or if the provider errors. func AuthzMiddleware(provider rbac.PermissionProvider, resource string, required rbac.Permission) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { identity, ok := rbac.FromContext(r.Context()) if !ok { WriteJSONError(w, http.StatusUnauthorized, string(xerrors.ErrUnauthorized), "unauthorized") return } mask, err := provider.ResolveMask(r.Context(), identity.UID, resource) if err != nil || !mask.Has(required) { WriteJSONError(w, http.StatusForbidden, string(xerrors.ErrPermissionDenied), "forbidden") return } next.ServeHTTP(w, r) }) } } // WriteJSONError writes a structured JSON error body matching the httputil.Error format. // Used by all httpauth-* provider packages to ensure a consistent error format across // the full middleware stack. func WriteJSONError(w http.ResponseWriter, status int, code, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]any{ "code": code, "message": message, }) }