Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

adding password_reset hammering protection

  • Loading branch information...
commit 61f28e766dc397af298de2f4f67b0b1c04dd4feb 1 parent a01719c
@NoamB authored
View
3  README.rdoc
@@ -25,6 +25,7 @@ Password Reset (see lib/sorcery/model/submodules/password_reset.rb):
* Reset password with email verification.
* configurable mailer, method name, and attribute name.
* configurable expiration.
+* configurable time between emails (hammering protection).
Remember Me (see lib/sorcery/model/submodules/remember_me.rb):
* Remember me with configurable expiration.
@@ -55,9 +56,9 @@ Other:
== Next Planned Features:
I've got many plans which include:
-* Hammering reset password protection
* Configurable Auto login on registration/activation
* Other reset password strategies (security questions?)
+* Other brute force protection strategies (captcha, lock account)
* Sinatra support
* Mongoid support
* OmniAuth integration
View
12 lib/sorcery/model/submodules/password_reset.rb
@@ -7,18 +7,22 @@ def self.included(base)
base.sorcery_config.class_eval do
attr_accessor :reset_password_code_attribute_name, # reset password code attribute name.
:reset_password_code_expires_at_attribute_name, # expires at attribute name.
+ :reset_password_email_sent_at_attribute_name, # when was email sent, used for hammering protection.
:reset_password_mailer, # mailer class. Needed.
:reset_password_email_method_name, # reset password email method on your mailer class.
- :reset_password_expiration_period # how many seconds before the reset request expires. nil for never expires.
+ :reset_password_expiration_period, # how many seconds before the reset request expires. nil for never expires.
+ :reset_password_time_between_emails # hammering protection, how long to wait before allowing another email to be sent.
end
base.sorcery_config.instance_eval do
@defaults.merge!(:@reset_password_code_attribute_name => :reset_password_code,
:@reset_password_code_expires_at_attribute_name => :reset_password_code_expires_at,
+ :@reset_password_email_sent_at_attribute_name => :reset_password_email_sent_at,
:@reset_password_mailer => nil,
:@reset_password_email_method_name => :reset_password_email,
- :@reset_password_expiration_period => nil )
+ :@reset_password_expiration_period => nil,
+ :@reset_password_time_between_emails => 5.minutes )
reset!
end
@@ -43,8 +47,12 @@ def validate_mailer_defined
module InstanceMethods
def reset_password!
config = sorcery_config
+ # hammering protection
+ return if self.send(config.reset_password_email_sent_at_attribute_name) && self.send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.ago
+
self.send(:"#{config.reset_password_code_attribute_name}=", generate_random_code)
self.send(:"#{config.reset_password_code_expires_at_attribute_name}=", Time.now.utc+config.reset_password_expiration_period) if config.reset_password_expiration_period
+ self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.utc)
self.class.transaction do
self.save!(:validate => false)
generic_send_email(:reset_password_email_method_name, :reset_password_mailer)
View
2  spec/rails3/app_root/db/migrate/password_reset/20101224223622_add_password_reset_to_users.rb
@@ -2,9 +2,11 @@ class AddPasswordResetToUsers < ActiveRecord::Migration
def self.up
add_column :users, :reset_password_code, :string, :default => nil
add_column :users, :reset_password_code_expires_at, :datetime, :default => nil
+ add_column :users, :reset_password_email_sent_at, :datetime, :default => nil
end
def self.down
+ remove_column :users, :reset_password_email_sent_at
remove_column :users, :reset_password_code_expires_at
remove_column :users, :reset_password_code
end
View
32 spec/rails3/user_password_reset_spec.rb
@@ -49,6 +49,16 @@
plugin_set_model_config_property(:reset_password_expiration_period, 16)
User.sorcery_config.reset_password_expiration_period.should equal(16)
end
+
+ it "should allow configuration option 'reset_password_email_sent_at_attribute_name'" do
+ plugin_set_model_config_property(:reset_password_email_sent_at_attribute_name, :blabla)
+ User.sorcery_config.reset_password_email_sent_at_attribute_name.should equal(:blabla)
+ end
+
+ it "should allow configuration option 'reset_password_time_between_emails'" do
+ plugin_set_model_config_property(:reset_password_time_between_emails, 16)
+ User.sorcery_config.reset_password_time_between_emails.should equal(16)
+ end
end
# ----------------- PLUGIN ACTIVATED -----------------------
@@ -71,6 +81,7 @@
it "the reset_password_code should be random" do
create_new_user
+ plugin_set_model_config_property(:reset_password_time_between_emails, 0)
@user.reset_password!
old_password_code = @user.reset_password_code
@user.reset_password!
@@ -121,6 +132,27 @@
@user.reset_password_code_valid?("asdadagfdgdf").should == false
end
+ it "should not send an email if time between emails has not passed since last email" do
+ create_new_user
+ plugin_set_model_config_property(:reset_password_time_between_emails, 10000)
+ old_size = ActionMailer::Base.deliveries.size
+ @user.reset_password!
+ ActionMailer::Base.deliveries.size.should == old_size + 1
+ @user.reset_password!
+ ActionMailer::Base.deliveries.size.should == old_size + 1
+ end
+
+ it "should send an email if time between emails has passed since last email" do
+ create_new_user
+ plugin_set_model_config_property(:reset_password_time_between_emails, 0.5)
+ old_size = ActionMailer::Base.deliveries.size
+ @user.reset_password!
+ ActionMailer::Base.deliveries.size.should == old_size + 1
+ sleep 0.5
+ @user.reset_password!
+ ActionMailer::Base.deliveries.size.should == old_size + 2
+ end
+
it "if mailer is nil on activation, throw exception!" do
expect{plugin_model_configure([:password_reset])}.to raise_error(ArgumentError)
end
Please sign in to comment.
Something went wrong with that request. Please try again.