sorcery 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. data/.travis.yml +3 -0
  2. data/Gemfile +9 -4
  3. data/Gemfile.lock +103 -62
  4. data/README.rdoc +33 -8
  5. data/Rakefile +6 -7
  6. data/VERSION +1 -1
  7. data/lib/generators/sorcery/install_generator.rb +15 -10
  8. data/lib/generators/sorcery/templates/initializer.rb +343 -125
  9. data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +2 -0
  10. data/lib/generators/sorcery/templates/migration/reset_password.rb +1 -1
  11. data/lib/sorcery/controller/submodules/activity_logging.rb +3 -3
  12. data/lib/sorcery/controller/submodules/brute_force_protection.rb +1 -2
  13. data/lib/sorcery/controller/submodules/external/protocols/oauth2.rb +19 -19
  14. data/lib/sorcery/controller/submodules/external/providers/facebook.rb +20 -5
  15. data/lib/sorcery/controller/submodules/external/providers/github.rb +15 -4
  16. data/lib/sorcery/controller/submodules/external/providers/google.rb +90 -0
  17. data/lib/sorcery/controller/submodules/external/providers/linkedin.rb +101 -0
  18. data/lib/sorcery/controller/submodules/external/providers/liveid.rb +91 -0
  19. data/lib/sorcery/controller/submodules/external/providers/twitter.rb +2 -1
  20. data/lib/sorcery/controller/submodules/external/providers/vkontakte.rb +94 -0
  21. data/lib/sorcery/controller/submodules/external.rb +89 -15
  22. data/lib/sorcery/controller/submodules/http_basic_auth.rb +1 -1
  23. data/lib/sorcery/controller/submodules/remember_me.rb +11 -2
  24. data/lib/sorcery/controller.rb +14 -6
  25. data/lib/sorcery/crypto_providers/bcrypt.rb +1 -0
  26. data/lib/sorcery/model/adapters/active_record.rb +21 -1
  27. data/lib/sorcery/model/adapters/mongo_mapper.rb +22 -15
  28. data/lib/sorcery/model/adapters/mongoid.rb +20 -3
  29. data/lib/sorcery/model/submodules/activity_logging.rb +4 -4
  30. data/lib/sorcery/model/submodules/brute_force_protection.rb +46 -15
  31. data/lib/sorcery/model/submodules/remember_me.rb +4 -6
  32. data/lib/sorcery/model/submodules/reset_password.rb +18 -12
  33. data/lib/sorcery/model/submodules/user_activation.rb +15 -8
  34. data/lib/sorcery/model.rb +57 -43
  35. data/lib/sorcery/railties/tasks.rake +2 -0
  36. data/lib/sorcery.rb +5 -1
  37. data/sorcery.gemspec +33 -21
  38. data/spec/Gemfile +1 -1
  39. data/spec/Gemfile.lock +22 -21
  40. data/spec/README.md +8 -8
  41. data/spec/rails3/Gemfile +3 -3
  42. data/spec/rails3/Gemfile.lock +55 -33
  43. data/spec/rails3/app/controllers/application_controller.rb +85 -2
  44. data/spec/rails3/app/mailers/sorcery_mailer.rb +7 -0
  45. data/spec/rails3/app/views/sorcery_mailer/send_unlock_token_email.text.erb +1 -0
  46. data/spec/rails3/config/environments/in_memory.rb +35 -0
  47. data/spec/rails3/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +2 -0
  48. data/spec/rails3/spec/controller_activity_logging_spec.rb +10 -0
  49. data/spec/rails3/spec/controller_brute_force_protection_spec.rb +23 -1
  50. data/spec/rails3/spec/controller_oauth2_spec.rb +260 -22
  51. data/spec/rails3/spec/controller_oauth_spec.rb +111 -6
  52. data/spec/rails3/spec/controller_spec.rb +37 -2
  53. data/spec/rails3/spec/integration_spec.rb +15 -15
  54. data/spec/rails3/spec/spec_helper.rb +1 -1
  55. data/spec/rails3_mongo_mapper/Gemfile +2 -1
  56. data/spec/rails3_mongo_mapper/Gemfile.lock +40 -42
  57. data/spec/rails3_mongo_mapper/app/controllers/application_controller.rb +14 -0
  58. data/spec/rails3_mongo_mapper/spec/controller_spec.rb +41 -1
  59. data/spec/rails3_mongoid/Gemfile +1 -0
  60. data/spec/rails3_mongoid/Gemfile.lock +30 -27
  61. data/spec/rails3_mongoid/app/controllers/application_controller.rb +19 -0
  62. data/spec/rails3_mongoid/config/mongoid.yml +1 -1
  63. data/spec/rails3_mongoid/spec/controller_activity_logging_spec.rb +18 -11
  64. data/spec/rails3_mongoid/spec/controller_spec.rb +41 -1
  65. data/spec/shared_examples/controller_oauth2_shared_examples.rb +20 -1
  66. data/spec/shared_examples/controller_oauth_shared_examples.rb +18 -0
  67. data/spec/shared_examples/user_activation_shared_examples.rb +71 -41
  68. data/spec/shared_examples/user_reset_password_shared_examples.rb +80 -27
  69. data/spec/sorcery_crypto_providers_spec.rb +14 -1
  70. metadata +74 -46
