Files
zeekayandHanzo Dev a15aef0eff fix(identn): log GetIdentity failures instead of silently dropping to a generic 401
A GetIdentity error (e.g. provision: ensureOrg/Grant failure) was swallowed —
next.ServeHTTP without claims → downstream generic 'unauthenticated' 401 with NO
diagnostic. Log ::IDENTITY-GET-FAILED:: so the real cause is visible.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 08:25:11 -07:00

82 lines
2.2 KiB
Go

package middleware
import (
"context"
"log/slog"
"net/http"
"github.com/hanzoai/o11y/pkg/errors"
"github.com/hanzoai/o11y/pkg/identn"
"github.com/hanzoai/o11y/pkg/sharder"
"github.com/hanzoai/o11y/pkg/types"
"github.com/hanzoai/o11y/pkg/types/authtypes"
"github.com/hanzoai/o11y/pkg/types/ctxtypes"
"github.com/hanzoai/o11y/pkg/valuer"
)
const (
identityCrossOrgMessage string = "::IDENTITY-CROSS-ORG::"
)
type IdentN struct {
resolver identn.IdentNResolver
sharder sharder.Sharder
logger *slog.Logger
}
func NewIdentN(resolver identn.IdentNResolver, sharder sharder.Sharder, logger *slog.Logger) *IdentN {
return &IdentN{
resolver: resolver,
sharder: sharder,
logger: logger,
}
}
func (m *IdentN) Wrap(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
idn := m.resolver.GetIdentN(r)
if idn == nil {
next.ServeHTTP(w, r)
return
}
if pre, ok := idn.(identn.IdentNWithPreHook); ok {
r = pre.Pre(r)
}
identity, err := idn.GetIdentity(r)
if err != nil {
// Surface the identity-resolution failure instead of silently dropping to a
// downstream generic 401 — a swallowed provision/auth error is undebuggable.
m.logger.ErrorContext(r.Context(), "::IDENTITY-GET-FAILED::", errors.Attr(err))
next.ServeHTTP(w, r)
return
}
ctx := r.Context()
claims := identity.ToClaims()
if err := m.sharder.IsMyOwnedKey(ctx, types.NewOrganizationKey(valuer.MustNewUUID(claims.OrgID))); err != nil {
m.logger.ErrorContext(ctx, identityCrossOrgMessage, slog.Any("claims", claims), errors.Attr(err))
next.ServeHTTP(w, r)
return
}
ctx = authtypes.NewContextWithClaims(ctx, claims)
comment := ctxtypes.CommentFromContext(ctx)
comment.Set("identn_provider", claims.IdentNProvider.StringValue())
comment.Set("user_id", claims.UserID)
comment.Set("service_account_id", claims.ServiceAccountID)
comment.Set("principal", claims.Principal.StringValue())
comment.Set("org_id", claims.OrgID)
ctx = ctxtypes.NewContextWithComment(ctx, comment)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
if hook, ok := idn.(identn.IdentNWithPostHook); ok {
hook.Post(context.WithoutCancel(r.Context()), r, claims)
}
})
}