package api // #81: ALL password verification attempts (GET query param, header, POST // form) must go through the per-IP unlock limiter. Regression: N wrong // passwords via X-Paste-Password must eventually yield 429. import ( "encoding/json" "net/http/httptest" "strings" "testing" ) func createPasswordPaste(t *testing.T, s *apiServer, pw string) string { t.Helper() h := s.routes() body := `{"content":"secret","password":"` + pw + `"}` req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(body)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) var created struct { ID string `json:"id"` } json.Unmarshal(rec.Body.Bytes(), &created) return created.ID } // TestRateLimitGetPasswordQuery: repeated wrong passwords via GET // X-Paste-Password wrong attempts must eventually return 429 (unlock limiter: burst 5). func TestRateLimitGetPasswordQuery(t *testing.T) { s := testServer(t) h := s.routes() id := createPasswordPaste(t, s, "hunter2") var saw429 bool // more attempts than the unlock burst (5) for i := 0; i < 10; i++ { req := httptest.NewRequest("GET", "/api/pastes/"+id, nil) req.Header.Set("X-Paste-Password", "wrong"+string(rune('a'+i))) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code == 429 { saw429 = true break } if rec.Code != 401 { t.Fatalf("attempt %d: expected 401 before limit, got %d", i, rec.Code) } } if !saw429 { t.Fatal("expected 429 after repeated wrong password attempts, never got one") } } // TestRateLimitGetPasswordHeader: same guarantee for the X-Paste-Password header path. func TestRateLimitGetPasswordHeader(t *testing.T) { s := testServer(t) h := s.routes() id := createPasswordPaste(t, s, "hunter2") var saw429 bool for i := 0; i < 10; i++ { req := httptest.NewRequest("GET", "/api/pastes/"+id, nil) req.Header.Set("X-Paste-Password", "wrong"+string(rune('a'+i))) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code == 429 { saw429 = true break } if rec.Code != 401 { t.Fatalf("attempt %d: expected 401 before limit, got %d", i, rec.Code) } } if !saw429 { t.Fatal("expected 429 after repeated wrong header password attempts, never got one") } } // TestRateLimitGetPasswordCorrectStillAllowed: a correct password must still // work within the burst (the limiter gates attempts, not correctness). func TestRateLimitGetPasswordCorrectStillAllowed(t *testing.T) { s := testServer(t) h := s.routes() id := createPasswordPaste(t, s, "hunter2") req := httptest.NewRequest("GET", "/api/pastes/"+id, nil) req.Header.Set("X-Paste-Password", "hunter2") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("expected 200 for correct password within burst, got %d", rec.Code) } }