class Rack::Attack throttle("logins/email", limit: 5, period: 5.minutes) do |req| if req.path == "/login" && req.post? req.params["email"]&.to_s&.downcase&.presence end end throttle("logins/ip", limit: 10, period: 5.minutes) do |req| if req.path == "/login" && req.post? req.ip end end throttle("email_verify/attempt", limit: 10, period: 5.minutes) do |req| if req.path.match?(%r{^/login/(.+)/verify$}) && req.post? $1 end end throttle("totp_login/attempt", limit: 5, period: 5.minutes) do |req| if req.path.match?(%r{^/login/(.+)/totp$}) && req.post? $1 end end throttle("backup_code_login/attempt", limit: 5, period: 5.minutes) do |req| if req.path.match?(%r{^/login/(.+)/backup_code$}) && req.post? $1 end end throttle("login_verify/ip", limit: 20, period: 5.minutes) do |req| if req.path.match?(%r{^/login/.+/(verify|totp|backup_code)$}) && req.post? req.ip end end throttle("email_change/ip", limit: 3, period: 1.hour) do |req| if req.path == "/email_changes" && req.post? req.ip end end throttle("email_change_verify/ip", limit: 10, period: 5.minutes) do |req| if req.path.match?(%r{^/email_changes/verify/(old|new)$}) && %w[GET POST].include?(req.request_method) req.ip end end self.throttled_responder = lambda do |env| headers = { "Content-Type" => "text/html", "Retry-After" => "300" # } message = "slow your roll!" [ 429, headers, [ message ] ] end end