Rate-limit the password change endpoint (#65)
All checks were successful
check / check (push) Successful in 2m40s
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user