Files
beacon-server/internal/api/middleware/rate_limit.go
T

37 lines
1.3 KiB
Go

// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package middleware
import (
"net/http"
"time"
"github.com/go-chi/httprate"
"github.com/MeshCore-Beacon/beacon-server/internal/config"
)
// RateLimit applies the minute budget and one-second burst cap to the API.
// TrustedProxyIP must run first; the access logger must wrap this middleware.
func RateLimit(cfg config.ResolvedRateLimitConfig) func(http.Handler) http.Handler {
if !cfg.Enabled {
return func(next http.Handler) http.Handler { return next }
}
options := []httprate.Option{
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte(`{"error":{"code":"rate_limited","message":"Request rate limit exceeded. Retry later."}}`))
}),
// Only expose the backoff header, since the two windows have different budgets.
httprate.WithResponseHeaders(httprate.ResponseHeaders{RetryAfter: "Retry-After"}),
}
// RemoteAddr has already been resolved and forwarding headers removed.
minute := httprate.LimitBy(cfg.RequestsPerMinute, time.Minute, httprate.KeyByIP, options...)
burst := httprate.LimitBy(cfg.Burst, time.Second, httprate.KeyByIP, options...)
return func(next http.Handler) http.Handler {
return minute(burst(next))
}
}