package auth import ( "context" "net/http" "github.com/google/uuid" ) // Trusted identity headers — the wasm plugin auth contract. // // Context values do NOT cross the wasm ABI, so a guest cannot see the auth // context the host's middleware built. The SECURE way to convey the caller's // identity to a guest is these headers, populated HOST-SIDE from the // signature-verified principal after the host has run its RBAC guard // (rbac.Authorize against the verified JWT). The host STRIPS any client-supplied // copy of these headers before setting them, so a guest may trust them // unconditionally. // // A guest MUST NOT decode a client-supplied cookie or Authorization/Bearer // token to establish identity — those are attacker-controlled across the // boundary (the host forwards the raw request), and trusting them is a // privilege-escalation bug. Read identity ONLY from these headers (via // TrustedHeaderMiddleware / ContextFromTrustedHeaders). See // core/docs/wasm-abi.md ("Trusted identity headers"). // // Values are the canonical MIME header form so a host map-set and a guest // http.Header.Get agree without re-canonicalization surprises. const ( // Admin principal (auth.Claims). HeaderVerifiedUserID = "X-Bn-Verified-User-Id" HeaderVerifiedRole = "X-Bn-Verified-Role" HeaderVerifiedEmail = "X-Bn-Verified-Email" // Public/community principal (auth.PublicClaims). HeaderVerifiedPublicUserID = "X-Bn-Verified-Public-User-Id" HeaderVerifiedPublicUsername = "X-Bn-Verified-Public-Username" HeaderVerifiedPublicEmail = "X-Bn-Verified-Public-Email" ) // AllTrustedHeaders lists every trusted identity header, canonical form. The // host deletes each of these from the inbound request before injecting its own // verified values, so a client cannot forge one. func AllTrustedHeaders() []string { return []string{ HeaderVerifiedUserID, HeaderVerifiedRole, HeaderVerifiedEmail, HeaderVerifiedPublicUserID, HeaderVerifiedPublicUsername, HeaderVerifiedPublicEmail, } } // ContextFromTrustedHeaders rebuilds the request's auth context from the host's // verified identity headers. Trust model: see the const block above — these // headers are host-controlled, so no token parsing or signature check happens // here (the guest has no signing secret by design). A malformed user-id header // is ignored rather than trusted. func ContextFromTrustedHeaders(ctx context.Context, h http.Header) context.Context { if s := h.Get(HeaderVerifiedUserID); s != "" { if id, err := uuid.Parse(s); err == nil { ctx = WithUser(ctx, &Claims{ UserID: id, Email: h.Get(HeaderVerifiedEmail), Role: h.Get(HeaderVerifiedRole), }) } } if s := h.Get(HeaderVerifiedPublicUserID); s != "" { if id, err := uuid.Parse(s); err == nil { ctx = WithPublicUser(ctx, &PublicClaims{ UserID: id, Email: h.Get(HeaderVerifiedPublicEmail), Username: h.Get(HeaderVerifiedPublicUsername), }) } } return ctx } // TrustedHeaderMiddleware wraps a guest HTTP handler so downstream RPCs can read // auth.GetUserFromContext / GetPublicUserFromContext. Plugins whose Connect RPCs // resolve the caller from context MUST mount this around their handler (context // does not cross the ABI otherwise). This is the ONLY sanctioned way for a guest // to learn the caller's identity. func TrustedHeaderMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r.WithContext(ContextFromTrustedHeaders(r.Context(), r.Header))) }) }