@@ -8,6 +8,15 @@ module Sorcery
8
8
  module RememberMe
9
9
  def self.included(base)
10
10
  base.send(:include, InstanceMethods)
11
+ Config.module_eval do
12
+ class << self
13
+ attr_accessor :remember_me_httponly
14
+ def merge_remember_me_defaults!
15
+ @defaults.merge!(:@remember_me_httponly => true)
16
+ end
17
+ end
18
+ merge_remember_me_defaults!
19
+ end
11
20
  Config.login_sources << :login_from_cookie
12
21
  Config.after_login << :remember_me_if_asked_to
13
22
  Config.after_logout << :forget_me!
@@ -60,7 +69,7 @@ module Sorcery
60
69
  cookies.signed[:remember_me_token] = {
61
70
  :value => user.send(user.sorcery_config.remember_me_token_attribute_name),
62
71
  :expires => user.send(user.sorcery_config.remember_me_token_expires_at_attribute_name),
63
- :httponly => true,
72
+ :httponly => Config.remember_me_httponly,
64
73
  :domain => Config.cookie_domain
65
74
  }
66
75
  end
@@ -69,4 +78,4 @@ module Sorcery
69
78
  end
70
79
  end
71
80
  end
72
- end
81
+ end
@@ -21,19 +21,22 @@ module Sorcery
21
21
  # If all attempts to auto-login fail, the failure callback will be called.
22
22
  def require_login
23
23
  if !logged_in?
24
- session[:return_to_url] = request.url if Config.save_return_to_url
24
+ session[:return_to_url] = request.url if Config.save_return_to_url && request.get?
25
25
  self.send(Config.not_authenticated_action)
26
26
  end
27
27
  end
28
28
 
29
29
  # Takes credentials and returns a user on successful authentication.
30
30
  # Runs hooks after login or failed login.
31
- def login(*credentials)
31
+ def login(*credentials)
32
+ @current_user = nil
32
33
  user = user_class.authenticate(*credentials)
33
34
  if user
34
- return_to_url = session[:return_to_url]
35
+ old_session = session.dup
35
36
  reset_session # protect from session fixation attacks
36
- session[:return_to_url] = return_to_url
37
+ old_session.to_hash.each_pair do |k,v|
38
+ session[k.to_sym] = v
39
+ end
37
40
  auto_login(user)
38
41
  after_login!(user, credentials)
39
42
  current_user
@@ -71,6 +74,7 @@ module Sorcery
71
74
  # and we want to return him back to the page he originally wanted.
72
75
  def redirect_back_or_to(url, flash_hash = {})
73
76
  redirect_to(session[:return_to_url] || url, :flash => flash_hash)
77
+ session[:return_to_url] = nil
74
78
  end
75
79
 
76
80
  # The default action for denying non-authenticated users.
@@ -108,10 +112,14 @@ module Sorcery
108
112
  end
109
113
 
110
114
  def login_from_session
111
- @current_user = (user_class.find_by_id(session[:user_id]) if session[:user_id]) || false
115
+ @current_user = (user_class.find(session[:user_id]) if session[:user_id]) || false
116
+ rescue => exception
117
+ return false if defined?(Mongoid) and exception.is_a?(Mongoid::Errors::DocumentNotFound)
118
+ return false if defined?(ActiveRecord) and exception.is_a?(ActiveRecord::RecordNotFound)
119
+ raise exception
112
120
  end
