mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
* feat(redis): support standalone and cluster clients Co-authored-by: multica-agent <[email protected]> * fix(redis): use one global Redis configuration Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]>
41 lines
1.3 KiB
Go
41 lines
1.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
publicapiv1 "github.com/multica-ai/multica/server/pkg/publicapi/v1"
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// PluginRateLimit applies one fixed-window budget to each opaque Plugin
|
|
// credential. The token is hashed before it is used as a Redis key; secrets
|
|
// must never enter logs or operational data. A missing Redis client fails open,
|
|
// matching the existing public-edge limiter behavior.
|
|
func PluginRateLimit(rdb redis.UniversalClient, limit int, window time.Duration) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
if rdb == nil {
|
|
return next
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
digest := sha256.Sum256([]byte(BearerToken(r)))
|
|
key := fmt.Sprintf("mul:ratelimit:plugin:%x", digest)
|
|
count, err := rateLimitScript.Run(r.Context(), rdb, []string{key}, int(window.Seconds())).Int64()
|
|
if err != nil {
|
|
slog.Warn("plugin ratelimit: redis error; allowing request", "error", err)
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if count > int64(limit) {
|
|
w.Header().Set("Retry-After", fmt.Sprintf("%d", int(window.Seconds())))
|
|
publicapiv1.WriteProblem(w, r, http.StatusTooManyRequests, "rate_limited", "Plugin API rate limit exceeded")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|