diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb new file mode 100644 index 000000000..253ed69c7 --- /dev/null +++ b/config/initializers/rack_attack.rb @@ -0,0 +1,8 @@ +Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new +Rack::Attack.enabled = false + +Rack::Attack.throttle("password reset requests", limit: 5, period: 60.seconds) do |request| + if request.params["user"].present? && request.path == "/users/password" && request.post? + request.params["user"]["email"].to_s.downcase.gsub(/\s+/, "") + end +end diff --git a/spec/requests/rack_attack_spec.rb b/spec/requests/rack_attack_spec.rb index a4950c150..3fd5cc850 100644 --- a/spec/requests/rack_attack_spec.rb +++ b/spec/requests/rack_attack_spec.rb @@ -11,20 +11,24 @@ describe "Rack::Attack" do let(:notify_client) { instance_double(Notifications::Client) } let(:devise_notify_mailer) { DeviseNotifyMailer.new } + let(:params) { { user: { email: } } } + let(:user) { FactoryBot.create(:user) } + let(:email) { user.email } + before do - Rack::Attack.enabled = false + Rack::Attack.enabled = true allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) allow(notify_client).to receive(:send_email).and_return(true) end - context "when a regular user" do - let(:params) { { user: { email: } } } - - context "when a password reset is requested for a valid email" do - let(:user) { FactoryBot.create(:user) } - let(:email) { user.email } + after do + Rack::Attack.enabled = false + Rack::Attack.reset! + end + context "when a password reset is requested" do + context "when the number of requests is under the throttle limit" do it "does not throttle" do under_limit.times do post "/users/password", params: params @@ -34,5 +38,26 @@ describe "Rack::Attack" do expect(last_response.status).to eq(200) end end + + context "when the number of requests is at the throttle limit" do + it "does not throttle" do + limit.times do + post "/users/password", params: params + follow_redirect! + end + last_response = response + expect(last_response.status).to eq(200) + end + end + + context "when the number of requests is over the throttle limit" do + it "throttles" do + over_limit.times do + post "/users/password", params: params + end + last_response = response + expect(last_response.status).to eq(429) + end + end end end