- store: SQLite (WAL, foreign keys ON, CGO-free driver) + the embedded migration runner; each module's migrations are name-prefixed (D-012) - audit: the append-only audit log; the actor threads via context (R-1) - users: the user_id/GUID/email anchors (D-013) - config: the YAML settings file (D-035) + validation - httpx: cookies · loopback detection · trusted-proxy XFF · CSRF · security headers · redaction - middleware: correlation id · audit · rate limiting - mail: the invite/recovery mailer seam - server: the core DOOR — the resolution switch, the dispatch registry (RegisterModule), the role gates, /setup · enroll/login · recovery flows, the reserved `static` workflow, and the loopback-only bootstrap (D-025)
57 lines
2 KiB
Go
57 lines
2 KiB
Go
// 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)))
|
|
})
|
|
}
|