Search code examples
ruby-on-railsrubyrubygemsruby-on-rails-pluginscaptcha

Gem-idea: Automatic spam protection with captcha in before_filter when HTTP-method is post,put or delete


I'm thinking about writing an automatic spam protection system (maybe I will write a public gem) for rails.

My concept is to include a helper method in application_controller f.e.:

class ApplicationController < ActionController::Base
  automatic_captcha_redirect(:min_time => 30.seconds :limit => 50)
...
end

Then I want to include automatical a before_filter in every controller, which checks, if the current request is via post, put or delete-method.

If the user's last post-request is smaller than :min_time, then the request should be redirected to an captcha-input-page (the posted user-data resides in hidden html fields).

# before_filter :check_spam
def check_spam
  if !request.get? && session[:last_manipulation_at] 
      && session[:last_manipulation_at] >= DateTime.now - 30.seconds
    redirect_to captcha_path 
      # (doesn't know yet how to handle the post data to 
      # display in hidden fields in the spam-captcha-form)
  end
end

And in captcha.haml

=form_tag 
-request.params.each do |key, value|
  =hidden_field_tag key, value

=captcha_image
=submit_button_tag

If the user submits the right captcha-word, his data will be posted to the right action.

Do you think thats realizable? Any critics or suggestions? Or an idea how to realize this behaviour?

EDIT:

  • this should not pass through all the ActiveRecord stack; can't it be implemented as a middleware hook (Rails Rack)?
    • Yes, would be a good idea - but I'm not very familiar with rails rack :/
  • what about file uploads? (you can not store it in a hidden file)
    • Hm... maybe a check if there is a file in the post? (How could that be realized?)
  • what about Ajax posting?
    • Maybe sending back http-status codes (f.e. 503 Service temporary unavailable)
  • why only POST and not also PUT and DELETE?
    • corrected this in my question

EDIT:

First structure of processing (as non rack-app - I dont know how to write rack apps):

0) Settings in environment.rb

auto_recaptcha[:limit] = 10
auto_recaptcha[:min_time] = 1.minute

1) User posts data

Check last_manipulation and max. amount of allowed manipultations in application_controller.rb

class ApplicationController < ActionController::Base
  before_filter :automatic_captcha_redirect

  def automatic_captcha_redirect
    session[:last_manipulation_at][:manipultation] = [] unless session[:last_manipulation_at][:manipultation]
    # Checks if requests are falling under the specifications for showing captcha


    if !request.get? 
       && session[:last_manipulation_at][:date] > DateTime.now - auto_recaptcha[:min_time] 
       && session[:last_manipulation_at][:manipultation].count < auto_recaptcha[:limit]

      # If user answered captcha, verify it
      if !verify_captcha(params)
        @url = request.url
        @params = request.params
        render "layouts/captcha.haml"
      else

        # Add successfull manipulation to counter
        session[:last_manipulation_at][:manipultation] << DateTime.now
        session[:last_manipulation_at][:date] = DateTime.now
      end
    end
  end
end

captcha.haml

-form_tag @url do 
  -request.params.each do |key, value|
    =hidden_field_tag key, value

  =captcha_image
  =submit_button_tag

2) ... ... ...

last) Post userdata to the right location

post(params) => users_path # path "/users" with method: post

Solution

  • One way this could be put together:

    • Middleware/rails metal component that monitors the requests and adds the information to the rack session.

    • Controller helpers for before_filters on things that might need captchas

    • View helpers for displaying the captchas

    You could make the captcha rate adjustable through the args passing mechanism of use

    #config/environment.rb
    config.middleware.use 'CaptchaMiddleware',:period=>5.minutes,:limit=>50,:captcha_url=>'/captcha'
    

    Also, this should not rely on hidden form fields because a determined bot writer could just change the value they are posting to your server code.

    Simple middleware example code(slightly better than a stab in the dark, but still)

    class CaptchaMiddleware
      def initialize app,options
        @app = app
        @options=options
      end
    
      def update_stats!
        #session based,on account of laziness
        session[:reqs] ||= []
        session[:reqs].reject!{ |request| request < Time.now - @options[:period]}
        session[:reqs] << Time.now
      end
    
      def over_limit?
        session[:reqs].length > @options[:limit]
      end
    
      def call env
        @env = env
        if @env["REQUEST_METHOD"]!='GET'
          update_stats!
          if over_limit?
            return [302,{"Location: #{options[:captcha_url]}"},'']
          end
        end
        @app.call env
      end
    
      def session
        @env["rack.session"]
      end
    end