Rate-limit the password change endpoint (#65)
All checks were successful
check / check (push) Successful in 2m40s

The password change POST verifies the current password, making it
a password-based authentication endpoint that REPO_POLICIES.md
requires rate limiting on. Extract the login limiter's POST-only
per-IP pattern into a shared postRateLimit helper and apply the
same 5-per-minute limit to POST /user/{username}/password.
This commit is contained in:
2026-08-07 16:29:43 +00:00
parent 3084ed545b
commit 8362ce9ee0
4 changed files with 102 additions and 19 deletions

View File

@@ -46,14 +46,20 @@ func TestLoginRateLimit_AllowsGET(t *testing.T) {
assert.Equal(t, 20, callCount)
}
func TestLoginRateLimit_LimitsPOST(t *testing.T) {
t.Parallel()
m, _ := testMiddleware(t, config.EnvironmentDev)
// runPostLimitTest exercises a POST-only rate limit middleware:
// the first limit POSTs to path from ip must pass, and the next
// one must be rejected with 429 without reaching the handler.
func runPostLimitTest(
t *testing.T,
mw func(http.Handler) http.Handler,
limit int,
path, ip string,
) {
t.Helper()
var callCount int
handler := m.LoginRateLimit()(http.HandlerFunc(
handler := mw(http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
callCount++
@@ -61,13 +67,13 @@ func TestLoginRateLimit_LimitsPOST(t *testing.T) {
},
))
// First loginRateLimit POST requests should succeed
for i := range middleware.LoginRateLimitConst {
// The first limit POST requests should succeed
for i := range limit {
req := httptest.NewRequestWithContext(
context.Background(),
http.MethodPost, "/pages/login", nil,
http.MethodPost, path, nil,
)
req.RemoteAddr = "10.0.0.1:12345"
req.RemoteAddr = ip
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
@@ -81,9 +87,9 @@ func TestLoginRateLimit_LimitsPOST(t *testing.T) {
// Next POST should be rate-limited
req := httptest.NewRequestWithContext(
context.Background(),
http.MethodPost, "/pages/login", nil,
http.MethodPost, path, nil,
)
req.RemoteAddr = "10.0.0.1:12345"
req.RemoteAddr = ip
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
@@ -92,7 +98,35 @@ func TestLoginRateLimit_LimitsPOST(t *testing.T) {
t, http.StatusTooManyRequests, w.Code,
"POST after limit should be 429",
)
assert.Equal(t, middleware.LoginRateLimitConst, callCount)
assert.Equal(t, limit, callCount)
}
func TestLoginRateLimit_LimitsPOST(t *testing.T) {
t.Parallel()
m, _ := testMiddleware(t, config.EnvironmentDev)
runPostLimitTest(
t,
m.LoginRateLimit(),
middleware.LoginRateLimitConst,
"/pages/login",
"10.0.0.1:12345",
)
}
func TestPasswordChangeRateLimit_LimitsPOST(t *testing.T) {
t.Parallel()
m, _ := testMiddleware(t, config.EnvironmentDev)
runPostLimitTest(
t,
m.PasswordChangeRateLimit(),
middleware.PasswordChangeRateLimitConst,
"/user/admin/password",
"10.0.0.2:12345",
)
}
func TestLoginRateLimit_IndependentPerIP(t *testing.T) {