rack_attack_spec.rb 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. require 'rails_helper'
  2. describe Rack::Attack, type: :request do
  3. def app
  4. Rails.application
  5. end
  6. shared_examples 'throttled endpoint' do
  7. context 'when the number of requests is lower than the limit' do
  8. it 'does not change the request status' do
  9. limit.times do
  10. request.call
  11. expect(response.status).to_not eq(429)
  12. end
  13. end
  14. end
  15. context 'when the number of requests is higher than the limit' do
  16. it 'returns http too many requests' do
  17. (limit * 2).times do |i|
  18. request.call
  19. expect(response.status).to eq(429) if i > limit
  20. end
  21. end
  22. end
  23. end
  24. let(:remote_ip) { '1.2.3.5' }
  25. describe 'throttle excessive sign-up requests by IP address' do
  26. context 'through the website' do
  27. let(:limit) { 25 }
  28. let(:request) { ->() { post path, headers: { 'REMOTE_ADDR' => remote_ip } } }
  29. context 'for exact path' do
  30. let(:path) { '/auth' }
  31. it_behaves_like 'throttled endpoint'
  32. end
  33. context 'for path with format' do
  34. let(:path) { '/auth.html' }
  35. it_behaves_like 'throttled endpoint'
  36. end
  37. end
  38. context 'through the API' do
  39. let(:limit) { 5 }
  40. let(:request) { ->() { post path, headers: { 'REMOTE_ADDR' => remote_ip } } }
  41. context 'for exact path' do
  42. let(:path) { '/api/v1/accounts' }
  43. it_behaves_like 'throttled endpoint'
  44. end
  45. context 'for path with format' do
  46. let(:path) { '/api/v1/accounts.json' }
  47. it 'returns http not found' do
  48. request.call
  49. expect(response.status).to eq(404)
  50. end
  51. end
  52. end
  53. end
  54. describe 'throttle excessive sign-in requests by IP address' do
  55. let(:limit) { 25 }
  56. let(:request) { ->() { post path, headers: { 'REMOTE_ADDR' => remote_ip } } }
  57. context 'for exact path' do
  58. let(:path) { '/auth/sign_in' }
  59. it_behaves_like 'throttled endpoint'
  60. end
  61. context 'for path with format' do
  62. let(:path) { '/auth/sign_in.html' }
  63. it_behaves_like 'throttled endpoint'
  64. end
  65. end
  66. describe 'throttle excessive password change requests by account' do
  67. let(:user) { Fabricate(:user, email: 'user@host.example') }
  68. let(:limit) { 10 }
  69. let(:period) { 10.minutes }
  70. let(:request) { -> { put path, headers: { 'REMOTE_ADDR' => remote_ip } } }
  71. let(:path) { '/auth' }
  72. before do
  73. sign_in user, scope: :user
  74. # Unfortunately, devise's `sign_in` helper causes the `session` to be
  75. # loaded in the next request regardless of whether it's actually accessed
  76. # by the client code.
  77. #
  78. # So, we make an extra query to clear issue a session cookie instead.
  79. #
  80. # A less resource-intensive way to deal with that would be to generate the
  81. # session cookie manually, but this seems pretty involved.
  82. get '/'
  83. end
  84. it_behaves_like 'throttled endpoint'
  85. end
  86. end