module Authlogic module Session # A brute force attacks is executed by hammering a login with as many password combinations as possible, until one works. A brute force attacked is # generally combated with a slow hasing algorithm such as BCrypt. You can increase the cost, which makes the hash generation slower, and ultimately # increases the time it takes to execute a brute force attack. Just to put this into perspective, if a hacker was to gain access to your server # and execute a brute force attack locally, meaning there is no network lag, it would probably take decades to complete. Now throw in network lag # and it would take MUCH longer. # # But for those that are extra paranoid and can't get enough protection, why not stop them as soon as you realize something isn't right? That's # what this module is all about. By default the consecutive_failed_logins_limit configuration option is set to 50, if someone consecutively fails to login # after 50 attempts their account will be suspended. This is a very liberal number and at this point it should be obvious that something is not right. # If you wish to lower this number just set the configuration to a lower number: # # class UserSession < Authlogic::Session::Base # consecutive_failed_logins_limit 10 # end module BruteForceProtection def self.included(klass) klass.class_eval do extend Config include InstanceMethods validate :reset_failed_login_count, :if => :reset_failed_login_count? validate :validate_failed_logins, :if => :being_brute_force_protected? end end # Configuration for the brute force protection feature. module Config # To help protect from brute force attacks you can set a limit on the allowed number of consecutive failed logins. By default this is 50, this is a very liberal # number, and if someone fails to login after 50 tries it should be pretty obvious that it's a machine trying to login in and very likely a brute force attack. # # In order to enable this field your model MUST have a failed_login_count (integer) field. # # If you don't know what a brute force attack is, it's when a machine tries to login into a system using every combination of character possible. Thus resulting # in possibly millions of attempts to log into an account. # # * Default: 50 # * Accepts: Integer, set to 0 to disable def consecutive_failed_logins_limit(value = nil) rw_config(:consecutive_failed_logins_limit, value, 50) end alias_method :consecutive_failed_logins_limit=, :consecutive_failed_logins_limit # Once the failed logins limit has been exceed, how long do you want to ban the user? This can be a temporary or permanent ban. # # * Default: 2.hours # * Accepts: Fixnum, set to 0 for permanent ban def failed_login_ban_for(value = nil) rw_config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i) end alias_method :failed_login_ban_for=, :failed_login_ban_for end # The methods available for an Authlogic::Session::Base object that make up the brute force protection feature. module InstanceMethods # Returns true when the consecutive_failed_logins_limit has been exceeded and is being temporarily banned. # Notice the word temporary, the user will not be permanently banned unless you choose to do so with configuration. # By default they will be banned for 2 hours. During that 2 hour period this method will return true. def being_brute_force_protected? exceeded_failed_logins_limit? && (failed_login_ban_for <= 0 || (attempted_record.respond_to?(:updated_at) && attempted_record.updated_at >= failed_login_ban_for.seconds.ago)) end private def exceeded_failed_logins_limit? !attempted_record.nil? && attempted_record.respond_to?(:failed_login_count) && consecutive_failed_logins_limit > 0 && attempted_record.failed_login_count && attempted_record.failed_login_count >= consecutive_failed_logins_limit end def reset_failed_login_count? exceeded_failed_logins_limit? && !being_brute_force_protected? end def reset_failed_login_count attempted_record.failed_login_count = 0 end def validate_failed_logins errors.clear # Clear all other error messages, as they are irrelevant at this point and can only provide additional information that is not needed errors.add(:base, I18n.t( 'error_messages.consecutive_failed_logins_limit_exceeded', :default => "Consecutive failed logins limit exceeded, account has been" + (failed_login_ban_for == 0 ? "" : " temporarily") + " disabled." )) end def consecutive_failed_logins_limit self.class.consecutive_failed_logins_limit end def failed_login_ban_for self.class.failed_login_ban_for end end end end end