package httpauth import ( "context" "net/http" "code.nochebuena.dev/go/rbac" ) // IdentityEnricher builds an rbac.Identity from verified token claims. // The application provides the implementation — typically reads from a user store. type IdentityEnricher interface { Enrich(ctx context.Context, uid string, claims map[string]any) (rbac.Identity, error) } // EnrichOpt configures EnrichmentMiddleware. type EnrichOpt func(*enrichConfig) type enrichConfig struct { tenantHeader string } // WithTenantHeader configures the request header from which TenantID is read. // If the header is absent on a request, TenantID remains "" — no error is returned. func WithTenantHeader(header string) EnrichOpt { return func(c *enrichConfig) { c.tenantHeader = header } } // EnrichmentMiddleware reads uid + claims injected by AuthMiddleware, calls // enricher.Enrich to build a full rbac.Identity, and stores it in the context. // Returns 401 if no uid is present (AuthMiddleware was not in the chain). // Returns 500 if the enricher fails. func EnrichmentMiddleware(enricher IdentityEnricher, opts ...EnrichOpt) func(http.Handler) http.Handler { cfg := &enrichConfig{} for _, o := range opts { o(cfg) } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { uid, ok := getUID(r.Context()) if !ok { http.Error(w, "unauthorized", http.StatusUnauthorized) return } claims, _ := getClaims(r.Context()) identity, err := enricher.Enrich(r.Context(), uid, claims) if err != nil { http.Error(w, "internal server error", http.StatusInternalServerError) return } if cfg.tenantHeader != "" { if tenantID := r.Header.Get(cfg.tenantHeader); tenantID != "" { identity = identity.WithTenant(tenantID) } } ctx := rbac.SetInContext(r.Context(), identity) next.ServeHTTP(w, r.WithContext(ctx)) }) } }