package middleware import ( "net/http" "github.com/veylant/ia-gateway/internal/apierror" "github.com/veylant/ia-gateway/internal/ratelimit" ) // RateLimit returns a middleware that enforces per-tenant and per-user request // rate limits using token buckets. On limit exceeded it writes HTTP 429 in the // OpenAI-compatible JSON error format and aborts the request. // // The middleware must run AFTER the Auth middleware so that tenant/user claims // are available in the request context. If no claims are present (anonymous // request that wasn't rejected by Auth), the request passes through. func RateLimit(limiter *ratelimit.Limiter) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { claims, ok := ClaimsFromContext(r.Context()) if !ok || claims == nil { // Auth middleware handles unauthenticated requests separately. next.ServeHTTP(w, r) return } if !limiter.Allow(claims.TenantID, claims.UserID) { apierror.WriteErrorWithRequestID(w, apierror.NewRateLimitError( "rate limit exceeded — try again later", ), RequestIDFromContext(r.Context())) return } next.ServeHTTP(w, r) }) } }