Skip to content
This repository
Browse code

adding password_reset hammering protection

  • Loading branch information...
commit 61f28e766dc397af298de2f4f67b0b1c04dd4feb 1 parent a01719c
Noam Ben Ari authored
3  README.rdoc
Source Rendered
@@ -25,6 +25,7 @@ Password Reset (see lib/sorcery/model/submodules/password_reset.rb):
25 25 * Reset password with email verification.
26 26 * configurable mailer, method name, and attribute name.
27 27 * configurable expiration.
  28 +* configurable time between emails (hammering protection).
28 29
29 30 Remember Me (see lib/sorcery/model/submodules/remember_me.rb):
30 31 * Remember me with configurable expiration.
@@ -55,9 +56,9 @@ Other:
55 56 == Next Planned Features:
56 57
57 58 I've got many plans which include:
58   -* Hammering reset password protection
59 59 * Configurable Auto login on registration/activation
60 60 * Other reset password strategies (security questions?)
  61 +* Other brute force protection strategies (captcha, lock account)
61 62 * Sinatra support
62 63 * Mongoid support
63 64 * OmniAuth integration
12 lib/sorcery/model/submodules/password_reset.rb
@@ -7,18 +7,22 @@ def self.included(base)
7 7 base.sorcery_config.class_eval do
8 8 attr_accessor :reset_password_code_attribute_name, # reset password code attribute name.
9 9 :reset_password_code_expires_at_attribute_name, # expires at attribute name.
  10 + :reset_password_email_sent_at_attribute_name, # when was email sent, used for hammering protection.
10 11 :reset_password_mailer, # mailer class. Needed.
11 12 :reset_password_email_method_name, # reset password email method on your mailer class.
12   - :reset_password_expiration_period # how many seconds before the reset request expires. nil for never expires.
  13 + :reset_password_expiration_period, # how many seconds before the reset request expires. nil for never expires.
  14 + :reset_password_time_between_emails # hammering protection, how long to wait before allowing another email to be sent.
13 15
14 16 end
15 17
16 18 base.sorcery_config.instance_eval do
17 19 @defaults.merge!(:@reset_password_code_attribute_name => :reset_password_code,
18 20 :@reset_password_code_expires_at_attribute_name => :reset_password_code_expires_at,
  21 + :@reset_password_email_sent_at_attribute_name => :reset_password_email_sent_at,
19 22 :@reset_password_mailer => nil,
20 23 :@reset_password_email_method_name => :reset_password_email,
21   - :@reset_password_expiration_period => nil )
  24 + :@reset_password_expiration_period => nil,
  25 + :@reset_password_time_between_emails => 5.minutes )
22 26
23 27 reset!
24 28 end
@@ -43,8 +47,12 @@ def validate_mailer_defined
43 47 module InstanceMethods
44 48 def reset_password!
45 49 config = sorcery_config
  50 + # hammering protection
  51 + 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
  52 +
46 53 self.send(:"#{config.reset_password_code_attribute_name}=", generate_random_code)
47 54 self.send(:"#{config.reset_password_code_expires_at_attribute_name}=", Time.now.utc+config.reset_password_expiration_period) if config.reset_password_expiration_period
  55 + self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.utc)
48 56 self.class.transaction do
49 57 self.save!(:validate => false)
50 58 generic_send_email(:reset_password_email_method_name, :reset_password_mailer)
2  spec/rails3/app_root/db/migrate/password_reset/20101224223622_add_password_reset_to_users.rb
@@ -2,9 +2,11 @@ class AddPasswordResetToUsers < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :users, :reset_password_code, :string, :default => nil
4 4 add_column :users, :reset_password_code_expires_at, :datetime, :default => nil
  5 + add_column :users, :reset_password_email_sent_at, :datetime, :default => nil
5 6 end
6 7
7 8 def self.down
  9 + remove_column :users, :reset_password_email_sent_at
8 10 remove_column :users, :reset_password_code_expires_at
9 11 remove_column :users, :reset_password_code
10 12 end
32 spec/rails3/user_password_reset_spec.rb
@@ -49,6 +49,16 @@
49 49 plugin_set_model_config_property(:reset_password_expiration_period, 16)
50 50 User.sorcery_config.reset_password_expiration_period.should equal(16)
51 51 end
  52 +
  53 + it "should allow configuration option 'reset_password_email_sent_at_attribute_name'" do
  54 + plugin_set_model_config_property(:reset_password_email_sent_at_attribute_name, :blabla)
  55 + User.sorcery_config.reset_password_email_sent_at_attribute_name.should equal(:blabla)
  56 + end
  57 +
  58 + it "should allow configuration option 'reset_password_time_between_emails'" do
  59 + plugin_set_model_config_property(:reset_password_time_between_emails, 16)
  60 + User.sorcery_config.reset_password_time_between_emails.should equal(16)
  61 + end
52 62 end
53 63
54 64 # ----------------- PLUGIN ACTIVATED -----------------------
@@ -71,6 +81,7 @@
71 81
72 82 it "the reset_password_code should be random" do
73 83 create_new_user
  84 + plugin_set_model_config_property(:reset_password_time_between_emails, 0)
74 85 @user.reset_password!
75 86 old_password_code = @user.reset_password_code
76 87 @user.reset_password!
@@ -121,6 +132,27 @@
121 132 @user.reset_password_code_valid?("asdadagfdgdf").should == false
122 133 end
123 134
  135 + it "should not send an email if time between emails has not passed since last email" do
  136 + create_new_user
  137 + plugin_set_model_config_property(:reset_password_time_between_emails, 10000)
  138 + old_size = ActionMailer::Base.deliveries.size
  139 + @user.reset_password!
  140 + ActionMailer::Base.deliveries.size.should == old_size + 1
  141 + @user.reset_password!
  142 + ActionMailer::Base.deliveries.size.should == old_size + 1
  143 + end
  144 +
  145 + it "should send an email if time between emails has passed since last email" do
  146 + create_new_user
  147 + plugin_set_model_config_property(:reset_password_time_between_emails, 0.5)
  148 + old_size = ActionMailer::Base.deliveries.size
  149 + @user.reset_password!
  150 + ActionMailer::Base.deliveries.size.should == old_size + 1
  151 + sleep 0.5
  152 + @user.reset_password!
  153 + ActionMailer::Base.deliveries.size.should == old_size + 2
  154 + end
  155 +
124 156 it "if mailer is nil on activation, throw exception!" do
125 157 expect{plugin_model_configure([:password_reset])}.to raise_error(ArgumentError)
126 158 end

0 comments on commit 61f28e7

Please sign in to comment.
Something went wrong with that request. Please try again.