rbac is a Tier 0 module (no micro-lib dependencies). The dependency line incorrectly cited it as Tier 1. The module's own tier (4) is unchanged — it remains the auth layer above the transport infrastructure.
67 lines
1.9 KiB
Go
67 lines
1.9 KiB
Go
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))
|
|
})
|
|
}
|
|
}
|