mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
IsSuperAdmin folded the case of the home org and fell back to the owner claim, the org of the application that minted the token, and the money bits were granted on X-User-Owner == admin. Cloud writes X-User-Owner for an admin-org application and an admin-org person's API key as well as for a person, so either could mint money and edit the catalog here while being nobody in the rest of the same process. Every gate now reads X-User-IsAdmin, which the edge strips and writes from authz.Claims.Sudo alone; the standalone EdgeAuth decides it with that predicate over the verified token's signed membership set. X-User-IsAdmin no longer doubles as the org-admin bit, which is X-User-IsOrgAdmin. No machine is SuperAdmin. Global admin is SuperAdmin wherever it named this predicate.
57 lines
2.4 KiB
Go
57 lines
2.4 KiB
Go
// Copyright (c) 2014-present Hanzo AI, Inc.
|
|
// Licensed under MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.
|
|
|
|
package middleware
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/hanzoai/commerce/middleware/iammiddleware"
|
|
"github.com/hanzoai/commerce/util/bit"
|
|
"github.com/hanzoai/commerce/util/json/http"
|
|
"github.com/hanzoai/commerce/util/permission"
|
|
)
|
|
|
|
// RequireAdmin is the ONE admin gate the money-moving handlers use — IAM-aware
|
|
// AND legacy/service-token-aware. It fails closed (403) unless the caller is an
|
|
// admin, and is enforced INSIDE each money handler because the route-level
|
|
// TokenRequired(permission.Admin) middleware is a NO-OP on the IAM path: it
|
|
// short-circuits (c.Next) for any IAM-authenticated request WITHOUT checking the
|
|
// Admin bit (Red HIGH-4). A handler must never trust that gate on its own.
|
|
//
|
|
// Precedence (fail-closed):
|
|
// 1. Permissions bit — the legacy access token AND the service token both set
|
|
// c["permissions"] with permission.Admin when the caller is admin
|
|
// (middleware/accesstoken.go). Honored FIRST so the trusted M2M service-token
|
|
// money path (cloud-api → commerce, which carries X-Org-Id) is authorized by
|
|
// its verified token, not mistaken for a spoofable IAM-edge header identity.
|
|
// 2. IAM identity — the gateway/EdgeAuth-minted, JWT-verified claims must carry
|
|
// org-level IsAdmin OR platform SuperAdmin.
|
|
//
|
|
// These are per-ORG money actions (the caller acts within its own resolved
|
|
// namespace), so org-level admin suffices and a SuperAdmin is also allowed
|
|
// (superset). Cross-tenant/platform actions gate on the STRICTER SuperAdmin
|
|
// predicate instead (api/catalog.requireSuperAdmin, and the same gate in
|
|
// api/plan and api/rate), never this one.
|
|
//
|
|
// Returns true when admin; writes a 403 and returns false otherwise. Reads
|
|
// c["permissions"] without MustGet so a handler mounted without the token gate
|
|
// fails closed (403) rather than panicking (500).
|
|
func RequireAdmin(c *zip.Ctx) bool {
|
|
if v := c.Locals("permissions"); v != nil {
|
|
if f, ok := v.(bit.Field); ok && f.Has(permission.Admin) {
|
|
return true
|
|
}
|
|
}
|
|
if iammiddleware.IsIAMAuthenticated(c) {
|
|
claims := iammiddleware.GetIAMClaims(c) // non-nil by contract
|
|
if claims.IsAdmin || claims.IsSuperAdmin() {
|
|
return true
|
|
}
|
|
}
|
|
_ = http.Fail(c, 403, "admin privileges required", errors.New("caller is not an admin"))
|
|
return false
|
|
}
|