113
121
 
114
- def after_login!(user, credentials)
122
+ def after_login!(user, credentials = [])
115
123
  Config.after_login.each {|c| self.send(c, user, credentials)}
116
124
  end
117
125
 
@@ -48,6 +48,7 @@ module Sorcery
48
48
  @cost ||= 10
49
49
  end
50
50
  attr_writer :cost
51
+ alias :stretches :cost
51
52
  alias :stretches= :cost=
52
53
 
53
54
  # Creates a BCrypt hash for the password passed.
@@ -4,11 +4,31 @@ module Sorcery
4
4
  module ActiveRecord
5
5
  def self.included(klass)
6
6
  klass.extend ClassMethods
7
+ klass.send(:include, InstanceMethods)
7
8
  end
8
9
 
10
+ module InstanceMethods
11
+ def update_many_attributes(attrs)
12
+ attrs.each do |name, value|
13
+ self.send(:"#{name}=", value)
14
+ end
15
+ primary_key = self.class.primary_key
16
+ self.class.where(:"#{primary_key}" => self.send(:"#{primary_key}")).update_all(attrs)
17
+ end
18
+
19
+ def update_single_attribute(name, value)
20
+ update_many_attributes(name => value)
21
+ end
22
+ end
23
+
9
24
  module ClassMethods
25
+ def column_name(attribute)
26
+ return "LOWER(#{attribute})" if (@sorcery_config.downcase_username_before_authenticating)
27
+ return "#{attribute}"
28
+ end
29
+
10
30
  def find_by_credentials(credentials)
11
- sql = @sorcery_config.username_attribute_names.map{|attribute| "#{attribute} = :login"}
31
+ sql = @sorcery_config.username_attribute_names.map{|attribute| column_name(attribute) + " = :login"}
12
32
  where(sql.join(' OR '), :login => credentials[0]).first
13
33
  end
14
34
 
@@ -3,42 +3,49 @@ module Sorcery
3
3
  module Adapters
4
4
  module MongoMapper
5
5
  extend ActiveSupport::Concern
6
-
6
+
7
7
  included do
8
8
  include Sorcery::Model
9
9
  end
10
-
11
- module InstanceMethods
12
- def increment(attr)
13
- self.inc(attr,1)
14
- end
15
-
16
- def save!(options = {})
17
- save(options)
18
- end
10
+
11
+ def increment(attr)
12
+ self.class.increment(id, attr => 1)
13
+ end
14
+
15
+ def save!(options = {})
16
+ save(options)
17
+ end
18
+
19
+ def update_many_attributes(attrs)
20
+ update_attributes(attrs)
19
21
  end
20
22
 
21
23
  module ClassMethods
