require "spec_helper" describe "Rack::Attack" do around do |example| default_store = Rack::Attack.cache.store Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new example.run Rack::Attack.cache.store = default_store end before { freeze_time } def unique_ip_env { "REMOTE_ADDR" => Faker::Internet.unique.public_ip_v4_address } end def unique_user_params { user: { login: generate(:login), password: "secret" } } end def unique_admin_params { admin: { login: generate(:login), password: "secretpassword" } } end it "test utility returns valid parameters for successful user login attempts" do params = unique_user_params create(:user, login: params[:user][:login], password: params[:user][:password]) post user_session_path, params: params.to_query expect(response).to have_http_status(:redirect) end it "test utility returns valid parameters for successful admin login attempts" do params = unique_admin_params create(:admin, login: params[:admin][:login], password: params[:admin][:password]) post admin_session_path, params: params.to_query expect(response).to have_http_status(:redirect) end it "successful response does not include retry-after header" do get root_path, env: { "REMOTE_ADDR" => Faker::Internet.unique.public_ip_v4_address } expect(response).to have_http_status(:ok) expect(response.headers["Retry-After"]).to be_nil end context "when there have been max number of requests from an IP address" do let(:ip) { Faker::Internet.unique.public_ip_v4_address } before do ArchiveConfig.RATE_LIMIT_NUMBER.times do get root_path, env: { "REMOTE_ADDR" => ip } end end it "response to the next attempt from the same IP includes retry-after header" do get root_path, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:too_many_requests) expect(response.headers["Retry-After"].to_i).to be > 0 expect(response.headers["Retry-After"].to_i).to be <= ArchiveConfig.RATE_LIMIT_PERIOD end end context "when there have been max user login attempts from an IP address" do let(:ip) { Faker::Internet.unique.public_ip_v4_address } before do ArchiveConfig.RATE_LIMIT_LOGIN_ATTEMPTS.times do post user_session_path, params: unique_user_params.to_query, env: { "REMOTE_ADDR" => ip } end end it "response to the next attempt from the same IP includes retry-after header" do post user_session_path, params: unique_user_params.to_query, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:too_many_requests) expect(response.headers["Retry-After"].to_i).to be > 0 expect(response.headers["Retry-After"].to_i).to be <= ArchiveConfig.RATE_LIMIT_LOGIN_PERIOD end it "throttles the next attempt from the same IP" do post user_session_path, params: unique_user_params.to_query, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:too_many_requests) end it "does not throttle an attempt from a different IP" do post user_session_path, params: unique_user_params.to_query, env: unique_ip_env expect(response).to have_http_status(:ok) end it "does not throttle the next attempt from the same IP after some time" do travel ArchiveConfig.RATE_LIMIT_LOGIN_PERIOD.seconds post user_session_path, params: unique_user_params.to_query, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:ok) end end context "when there have been max user login attempts for a username" do let(:params) { unique_user_params.to_query } before do ArchiveConfig.RATE_LIMIT_LOGIN_ATTEMPTS.times do post user_session_path, params: params, env: unique_ip_env end end it "response to the next attempt for the same username includes retry-after header" do post user_session_path, params: params, env: unique_ip_env expect(response).to have_http_status(:too_many_requests) expect(response.headers["Retry-After"].to_i).to be > 0 expect(response.headers["Retry-After"].to_i).to be <= ArchiveConfig.RATE_LIMIT_LOGIN_PERIOD end it "throttles the next attempt for the same username" do post user_session_path, params: params, env: unique_ip_env expect(response).to have_http_status(:too_many_requests) end it "does not throttle an attempt for a different username" do post user_session_path, params: unique_user_params.to_query, env: unique_ip_env expect(response).to have_http_status(:ok) end it "does not throttle the next attempt for the same username after some time" do travel ArchiveConfig.RATE_LIMIT_LOGIN_PERIOD.seconds post user_session_path, params: params, env: unique_ip_env expect(response).to have_http_status(:ok) end end context "when there have been max admin login attempts from an IP address" do let(:ip) { Faker::Internet.unique.public_ip_v4_address } before do ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_ATTEMPTS.times do post admin_session_path, params: unique_admin_params.to_query, env: { "REMOTE_ADDR" => ip } end end it "response to the next attempt from the same IP includes retry-after header" do post admin_session_path, params: unique_admin_params.to_query, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:too_many_requests) expect(response.headers["Retry-After"].to_i).to be > 0 expect(response.headers["Retry-After"].to_i).to be <= ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_PERIOD.seconds end it "throttles the next attempt from the same IP" do post admin_session_path, params: unique_admin_params.to_query, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:too_many_requests) end it "does not throttle an attempt from a different IP" do post admin_session_path, params: unique_admin_params.to_query, env: unique_ip_env expect(response).to have_http_status(:ok) end it "does not throttle the next attempt from the same IP after some time" do travel ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_PERIOD.seconds post admin_session_path, params: unique_admin_params.to_query, env: { "REMOTE_ADDR" => ip } expect(response).to have_http_status(:ok) end end context "when there have been max admin login attempts for a username" do let(:params) { unique_admin_params.to_query } before do ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_ATTEMPTS.times do post admin_session_path, params: params, env: unique_ip_env end end it "response to the next attempt for the same username includes retry-after header" do post admin_session_path, params: params, env: unique_ip_env expect(response).to have_http_status(:too_many_requests) expect(response.headers["Retry-After"].to_i).to be > 0 expect(response.headers["Retry-After"].to_i).to be <= ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_PERIOD.seconds end it "throttles the next attempt for the same username" do post admin_session_path, params: params, env: unique_ip_env expect(response).to have_http_status(:too_many_requests) end it "does not throttle an attempt for a different username" do post admin_session_path, params: unique_admin_params.to_query, env: unique_ip_env expect(response).to have_http_status(:ok) end it "does not throttle the next attempt for the same username after some time" do travel ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_PERIOD.seconds post admin_session_path, params: params, env: unique_ip_env expect(response).to have_http_status(:ok) end end end