isIPsafe
Ruby and Rails logo

Ruby and Rails

Check and report with Net::HTTP, and block in Rails with Rack::Attack.

The standard library is enough to check and report. In Rails, plug the check into Rack::Attack, which most apps already use for throttling.

Check and report

require "net/http"
require "json"

ISIPSAFE = URI("https://isipsafe.com")

def isipsafe_score(ip)
  uri = URI.join(ISIPSAFE, "/api/v1/check")
  uri.query = URI.encode_www_form(ip: ip)
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 2, read_timeout: 3) do |http|
    http.get(uri.request_uri)
  end
  res.is_a?(Net::HTTPSuccess) ? JSON.parse(res.body).dig("data", "abuseConfidenceScore").to_i : 0
rescue StandardError
  0 # fail open
end

def isipsafe_report(ip, categories, comment = "")
  uri = URI.join(ISIPSAFE, "/api/v1/report")
  req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{ENV.fetch('ISIPSAFE_KEY')}")
  req.set_form_data(ip: ip, categories: categories.join(","), comment: comment)
  Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 2, read_timeout: 5) { |http| http.request(req) }
end

isipsafe_report("203.0.113.50", %w[web-app-attack brute-force], "40 failed logins in 5 minutes")

Block in Rails with Rack::Attack

In config/initializers/rack_attack.rb, block listed addresses on the login and sign-up endpoints, caching each score for an hour:

Rack::Attack.blocklist("isipsafe listed") do |req|
  next false unless req.post? && %w[/users/sign_in /users].include?(req.path)

  score = Rails.cache.fetch("isipsafe:#{req.ip}", expires_in: 1.hour) { isipsafe_score(req.ip) }
  score >= 75
end

Pair it with Rack::Attack's track or your Devise failure hooks to call isipsafe_report for addresses that keep failing.

Need a hand?

If something here does not work with your version of Ruby and Rails, or you have improved it, tell us. We update these pages with what people send.

Ruby and Rails and its logo are trademarks of their respective owners. isipsafe is not affiliated with or endorsed by them.