24
+ def credential_regex(credential)
25
+ return { :$regex => /^#{credential}$/i } if (@sorcery_config.downcase_username_before_authenticating)
26
+ return credential
27
+ end
28
+
22
29
  def find_by_credentials(credentials)
23
30
  @sorcery_config.username_attribute_names.each do |attribute|
24
- @user = where(attribute => credentials[0]).first
31
+ @user = where(attribute => credential_regex(credentials[0])).first
25
32
  break if @user
26
33
  end
27
34
  @user
28
35
  end
29
-
36
+
30
37
  def find_by_id(id)
31
38
  find(id)
32
39
  end
33
-
40
+
34
41
  def find_by_activation_token(token)
35
42
  where(sorcery_config.activation_token_attribute_name => token).first
36
43
  end
37
-
44
+
38
45
  def transaction(&blk)
39
46
  tap(&blk)
40
47
  end
41
-
48
+
42
49
  def find_by_sorcery_token(token_attr_name, token)
43
50
  where(token_attr_name => token).first
44
51
  end
@@ -11,12 +11,29 @@ module Sorcery
11
11
  def increment(attr)
12
12
  self.inc(attr,1)
13
13
  end
14
+
15
+ def update_many_attributes(attrs)
16
+ attrs.each do |name, value|
17
+ attrs[name] = value.utc if value.is_a?(ActiveSupport::TimeWithZone)
18
+ self.send(:"#{name}=", value)
19
+ end
20
+ self.class.where(:_id => self.id).update_all(attrs)
21
+ end
22
+
23
+ def update_single_attribute(name, value)
24
+ update_many_attributes(name => value)
25
+ end
14
26
  end
15
27
 
16
28
  module ClassMethods
29
+ def credential_regex(credential)
30
+ return { :$regex => /^#{credential}$/i } if (@sorcery_config.downcase_username_before_authenticating)
31
+ return credential
32
+ end
33
+
17
34
  def find_by_credentials(credentials)
18
35
  @sorcery_config.username_attribute_names.each do |attribute|
19
- @user = where(attribute => credentials[0]).first
36
+ @user = where(attribute => credential_regex(credentials[0])).first
20
37
  break if @user
21
38
  end
22
39
  @user
@@ -61,8 +78,8 @@ module Sorcery
61
78
  def get_current_users
62
79
  config = sorcery_config
63
80
  where(config.last_activity_at_attribute_name.ne => nil) \
64
- .any_of({config.last_logout_at_attribute_name => nil},{config.last_activity_at_attribute_name.gt => config.last_logout_at_attribute_name}) \
65
- .and(config.last_activity_at_attribute_name.gt => config.activity_timeout.seconds.ago.utc.to_s(:db)).order_by([:_id,:asc])
81
+ .where("this.#{config.last_logout_at_attribute_name} == null || this.#{config.last_activity_at_attribute_name} > this.#{config.last_logout_at_attribute_name}") \
82
+ .where(config.last_activity_at_attribute_name.gt => config.activity_timeout.seconds.ago.utc).order_by([:_id,:asc])
66
83
  end
67
84
  end
68
85
  end
@@ -39,12 +39,12 @@ module Sorcery
39
39
  protected
40
40
 
41
41
  def define_activity_logging_mongoid_fields
42
- field sorcery_config.last_login_at_attribute_name, :type => DateTime
43
- field sorcery_config.last_logout_at_attribute_name, :type => DateTime
44
- field sorcery_config.last_activity_at_attribute_name, :type => DateTime
42
+ field sorcery_config.last_login_at_attribute_name, :type => Time
43
+ field sorcery_config.last_logout_at_attribute_name, :type => Time
44
+ field sorcery_config.last_activity_at_attribute_name, :type => Time
45
45
  end
46
46
  end
47
47
  end
48
48
  end
49
49
  end
50
- end
50
+ end
@@ -12,15 +12,25 @@ module Sorcery
12
12
  :lock_expires_at_attribute_name, # this field indicates whether user
13
13
  # is banned and when it will be active again.
14
14
  :consecutive_login_retries_amount_limit, # how many failed logins allowed.
15
- :login_lock_time_period # how long the user should be banned.
15
+ :login_lock_time_period, # how long the user should be banned.
16
16
  # in seconds. 0 for permanent.
17
+
18
+ :unlock_token_attribute_name, # Unlock token attribute name
19
+ :unlock_token_email_method_name, # Mailer method name
20
+ :unlock_token_mailer_disabled, # When true, dont send unlock token via email
21
+ :unlock_token_mailer # Mailer class
17
22
  end
18
23
 
19
24
  base.sorcery_config.instance_eval do
20
25
  @defaults.merge!(:@failed_logins_count_attribute_name => :failed_logins_count,
21
26
  :@lock_expires_at_attribute_name => :lock_expires_at,
22
27
  :@consecutive_login_retries_amount_limit => 50,
23
- :@login_lock_time_period => 60 * 60)
28
+ :@login_lock_time_period => 60 * 60,
29
+
30
+ :@unlock_token_attribute_name => :unlock_token,
31
+ :@unlock_token_email_method_name => :send_unlock_token_email,
32
+ :@unlock_token_mailer_disabled => false,
33
+ :@unlock_token_mailer => nil)
24
34
  reset!
25
35
  end
26
36
 
@@ -34,16 +44,24 @@ module Sorcery
34
44
  end
35
45
 
36
46
  module ClassMethods
47
+ def load_from_unlock_token(token)
48
+ return nil if token.blank?
49
+ user = find_by_sorcery_token(sorcery_config.unlock_token_attribute_name,token)
50
+ user
51
+ end
52
+
37
53
  protected
38
54
 
39
55
  def define_brute_force_protection_mongoid_fields
40
- field sorcery_config.failed_logins_count_attribute_name, :type => Integer
41
- field sorcery_config.lock_expires_at_attribute_name, :type => DateTime
56
+ field sorcery_config.failed_logins_count_attribute_name, :type => Integer, :default => 0
57
+ field sorcery_config.lock_expires_at_attribute_name, :type => Time
58
+ field sorcery_config.unlock_token_attribute_name, :type => String
42
59
  end
43
60
 
44
61
  def define_brute_force_protection_mongo_mapper_fields
45
- key sorcery_config.failed_logins_count_attribute_name, Integer
46
- key sorcery_config.lock_expires_at_attribute_name, DateTime
62
+ key sorcery_config.failed_logins_count_attribute_name, Integer, :default => 0
63
+ key sorcery_config.lock_expires_at_attribute_name, Time
64
+ key sorcery_config.unlock_token_attribute_name, String
47
65
  end
48
66
  end
49
67
 
@@ -54,29 +72,42 @@ module Sorcery
54
72
  config = sorcery_config
55
73
  return if !unlocked?
56
74
  self.increment(config.failed_logins_count_attribute_name)
57
- save!(:validate => false)
75
+ self.update_many_attributes(config.failed_logins_count_attribute_name => self.send(config.failed_logins_count_attribute_name))
58
76
  self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_limit
59
77
  end
60
78
 
79
+ # /!\
80
+ # Moved out of protected for use like activate! in controller
81
+ # /!\
82
+ def unlock!
83
+ config = sorcery_config
84
+ attributes = {config.lock_expires_at_attribute_name => nil,
85
+ config.failed_logins_count_attribute_name => 0}
86
+ attributes[config.unlock_token_attribute_name] = nil unless config.unlock_token_mailer_disabled or config.unlock_token_mailer.nil?
87
+ self.update_many_attributes(attributes)
88
+ end
89
+
61
90
  protected
62
91
 
63
92
  def lock!
64
93
  config = sorcery_config
65
- self.send(:"#{config.lock_expires_at_attribute_name}=", Time.now.in_time_zone + config.login_lock_time_period)
66
- self.save!(validate: false)
67
- end
94
+ attributes = {config.lock_expires_at_attribute_name => Time.now.in_time_zone + config.login_lock_time_period}
68
95
 
69
- def unlock!
70
- config = sorcery_config
71
- self.send(:"#{config.lock_expires_at_attribute_name}=", nil)
72
- self.send(:"#{config.failed_logins_count_attribute_name}=", 0)
73
- self.save!(validate: false)
96
+ unless config.unlock_token_mailer_disabled || config.unlock_token_mailer.nil?
97
+ attributes[config.unlock_token_attribute_name] = TemporaryToken.generate_random_token
98
+ send_unlock_token_email!
99
+ end
100
+ self.update_many_attributes(attributes)
74
101
  end
75
102
 
76
103
  def unlocked?
77
104
  config = sorcery_config
78
105
  self.send(config.lock_expires_at_attribute_name).nil?
79
106
  end
107
+
108
+ def send_unlock_token_email!
109
+ generic_send_email(:unlock_token_email_method_name, :unlock_token_mailer) unless sorcery_config.unlock_token_email_method_name.nil? or sorcery_config.unlock_token_mailer_disabled == true
110
+ end
80
111
 
81
112
  # Prevents a locked user from logging in, and unlocks users that expired their lock time.
82
113
  # Runs as a hook before authenticate.
@@ -49,17 +49,15 @@ module Sorcery
49
49
  # You shouldn't really use this one yourself - it's called by the controller's 'remember_me!' method.
50
50
  def remember_me!
51
51
  config = sorcery_config
52
- self.send(:"#{config.remember_me_token_attribute_name}=", TemporaryToken.generate_random_token)
53
- self.send(:"#{config.remember_me_token_expires_at_attribute_name}=", Time.now.in_time_zone + config.remember_me_for)
54
- self.save!(:validate => false)
52
+ self.update_many_attributes(config.remember_me_token_attribute_name => TemporaryToken.generate_random_token,
53
+ config.remember_me_token_expires_at_attribute_name => Time.now.in_time_zone + config.remember_me_for)
55
54
  end
56
55
 
57
56
  # You shouldn't really use this one yourself - it's called by the controller's 'forget_me!' method.
58
57
  def forget_me!
59
58
  config = sorcery_config
60
- self.send(:"#{config.remember_me_token_attribute_name}=", nil)
61
- self.send(:"#{config.remember_me_token_expires_at_attribute_name}=", nil)
62
- self.save!(:validate => false)
59
+ self.update_many_attributes(config.remember_me_token_attribute_name => nil,
60
+ config.remember_me_token_expires_at_attribute_name => nil)
63
61
  end
64
62
  end
65
63
  end
@@ -18,6 +18,11 @@ module Sorcery
18
18
  # protection.
19
19
 
20
20
  :reset_password_mailer, # mailer class. Needed.
21
+
22
+ :reset_password_mailer_disabled, # when true sorcery will not automatically
23
+ # email password reset details and allow you to
24
+ # manually handle how and when email is sent
25
+
21
26
  :reset_password_email_method_name, # reset password email method on your
22
27
  # mailer class.
23
28
 
@@ -34,6 +39,7 @@ module Sorcery
34
39
  :@reset_password_token_expires_at_attribute_name => :reset_password_token_expires_at,
35
40
  :@reset_password_email_sent_at_attribute_name => :reset_password_email_sent_at,
36
41
  :@reset_password_mailer => nil,
42
+ :@reset_password_mailer_disabled => false,
37
43
  :@reset_password_email_method_name => :reset_password_email,
38
44
  :@reset_password_expiration_period => nil,
39
45
  :@reset_password_time_between_emails => 5 * 60 )
