login_limiter_test.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. package controller
  2. import (
  3. "testing"
  4. "time"
  5. )
  6. func TestLoginLimiterBlocksAfterConfiguredFailures(t *testing.T) {
  7. now := time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC)
  8. limiter := newLoginLimiter(5, 5*time.Minute, 15*time.Minute)
  9. limiter.now = func() time.Time { return now }
  10. for i := 0; i < 4; i++ {
  11. if _, blocked := limiter.registerFailure("192.0.2.10", "Admin"); blocked {
  12. t.Fatalf("failure %d should not block yet", i+1)
  13. }
  14. if _, ok := limiter.allow("192.0.2.10", "admin"); !ok {
  15. t.Fatalf("failure %d should still allow login attempts", i+1)
  16. }
  17. }
  18. blockedUntil, blocked := limiter.registerFailure("192.0.2.10", "ADMIN")
  19. if !blocked {
  20. t.Fatal("fifth failure should start cooldown")
  21. }
  22. if want := now.Add(15 * time.Minute); !blockedUntil.Equal(want) {
  23. t.Fatalf("blocked until %s, want %s", blockedUntil, want)
  24. }
  25. if _, ok := limiter.allow("192.0.2.10", "admin"); ok {
  26. t.Fatal("login should be blocked during cooldown")
  27. }
  28. now = blockedUntil
  29. if _, ok := limiter.allow("192.0.2.10", "admin"); !ok {
  30. t.Fatal("login should be allowed after cooldown")
  31. }
  32. }
  33. func TestLoginLimiterPrunesOldFailuresAndResetsOnSuccess(t *testing.T) {
  34. now := time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC)
  35. limiter := newLoginLimiter(5, 5*time.Minute, 15*time.Minute)
  36. limiter.now = func() time.Time { return now }
  37. for i := 0; i < 4; i++ {
  38. limiter.registerFailure("192.0.2.10", "admin")
  39. }
  40. now = now.Add(6 * time.Minute)
  41. if _, blocked := limiter.registerFailure("192.0.2.10", "admin"); blocked {
  42. t.Fatal("old failures should be pruned outside the rolling window")
  43. }
  44. limiter.registerSuccess("192.0.2.10", "admin")
  45. for i := 0; i < 4; i++ {
  46. if _, blocked := limiter.registerFailure("192.0.2.10", "admin"); blocked {
  47. t.Fatalf("success should reset previous failures; failure %d blocked", i+1)
  48. }
  49. }
  50. }
  51. func TestLoginLimiterSeparatesIPAndUsername(t *testing.T) {
  52. now := time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC)
  53. limiter := newLoginLimiter(5, 5*time.Minute, 15*time.Minute)
  54. limiter.now = func() time.Time { return now }
  55. for i := 0; i < 5; i++ {
  56. limiter.registerFailure("192.0.2.10", "admin")
  57. }
  58. if _, ok := limiter.allow("192.0.2.11", "admin"); !ok {
  59. t.Fatal("different IP should not be blocked")
  60. }
  61. if _, ok := limiter.allow("192.0.2.10", "other-admin"); !ok {
  62. t.Fatal("different username should not be blocked")
  63. }
  64. }