fix(api): stop sending X-RateLimit headers from API v1

Rate-limit counts are per node, so X-RateLimit-Remaining would mislead
clients once API v1 runs behind more than one instance. API v1 now sends
only Retry-After on 429; v0 and Jellyfin limiters keep their headers.
This commit is contained in:
Deluan 2026-09-26 12:54:51 -04:00
commit c205b894aa
3 changed files with 19 additions and 1 deletions

View file

@ -93,6 +93,8 @@ func newGate(doc *openapi3.T, mux chi.Routes, auth authenticator, rules gateRule
}
if conf.Server.AuthRequestLimit > 0 {
g.limiter = server.ClientIPRateLimiter(conf.Server.AuthRequestLimit, conf.Server.AuthWindowLength,
// Counts are per node, so X-RateLimit-Remaining would mislead clients of a scaled-out server.
httprate.WithResponseHeaders(httprate.ResponseHeaders{RetryAfter: "Retry-After"}),
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
writeProblemStatus(w, r, http.StatusTooManyRequests, ProblemCodeRateLimited, "too many requests")
}))

View file

@ -370,10 +370,13 @@ var _ = Describe("spec gate", func() {
var err error
mux, err = build(gateSpec)
Expect(err).ToNot(HaveOccurred())
Expect(do(http.MethodPost, "/limited", "", "").Code).To(Equal(http.StatusOK))
w := do(http.MethodPost, "/limited", "", "")
Expect(w.Code).To(Equal(http.StatusOK))
expectNoXRateLimitHeaders(w)
w = do(http.MethodPost, "/limited", "", "")
Expect(w.Code).To(Equal(http.StatusTooManyRequests))
Expect(w.Header().Get("Retry-After")).ToNot(BeEmpty())
expectNoXRateLimitHeaders(w)
Expect(decodeProblem(w).Code).To(Equal(ProblemCodeRateLimited))
})
@ -436,3 +439,10 @@ func captureLogs() *bytes.Buffer {
})
return buf
}
func expectNoXRateLimitHeaders(w *httptest.ResponseRecorder) {
GinkgoHelper()
for _, h := range []string{"X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Increment", "X-RateLimit-Reset"} {
Expect(w.Header().Values(h)).To(BeEmpty(), h)
}
}

View file

@ -549,6 +549,12 @@ var _ = Describe("middlewares", func() {
Entry("True-Client-IP", "True-Client-IP"),
)
It("sends the X-RateLimit headers by default", func() {
w := httptest.NewRecorder()
handler.ServeHTTP(w, httptest.NewRequestWithContext(GinkgoT().Context(), "POST", "/auth/login", nil))
Expect(w.Header().Get("X-RateLimit-Limit")).To(Equal("2"))
})
Context("behind a trusted proxy", func() {
BeforeEach(func() {
conf.Server.ExtAuth.TrustedSources = "10.0.0.0/8"