@@ -64,21 +70,21 @@ module Sorcery
64
70
 
65
71
  protected
66
72
 
67
- # This submodule requires the developer to define his own mailer class to be used by it.
73
+ # This submodule requires the developer to define his own mailer class to be used by it
74
+ # when reset_password_mailer_disabled is false
68
75
  def validate_mailer_defined
69
76
  msg = "To use reset_password submodule, you must define a mailer (config.reset_password_mailer = YourMailerClass)."
70
- raise ArgumentError, msg if @sorcery_config.reset_password_mailer == nil
77
+ raise ArgumentError, msg if @sorcery_config.reset_password_mailer == nil and @sorcery_config.reset_password_mailer_disabled == false
71
78
  end
72
79
 
73
80
  def define_reset_password_mongoid_fields
74
81
  field sorcery_config.reset_password_token_attribute_name, :type => String
75
- field sorcery_config.reset_password_token_expires_at_attribute_name, :type => DateTime
76
- field sorcery_config.reset_password_email_sent_at_attribute_name, :type => DateTime
82
+ field sorcery_config.reset_password_token_expires_at_attribute_name, :type => Time
83
+ field sorcery_config.reset_password_email_sent_at_attribute_name, :type => Time
77
84
  end
78
85
 
79
86
  def define_reset_password_mongo_mapper_fields
