Files
zeekayandHanzo Dev 60b9474416 gateway: the edge writes identity headers, it does not mint them
Minting is issuing authority. IAM signs the token, and the signature is
where the authority is created. This edge VERIFIES a token and restates
what it proved as headers, so a transport that cannot carry a token can
read the same decision. Calling both operations "mint" put the issuer's
job and the edge's under one word, and a word that covers two operations
stops distinguishing them — a reader who thinks the edge mints reads it
as a second issuer.

So every use of `mint` for writing or stripping identity HEADERS becomes
`write`, which is already the leaf's own word (hanzoai/authz: "strips
every identity header on ingress and rewrites it from verified claims").
The identifiers follow: SetGatewayMinted/AssertGatewayMinted become
SetGatewayWritten/AssertGatewayWritten, mintIdentity becomes
writeIdentity, and waitlistMintedHeaders becomes waitlistHeaders — they
are simply the guard's own response headers, and the adjective added
nothing but the wrong verb.

`mint` stays where it is correct: IAM issuing a token, a sign-in issuing
a session cookie, commerce creating balance, and "the app the token was
minted through".

Two doc citations named symbols the migration removed and are re-pointed
at the live ones: edge.Mint to edge.Render, gatewayMintedIdentityHeaders
to authz.Headers.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-30 16:07:41 -07:00

86 lines
2.8 KiB
Go

// Package middleware ships gateway-owned middleware for the zip web
// framework. JWT validation + identity-header stripping live here (and
// NOT in github.com/zap-proto/zip/middleware) because they are the
// gateway subsystem's responsibility per HIP-0106.
//
// Other subsystems mounted inside the unified cloud binary trust the
// gateway-written X-Org-Id header and do not re-validate JWTs
// themselves — re-running JWT validation per-subsystem is wasteful and
// risks divergent validation rules.
package middleware
import (
"context"
"strings"
"github.com/zap-proto/zip"
gateway "github.com/hanzoai/gateway/v2"
)
// AuthVerifier is the interface Auth() consumes. The real implementation
// in hanzoai/iam or hanzoai/gateway-sdk satisfies it. A nil verifier on
// a request that has no gateway X-* headers and no Authorization bearer
// is rejected with 401.
type AuthVerifier interface {
// Verify validates the bearer token and returns the canonical
// X-* headers to write (Org / User / Email / IsAdmin / Roles).
Verify(ctx context.Context, bearer string) (Identity, error)
}
// Identity is the validated identity payload returned by AuthVerifier.
type Identity struct {
Org string
User string
UserEmail string
IsAdmin bool
Roles []string
}
// Auth validates incoming requests via verifier. When the request already
// carries gateway-written X-Org-Id (i.e. behind hanzoai/gateway), the
// verifier is bypassed and the headers are trusted. Otherwise the
// Authorization: Bearer <token> is verified.
//
// On successful gateway-trust or successful in-process verification,
// Auth marks the request as gateway-written via SetGatewayWritten so
// downstream subsystems can assert the trust boundary with
// gateway.AssertGatewayWritten(c).
//
// Pass a nil verifier to only accept gateway-written headers (no in-binary
// JWT validation).
func Auth(verifier AuthVerifier) zip.Handler {
return func(c *zip.Ctx) error {
// Trust the gateway path first.
if c.Org() != "" || c.User() != "" {
gateway.SetGatewayWritten(c)
return c.Continue()
}
if verifier == nil {
return zip.ErrUnauthorized("authentication required")
}
raw := c.Header("Authorization")
if !strings.HasPrefix(raw, "Bearer ") {
return zip.ErrUnauthorized("missing bearer token")
}
id, err := verifier.Verify(c.Context(), raw[len("Bearer "):])
if err != nil {
return zip.ErrUnauthorized("invalid token")
}
// Write the validated headers so handlers see the same shape as
// the gateway-fronted path.
req := c.Fiber().Request()
req.Header.Set("X-Org-Id", id.Org)
req.Header.Set("X-User-Id", id.User)
req.Header.Set("X-User-Email", id.UserEmail)
if id.IsAdmin {
req.Header.Set("X-User-IsAdmin", "true")
}
if len(id.Roles) > 0 {
req.Header.Set("X-Roles", strings.Join(id.Roles, ","))
}
gateway.SetGatewayWritten(c)
return c.Continue()
}
}