Bo Jeanes, a coworker at Mocra, recently implemented a way to ban people on one of his side projects DearQUT because somebody was posting nasty messages. We were talking earlier today about it and the topic of using a middleware came up since we “don’t want to waste resources on that asshole”. So I thought I’d investigate to to see how easy it is to make a middleware in Rails, turns out it’s very easy!. If you don’t like reading blog posts, I have sample Rails application containing the very same source code I used for this blog post. This contains also the “Special Github” extras like an admin section for adding/editing/deleting banned IPs! I also “cheated” by stealing elements from the Railscast on Rack Middleware.

The Ban Hammer

First off I generated a model called BannedIP by running script/generate model BannedIP ip:string and ran rake db:migrate to create the database and the banned_ips table.

After that, I made a file called lib/hammer.rb and it goes a little like:

class Hammer
  def initialize(app)
    @app = app
  end
  
  def call(env)
    if BannedIP.find_by_ip(env["REMOTE_ADDR"])
      file = "#{RAILS_ROOT}/public/banned.html"
      [403, {"Content-Type" => "text/html" }, [File.read(file)]]
    else
      @status, @headers, @response = @app.call(env)
      [@status, @headers, self]
    end
  end
  
  def each(&block)
    @response.each(&block)
  end
end

Eagle-eyes will see that this is almost a blatant rip-off of Ryan Bates’ code. Ignore that part. Admire that I call BannedIP.find_by_ip(env[“REMOTE_ADDR”]) which will return an BannedIP object if one exists, otherwise it’ll return nil. in the case that someone’s banned then it’ll show them a very lightweight page with “You have been banned from this site.” and they’ll feel guilty and stuff (this feature actually coming in v2).

Now to use this middleware you have to add config.middleware.use ‘Hammer’ to your config/environment.rb file and (of course) restart the server. Every request will of course query the database once more which, if you’re running a large big HUGE site can lead to performance issues.

Of course you could just use iptables and do something like iptables -I INPUT -s 25.55.55.55 -j DROP, but then they won’t be told why they’re banned.