mirror of
https://github.com/penpot/penpot.git
synced 2026-09-25 21:36:15 +00:00
* 🐛 Restore rate-limit headers and add Retry-After The account-lockout change replaced the header-forwarding 429 handler with a body-only one, so existing RPC rate-limit responses lost their x-rate-limit-remaining and x-rate-limit-reset headers. Account lockout never sent Retry-After either. Make handle-error :rate-limit preserve ::http/headers and add a retry-after header when the exception carries a non-nil :ttl in seconds, keeping the current JSON body. Add focused tests for both the lockout and the RPC limiter paths. Document activation, defaults, password/LDAP scope, Redis fail-open behavior, and the lockout risk, and record the final HTTP contract in the backend auth memory. Refs #11397 AI-assisted-by: deepseek-v4.1-flash * 🐛 Add Retry-After to RPC 429 and expose headers in CORS Address review follow-ups on the account-lockout 429 contract: - The RPC limiter now sets retry-after in its 429 headers (seconds until the longest rejecting limit resets), so it matches the account-lockout response and the HTTP standard. - CORS exposes retry-after, x-rate-limit-remaining, and x-rate-limit-reset so browser clients can read them. - Use backticks for Retry-After and account-locked in the docs for consistency with nearby sections. Refs #11397 AI-assisted-by: deepseek-v4.1-flash
71 lines
3.1 KiB
Clojure
71 lines
3.1 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
|
|
|
(ns backend-tests.http-errors-test
|
|
"Focused tests for the HTTP error mapping in `app.http.errors`."
|
|
(:require
|
|
[app.http :as-alias http]
|
|
[app.http.errors :as http-errors]
|
|
[clojure.test :as t]
|
|
[yetti.response :as yres]))
|
|
|
|
(t/deftest account-lockout-returns-429-with-retry-after
|
|
(let [cause (ex-info "account locked"
|
|
{:type :rate-limit
|
|
:code :account-locked
|
|
:hint "account locked due to too many failed login attempts"
|
|
:ttl 900})
|
|
response (http-errors/handle cause {})
|
|
headers (::yres/headers response)
|
|
body (::yres/body response)]
|
|
(t/is (= 429 (::yres/status response)))
|
|
(t/is (= "900" (get headers "retry-after")))
|
|
(t/is (= :rate-limit (:type body)))
|
|
(t/is (= :account-locked (:code body)))
|
|
(t/is (= 900 (:ttl body)))))
|
|
|
|
(t/deftest rpc-rate-limit-preserves-headers
|
|
(let [cause (ex-info "rate limit reached"
|
|
{:type :rate-limit
|
|
:code :request-blocked
|
|
:hint "rate limit reached"
|
|
::http/headers {"x-rate-limit-remaining" "3"
|
|
"x-rate-limit-reset" "60"}})
|
|
response (http-errors/handle cause {})
|
|
headers (::yres/headers response)
|
|
body (::yres/body response)]
|
|
(t/is (= 429 (::yres/status response)))
|
|
(t/is (= "3" (get headers "x-rate-limit-remaining")))
|
|
(t/is (= "60" (get headers "x-rate-limit-reset")))
|
|
(t/is (nil? (get headers "retry-after")))
|
|
(t/is (= :rate-limit (:type body)))
|
|
(t/is (= :request-blocked (:code body)))))
|
|
|
|
(t/deftest rate-limit-with-ttl-keeps-headers-and-adds-retry-after
|
|
(let [cause (ex-info "rate limit reached"
|
|
{:type :rate-limit
|
|
:code :request-blocked
|
|
:hint "rate limit reached"
|
|
:ttl 120
|
|
::http/headers {"x-rate-limit-remaining" "0"
|
|
"x-rate-limit-reset" "120"}})
|
|
response (http-errors/handle cause {})
|
|
headers (::yres/headers response)]
|
|
(t/is (= 429 (::yres/status response)))
|
|
(t/is (= "0" (get headers "x-rate-limit-remaining")))
|
|
(t/is (= "120" (get headers "x-rate-limit-reset")))
|
|
(t/is (= "120" (get headers "retry-after")))))
|
|
|
|
(t/deftest rate-limit-without-ttl-has-no-retry-after
|
|
(let [cause (ex-info "rate limit reached"
|
|
{:type :rate-limit
|
|
:code :request-blocked
|
|
:hint "rate limit reached"})
|
|
response (http-errors/handle cause {})
|
|
headers (::yres/headers response)]
|
|
(t/is (= 429 (::yres/status response)))
|
|
(t/is (nil? (get headers "retry-after")))))
|