fix(httpauth): wildcard resource fallback, json.Number precision, xerrors JSON error bodies (#1)
ClaimsPermissionProvider.ResolveMask now falls back to the "*" resource key when the
exact resource is absent in the JWT masks claim. Specific resource takes precedence;
wildcard is the fallback. Enables ADMIN wildcard masks ({"*": MaxInt64}) to pass every
endpoint guard without per-resource entries.
Add json.Number handling alongside existing int64/float64 paths. json.Number is
produced by jwt.WithJSONNumber() (httpauth-jwt) and preserves math.MaxInt64 exactly —
float64 would round to 2^63 and overflow on cast back to int64.
Export WriteJSONError(w, status, code, message) — shared JSON error helper for all
httpauth-* provider packages. Ensures a consistent {"code":"...","message":"..."} body
and Content-Type: application/json across the full middleware stack.
AuthzMiddleware and EnrichmentMiddleware now use WriteJSONError with xerrors code
constants (UNAUTHENTICATED, PERMISSION_DENIED, INTERNAL) instead of http.Error which
writes text/plain. AuthzMiddleware also fixes a wrong code string: UNAUTHORIZED →
UNAUTHENTICATED (stable gRPC-aligned name, matching xerrors.ErrUnauthorized).
Add xerrors v1.0.1 as a direct dependency for the code constants.
Reviewed-on: #1
Co-authored-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
Co-committed-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
This commit was merged in pull request #1.
This commit is contained in:
18
authz.go
18
authz.go
@@ -1,9 +1,11 @@
|
||||
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)
|
||||
@@ -16,13 +18,13 @@ func AuthzMiddleware(provider rbac.PermissionProvider, resource string, required
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
identity, ok := rbac.FromContext(r.Context())
|
||||
if !ok {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
WriteJSONError(w, http.StatusUnauthorized, string(xerrors.ErrUnauthorized), "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
mask, err := provider.ResolveMask(r.Context(), identity.UID, resource)
|
||||
if err != nil || !mask.Has(required) {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
WriteJSONError(w, http.StatusForbidden, string(xerrors.ErrPermissionDenied), "forbidden")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,3 +32,15 @@ func AuthzMiddleware(provider rbac.PermissionProvider, resource string, required
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user