// Package middleware holds the door's cross-cutting HTTP concerns — the // correlation id, the rate limiter, and the audit-metadata attachment // (SPEC-003 §3/§4). // // These are deliberately small and transport-level: the door // (internal/core/server) orchestrates them in one visible pipeline. package middleware import ( "context" "crypto/rand" "fmt" "net/http" "time" ) // CorrelationHeader is the response header carrying the correlation id, echoed // in every log line and audit row (SPEC-003 §4/§8, AC-8). const CorrelationHeader = "X-Correlation-ID" // correlationKey is the unexported context key for the correlation id. type correlationKey struct{} // NewCorrelationID returns a fresh UUID v4 string. It is generated per request: // an inbound header is never trusted, so a caller cannot forge a trail. func NewCorrelationID() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { // crypto/rand failing is a platform fault; fall back to a // time-derived id rather than emitting an empty correlation. return fmt.Sprintf("corr-%d", time.Now().UnixNano()) } b[6] = (b[6] & 0x0f) | 0x40 // version 4 b[8] = (b[8] & 0x3f) | 0x80 // RFC 4122 variant return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) } // WithCorrelation attaches id to ctx. func WithCorrelation(ctx context.Context, id string) context.Context { return context.WithValue(ctx, correlationKey{}, id) } // CorrelationFrom returns the correlation id attached to ctx, or "". func CorrelationFrom(ctx context.Context) string { id, _ := ctx.Value(correlationKey{}).(string) return id } // CorrelationID is the middleware form: it generates a correlation id, puts it // on the response header and the request context, then calls next. func CorrelationID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := NewCorrelationID() w.Header().Set(CorrelationHeader, id) next.ServeHTTP(w, r.WithContext(WithCorrelation(r.Context(), id))) }) }