80
87
  key sorcery_config.reset_password_token_attribute_name, String
81
- # no DateTime in MM
82
88
  key sorcery_config.reset_password_token_expires_at_attribute_name, Time
83
89
  key sorcery_config.reset_password_email_sent_at_attribute_name, Time
84
90
  end
@@ -89,13 +95,13 @@ module Sorcery
89
95
  def deliver_reset_password_instructions!
90
96
  config = sorcery_config
91
97
  # hammering protection
92
- return if config.reset_password_time_between_emails && 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.utc
93
- self.send(:"#{config.reset_password_token_attribute_name}=", TemporaryToken.generate_random_token)
94
- self.send(:"#{config.reset_password_token_expires_at_attribute_name}=", Time.now.in_time_zone + config.reset_password_expiration_period) if config.reset_password_expiration_period
95
- self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.in_time_zone)
98
+ return false if config.reset_password_time_between_emails && 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.utc
99
+ attributes = {config.reset_password_token_attribute_name => TemporaryToken.generate_random_token,
100
+ config.reset_password_email_sent_at_attribute_name => Time.now.in_time_zone}
101
+ attributes[config.reset_password_token_expires_at_attribute_name] = Time.now.in_time_zone + config.reset_password_expiration_period if config.reset_password_expiration_period
96
102
  self.class.transaction do
97
- self.save!(:validate => false)
98
- generic_send_email(:reset_password_email_method_name, :reset_password_mailer)
103
+ self.update_many_attributes(attributes)
104
+ generic_send_email(:reset_password_email_method_name, :reset_password_mailer) unless config.reset_password_mailer_disabled
99
105
  end
100
106
  end
101
107
 
@@ -119,4 +125,4 @@ module Sorcery
119
125
  end
120
126
  end
121
127
  end
