Files
commerce/api/store/token.go
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

51 lines
2.0 KiB
Go

package store
import (
"errors"
"github.com/zap-proto/zip"
"github.com/hanzoai/commerce/middleware"
"github.com/hanzoai/commerce/util/json/http"
"github.com/hanzoai/commerce/util/permission"
)
// mintStorefrontToken mints (idempotently) the org's storefront read key — an
// access token carrying ONLY permission.Published, the least-privilege key a
// logged-out shopper's storefront uses to read the org's published catalog
// (design path b: "a per-org storefront Published key, minted once, kept in
// KMS"). It is org-bound (signed by the org's own SecretKey, subject = org id),
// so unlike the shared service token it can NEVER act on another tenant and
// carries no write/admin scope.
//
// Admin-gated (the route wraps middleware.TokenRequired(permission.Admin)): only
// an org admin — or the platform service token scoped to this org via X-Org-Id —
// may mint its own org's storefront key. Re-minting rotates the key (RemoveToken
// drops the prior "storefront" token, invalidating it).
//
// Returns { status, org, token }. The caller stores `token` in KMS and injects
// it as HANZO_COMMERCE_STOREFRONT_TOKEN on the storefront deployment.
func mintStorefrontToken(c *zip.Ctx) error {
// Authoritative admin gate. The route's TokenRequired(Admin) NO-OPS on the
// IAM path (EdgeAuth/gateway-minted identity), so enforce admin here the same
// way the money handlers do — IAM-aware, fail-closed — or a non-admin org
// member could mint their org's storefront key. RequireAdmin renders the
// rejection itself, so returning nil here ends the chain.
if !middleware.RequireAdmin(c) {
return nil
}
org, ok := middleware.GetOrganizationOK(c)
if !ok || org == nil {
return http.Fail(c, 400, "organization required", errors.New("no organization in context"))
}
org.RemoveToken("storefront")
token := org.AddToken("storefront", permission.Published)
if err := org.Put(); err != nil {
return http.Fail(c, 500, "failed to persist storefront token", err)
}
return http.Render(c, 200, map[string]any{"status": "ok", "org": org.Name, "token": token})
}