Files
zeekay 1943f1c093 Hanzo Commerce
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans,
usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0.

Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
2026-09-23 21:52:16 -07:00

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 global admin 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
}