122
- end
128
+ end
@@ -20,7 +20,13 @@ module Sorcery
20
20
  :activation_token_expiration_period, # how many seconds before the activation code
21
21
  # expires. nil for never expires.
22
22
 
23
- :user_activation_mailer, # your mailer class. Required.
23
+ :user_activation_mailer, # your mailer class. Required when
24
+ # activation_mailer_disabled == false.
25
+
26
+ :activation_mailer_disabled, # when true sorcery will not automatically
27
+ # email activation details and allow you to
28
+ # manually handle how and when email is sent
29
+
24
30
  :activation_needed_email_method_name, # activation needed email method on your
25
31
  # mailer class.
26
32
 
@@ -37,6 +43,7 @@ module Sorcery
37
43
  :@activation_token_expires_at_attribute_name => :activation_token_expires_at,
38
44
  :@activation_token_expiration_period => nil,
39
45
  :@user_activation_mailer => nil,
46
+ :@activation_mailer_disabled => false,
40
47
  :@activation_needed_email_method_name => :activation_needed_email,
41
48
  :@activation_success_email_method_name => :activation_success_email,
42
49
  :@prevent_non_active_users_to_login => true)
@@ -47,7 +54,7 @@ module Sorcery
47
54
  # don't setup activation if no password supplied - this user is created automatically
48
55
  before_create :setup_activation, :if => Proc.new { |user| user.send(sorcery_config.password_attribute_name).present? }
49
56
  # don't send activation needed email if no crypted password created - this user is external (OAuth etc.)
50
- after_create :send_activation_needed_email!, :if => Proc.new { |user| !user.external?}
57
+ after_create :send_activation_needed_email!, :if => Proc.new { |user| !user.external? }
51
58
  end
52
59
 
53
60
  base.sorcery_config.after_config << :validate_mailer_defined
@@ -74,17 +81,18 @@ module Sorcery
74
81
 
75
82
  protected
76
83
 
77
- # This submodule requires the developer to define his own mailer class to be used by it.
84
+ # This submodule requires the developer to define his own mailer class to be used by it
85
+ # when activation_mailer_disabled is false
78
86
  def validate_mailer_defined
79
87
  msg = "To use user_activation submodule, you must define a mailer (config.user_activation_mailer = YourMailerClass)."
80
- raise ArgumentError, msg if @sorcery_config.user_activation_mailer == nil
88
+ raise ArgumentError, msg if @sorcery_config.user_activation_mailer == nil and @sorcery_config.activation_mailer_disabled == false
81
89
  end
82
90
 
83
91
  def define_user_activation_mongoid_fields
84
92
  self.class_eval do
85
93
  field sorcery_config.activation_state_attribute_name, :type => String
86
94
  field sorcery_config.activation_token_attribute_name, :type => String
87
- field sorcery_config.activation_token_expires_at_attribute_name, :type => DateTime
95
+ field sorcery_config.activation_token_expires_at_attribute_name, :type => Time
88
96
  end
89
97
  end
90
98
 
@@ -92,7 +100,6 @@ module Sorcery
92
100
  self.class_eval do
93
101
  key sorcery_config.activation_state_attribute_name, String
94
102
  key sorcery_config.activation_token_attribute_name, String
95
- # no DateTime in MM
96
103
  key sorcery_config.activation_token_expires_at_attribute_name, Time
97
104
  end
98
105
  end
@@ -120,11 +127,11 @@ module Sorcery
120
127
 
121
128
  # called automatically after user initial creation.
122
129
  def send_activation_needed_email!
123
- generic_send_email(:activation_needed_email_method_name, :user_activation_mailer) unless sorcery_config.activation_needed_email_method_name.nil?
130
+ generic_send_email(:activation_needed_email_method_name, :user_activation_mailer) unless sorcery_config.activation_needed_email_method_name.nil? or sorcery_config.activation_mailer_disabled == true
124
131
  end
125
132
 
126
133
  def send_activation_success_email!
127
- generic_send_email(:activation_success_email_method_name, :user_activation_mailer) unless sorcery_config.activation_success_email_method_name.nil?
134
+ generic_send_email(:activation_success_email_method_name, :user_activation_mailer) unless sorcery_config.activation_success_email_method_name.nil? or sorcery_config.activation_mailer_disabled == true
128
135
  end
129
136
 
130
137
  def prevent_non_active_login