mongoid-devise 1.0.1

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 (142) hide show
  1. data/CHANGELOG.rdoc +333 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +260 -0
  4. data/Rakefile +53 -0
  5. data/TODO +2 -0
  6. data/app/controllers/confirmations_controller.rb +33 -0
  7. data/app/controllers/passwords_controller.rb +42 -0
  8. data/app/controllers/registrations_controller.rb +55 -0
  9. data/app/controllers/sessions_controller.rb +45 -0
  10. data/app/controllers/unlocks_controller.rb +33 -0
  11. data/app/models/devise_mailer.rb +68 -0
  12. data/app/views/confirmations/new.html.erb +12 -0
  13. data/app/views/devise_mailer/confirmation_instructions.html.erb +5 -0
  14. data/app/views/devise_mailer/reset_password_instructions.html.erb +8 -0
  15. data/app/views/devise_mailer/unlock_instructions.html.erb +7 -0
  16. data/app/views/passwords/edit.html.erb +16 -0
  17. data/app/views/passwords/new.html.erb +12 -0
  18. data/app/views/registrations/edit.html.erb +25 -0
  19. data/app/views/registrations/new.html.erb +17 -0
  20. data/app/views/sessions/new.html.erb +17 -0
  21. data/app/views/shared/_devise_links.erb +19 -0
  22. data/app/views/unlocks/new.html.erb +12 -0
  23. data/generators/devise/USAGE +5 -0
  24. data/generators/devise/devise_generator.rb +15 -0
  25. data/generators/devise/lib/route_devise.rb +32 -0
  26. data/generators/devise/templates/migration.rb +23 -0
  27. data/generators/devise/templates/model.rb +9 -0
  28. data/generators/devise_install/USAGE +3 -0
  29. data/generators/devise_install/devise_install_generator.rb +15 -0
  30. data/generators/devise_install/templates/README +18 -0
  31. data/generators/devise_install/templates/devise.rb +102 -0
  32. data/generators/devise_views/USAGE +3 -0
  33. data/generators/devise_views/devise_views_generator.rb +21 -0
  34. data/init.rb +2 -0
  35. data/lib/devise.rb +253 -0
  36. data/lib/devise/controllers/helpers.rb +200 -0
  37. data/lib/devise/controllers/internal_helpers.rb +129 -0
  38. data/lib/devise/controllers/url_helpers.rb +41 -0
  39. data/lib/devise/encryptors/authlogic_sha512.rb +21 -0
  40. data/lib/devise/encryptors/base.rb +20 -0
  41. data/lib/devise/encryptors/bcrypt.rb +21 -0
  42. data/lib/devise/encryptors/clearance_sha1.rb +19 -0
  43. data/lib/devise/encryptors/restful_authentication_sha1.rb +22 -0
  44. data/lib/devise/encryptors/sha1.rb +27 -0
  45. data/lib/devise/encryptors/sha512.rb +27 -0
  46. data/lib/devise/failure_app.rb +65 -0
  47. data/lib/devise/hooks/activatable.rb +15 -0
  48. data/lib/devise/hooks/rememberable.rb +30 -0
  49. data/lib/devise/hooks/timeoutable.rb +18 -0
  50. data/lib/devise/hooks/trackable.rb +18 -0
  51. data/lib/devise/locales/en.yml +35 -0
  52. data/lib/devise/mapping.rb +131 -0
  53. data/lib/devise/models.rb +112 -0
  54. data/lib/devise/models/activatable.rb +16 -0
  55. data/lib/devise/models/authenticatable.rb +146 -0
  56. data/lib/devise/models/confirmable.rb +172 -0
  57. data/lib/devise/models/http_authenticatable.rb +21 -0
  58. data/lib/devise/models/lockable.rb +160 -0
  59. data/lib/devise/models/recoverable.rb +80 -0
  60. data/lib/devise/models/registerable.rb +8 -0
  61. data/lib/devise/models/rememberable.rb +94 -0
  62. data/lib/devise/models/timeoutable.rb +28 -0
  63. data/lib/devise/models/token_authenticatable.rb +89 -0
  64. data/lib/devise/models/trackable.rb +16 -0
  65. data/lib/devise/models/validatable.rb +48 -0
  66. data/lib/devise/orm/active_record.rb +41 -0
  67. data/lib/devise/orm/data_mapper.rb +83 -0
  68. data/lib/devise/orm/mongo_mapper.rb +51 -0
  69. data/lib/devise/orm/mongoid.rb +60 -0
  70. data/lib/devise/rails.rb +14 -0
  71. data/lib/devise/rails/routes.rb +125 -0
  72. data/lib/devise/rails/warden_compat.rb +25 -0
  73. data/lib/devise/schema.rb +65 -0
  74. data/lib/devise/strategies/authenticatable.rb +36 -0
  75. data/lib/devise/strategies/base.rb +16 -0
  76. data/lib/devise/strategies/http_authenticatable.rb +49 -0
  77. data/lib/devise/strategies/rememberable.rb +37 -0
  78. data/lib/devise/strategies/token_authenticatable.rb +37 -0
  79. data/lib/devise/test_helpers.rb +86 -0
  80. data/lib/devise/version.rb +3 -0
  81. data/test/controllers/helpers_test.rb +177 -0
  82. data/test/controllers/internal_helpers_test.rb +55 -0
  83. data/test/controllers/url_helpers_test.rb +47 -0
  84. data/test/devise_test.rb +69 -0
  85. data/test/encryptors_test.rb +31 -0
  86. data/test/failure_app_test.rb +44 -0
  87. data/test/integration/authenticatable_test.rb +271 -0
  88. data/test/integration/confirmable_test.rb +97 -0
  89. data/test/integration/http_authenticatable_test.rb +44 -0
  90. data/test/integration/lockable_test.rb +83 -0
  91. data/test/integration/recoverable_test.rb +141 -0
  92. data/test/integration/registerable_test.rb +130 -0
  93. data/test/integration/rememberable_test.rb +63 -0
  94. data/test/integration/timeoutable_test.rb +68 -0
  95. data/test/integration/token_authenticatable_test.rb +55 -0
  96. data/test/integration/trackable_test.rb +64 -0
  97. data/test/mailers/confirmation_instructions_test.rb +80 -0
  98. data/test/mailers/reset_password_instructions_test.rb +68 -0
  99. data/test/mailers/unlock_instructions_test.rb +62 -0
  100. data/test/mapping_test.rb +153 -0
  101. data/test/models/authenticatable_test.rb +180 -0
  102. data/test/models/confirmable_test.rb +228 -0
  103. data/test/models/lockable_test.rb +202 -0
  104. data/test/models/recoverable_test.rb +138 -0
  105. data/test/models/rememberable_test.rb +135 -0
  106. data/test/models/timeoutable_test.rb +28 -0
  107. data/test/models/token_authenticatable_test.rb +51 -0
  108. data/test/models/trackable_test.rb +5 -0
  109. data/test/models/validatable_test.rb +106 -0
  110. data/test/models_test.rb +56 -0
  111. data/test/orm/active_record.rb +31 -0
  112. data/test/orm/mongo_mapper.rb +20 -0
  113. data/test/orm/mongoid.rb +22 -0
  114. data/test/rails_app/app/active_record/admin.rb +7 -0
  115. data/test/rails_app/app/active_record/user.rb +7 -0
  116. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  117. data/test/rails_app/app/controllers/application_controller.rb +10 -0
  118. data/test/rails_app/app/controllers/home_controller.rb +4 -0
  119. data/test/rails_app/app/controllers/users_controller.rb +16 -0
  120. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  121. data/test/rails_app/app/mongo_mapper/admin.rb +9 -0
  122. data/test/rails_app/app/mongo_mapper/user.rb +8 -0
  123. data/test/rails_app/app/mongoid/admin.rb +9 -0
  124. data/test/rails_app/app/mongoid/user.rb +8 -0
  125. data/test/rails_app/config/boot.rb +110 -0
  126. data/test/rails_app/config/environment.rb +42 -0
  127. data/test/rails_app/config/environments/development.rb +17 -0
  128. data/test/rails_app/config/environments/production.rb +28 -0
  129. data/test/rails_app/config/environments/test.rb +28 -0
  130. data/test/rails_app/config/initializers/devise.rb +79 -0
  131. data/test/rails_app/config/initializers/inflections.rb +2 -0
  132. data/test/rails_app/config/initializers/new_rails_defaults.rb +24 -0
  133. data/test/rails_app/config/initializers/session_store.rb +15 -0
  134. data/test/rails_app/config/routes.rb +21 -0
  135. data/test/routes_test.rb +110 -0
  136. data/test/support/assertions_helper.rb +37 -0
  137. data/test/support/integration_tests_helper.rb +71 -0
  138. data/test/support/test_silencer.rb +5 -0
  139. data/test/support/tests_helper.rb +39 -0
  140. data/test/test_helper.rb +21 -0
  141. data/test/test_helpers_test.rb +57 -0
  142. metadata +216 -0
@@ -0,0 +1,16 @@
1
+ require 'devise/hooks/activatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # This module implements the default API required in activatable hook.
6
+ module Activatable
7
+ def active?
8
+ true
9
+ end
10
+
11
+ def inactive_message
12
+ :inactive
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,146 @@
1
+ require 'devise/strategies/authenticatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # Authenticable Module, responsible for encrypting password and validating
6
+ # authenticity of a user while signing in.
7
+ #
8
+ # Configuration:
9
+ #
10
+ # You can overwrite configuration values by setting in globally in Devise,
11
+ # using devise method or overwriting the respective instance method.
12
+ #
13
+ # pepper: encryption key used for creating encrypted password. Each time
14
+ # password changes, it's gonna be encrypted again, and this key
15
+ # is added to the password and salt to create a secure hash.
16
+ # Always use `rake secret' to generate a new key.
17
+ #
18
+ # stretches: defines how many times the password will be encrypted.
19
+ #
20
+ # encryptor: the encryptor going to be used. By default :sha1.
21
+ #
22
+ # authentication_keys: parameters used for authentication. By default [:email]
23
+ #
24
+ # Examples:
25
+ #
26
+ # User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
27
+ # User.find(1).valid_password?('password123') # returns true/false
28
+ #
29
+ module Authenticatable
30
+ def self.included(base)
31
+ base.class_eval do
32
+ extend ClassMethods
33
+
34
+ attr_reader :password, :current_password
35
+ attr_accessor :password_confirmation
36
+ end
37
+ end
38
+
39
+ # TODO Remove me in next release
40
+ def old_password
41
+ ActiveSupport::Deprecation.warn "old_password is deprecated, please use current_password instead", caller
42
+ @old_password
43
+ end
44
+
45
+ # Regenerates password salt and encrypted password each time password is set,
46
+ # and then trigger any "after_changed_password"-callbacks.
47
+ def password=(new_password)
48
+ @password = new_password
49
+
50
+ if @password.present?
51
+ self.password_salt = self.class.encryptor_class.salt
52
+ self.encrypted_password = password_digest(@password)
53
+ end
54
+ end
55
+
56
+ # Verifies whether an incoming_password (ie from sign in) is the user password.
57
+ def valid_password?(incoming_password)
58
+ password_digest(incoming_password) == self.encrypted_password
59
+ end
60
+
61
+ # Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
62
+ # is the user authentication token.
63
+ def valid_authentication_token?(incoming_auth_token)
64
+ incoming_auth_token == self.authentication_token
65
+ end
66
+
67
+ # Checks if a resource is valid upon authentication.
68
+ def valid_for_authentication?(attributes)
69
+ valid_password?(attributes[:password])
70
+ end
71
+
72
+ # Set password and password confirmation to nil
73
+ def clean_up_passwords
74
+ self.password = self.password_confirmation = nil
75
+ end
76
+
77
+ # Update record attributes when :current_password matches, otherwise returns
78
+ # error on :current_password. It also automatically rejects :password and
79
+ # :password_confirmation if they are blank.
80
+ def update_with_password(params={})
81
+ # TODO Remove me in next release
82
+ if params[:old_password].present?
83
+ params[:current_password] ||= params[:old_password]
84
+ ActiveSupport::Deprecation.warn "old_password is deprecated, please use current_password instead", caller
85
+ end
86
+
87
+ params.delete(:password) if params[:password].blank?
88
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
89
+ current_password = params.delete(:current_password)
90
+
91
+ result = if valid_password?(current_password)
92
+ update_attributes(params)
93
+ else
94
+ message = current_password.blank? ? :blank : :invalid
95
+ self.class.add_error_on(self, :current_password, message, false)
96
+ self.attributes = params
97
+ false
98
+ end
99
+
100
+ clean_up_passwords unless result
101
+ result
102
+ end
103
+
104
+ protected
105
+
106
+ # Digests the password using the configured encryptor.
107
+ def password_digest(password)
108
+ self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
109
+ end
110
+
111
+ module ClassMethods
112
+ Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
113
+
114
+ # Authenticate a user based on configured attribute keys. Returns the
115
+ # authenticated user if it's valid or nil.
116
+ def authenticate(attributes={})
117
+ return unless authentication_keys.all? { |k| attributes[k].present? }
118
+ conditions = attributes.slice(*authentication_keys)
119
+ resource = find_for_authentication(conditions)
120
+ resource if resource.try(:valid_for_authentication?, attributes)
121
+ end
122
+
123
+ # Returns the class for the configured encryptor.
124
+ def encryptor_class
125
+ @encryptor_class ||= ::Devise::Encryptors.const_get(encryptor.to_s.classify)
126
+ end
127
+
128
+ protected
129
+
130
+ # Find first record based on conditions given (ie by the sign in form).
131
+ # Overwrite to add customized conditions, create a join, or maybe use a
132
+ # namedscope to filter records while authenticating.
133
+ # Example:
134
+ #
135
+ # def self.find_for_authentication(conditions={})
136
+ # conditions[:active] = true
137
+ # find(:first, :conditions => conditions)
138
+ # end
139
+ #
140
+ def find_for_authentication(conditions)
141
+ find(:first, :conditions => conditions)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,172 @@
1
+ require 'devise/models/activatable'
2
+
3
+ module Devise
4
+ module Models
5
+
6
+ # Confirmable is responsible to verify if an account is already confirmed to
7
+ # sign in, and to send emails with confirmation instructions.
8
+ # Confirmation instructions are sent to the user email after creating a
9
+ # record, after updating it's email and also when manually requested by
10
+ # a new confirmation instruction request.
11
+ # Whenever the user update it's email, his account is automatically unconfirmed,
12
+ # it means it won't be able to sign in again without confirming the account
13
+ # again through the email that was sent.
14
+ #
15
+ # Configuration:
16
+ #
17
+ # confirm_within: the time you want the user will have to confirm it's account
18
+ # without blocking his access. When confirm_within is zero, the
19
+ # user won't be able to sign in without confirming. You can
20
+ # use this to let your user access some features of your
21
+ # application without confirming the account, but blocking it
22
+ # after a certain period (ie 7 days). By default confirm_within is
23
+ # zero, it means users always have to confirm to sign in.
24
+ #
25
+ # Examples:
26
+ #
27
+ # User.find(1).confirm! # returns true unless it's already confirmed
28
+ # User.find(1).confirmed? # true/false
29
+ # User.find(1).send_confirmation_instructions # manually send instructions
30
+ # User.find(1).resend_confirmation! # generates a new token and resent it
31
+ module Confirmable
32
+ include Devise::Models::Activatable
33
+
34
+ def self.included(base)
35
+ base.class_eval do
36
+ extend ClassMethods
37
+
38
+ before_create :generate_confirmation_token, :if => :confirmation_required?
39
+ after_create :send_confirmation_instructions, :if => :confirmation_required?
40
+ end
41
+ end
42
+
43
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
44
+ # is already confirmed, add en error to email field
45
+ def confirm!
46
+ unless_confirmed do
47
+ self.confirmation_token = nil
48
+ self.confirmed_at = Time.now
49
+ save(false)
50
+ end
51
+ end
52
+
53
+ # Verifies whether a user is confirmed or not
54
+ def confirmed?
55
+ !new_record? && !confirmed_at.nil?
56
+ end
57
+
58
+ # Send confirmation instructions by email
59
+ def send_confirmation_instructions
60
+ ::DeviseMailer.deliver_confirmation_instructions(self)
61
+ end
62
+
63
+ # Remove confirmation date and send confirmation instructions, to ensure
64
+ # after sending these instructions the user won't be able to sign in without
65
+ # confirming it's account
66
+ def resend_confirmation!
67
+ unless_confirmed do
68
+ generate_confirmation_token
69
+ save(false)
70
+ send_confirmation_instructions
71
+ end
72
+ end
73
+
74
+ # Overwrites active? from Devise::Models::Activatable for confirmation
75
+ # by verifying whether an user is active to sign in or not. If the user
76
+ # is already confirmed, it should never be blocked. Otherwise we need to
77
+ # calculate if the confirm time has not expired for this user.
78
+ def active?
79
+ super && (confirmed? || confirmation_period_valid?)
80
+ end
81
+
82
+ # The message to be shown if the account is inactive.
83
+ def inactive_message
84
+ if !confirmed?
85
+ :unconfirmed
86
+ else
87
+ super
88
+ end
89
+ end
90
+
91
+ # If you don't want confirmation to be sent on create, neither a code
92
+ # to be generated, call skip_confirmation!
93
+ def skip_confirmation!
94
+ self.confirmed_at = Time.now
95
+ @skip_confirmation = true
96
+ end
97
+
98
+ protected
99
+
100
+ # Callback to overwrite if confirmation is required or not.
101
+ def confirmation_required?
102
+ !@skip_confirmation
103
+ end
104
+
105
+ # Checks if the confirmation for the user is within the limit time.
106
+ # We do this by calculating if the difference between today and the
107
+ # confirmation sent date does not exceed the confirm in time configured.
108
+ # Confirm_in is a model configuration, must always be an integer value.
109
+ #
110
+ # Example:
111
+ #
112
+ # # confirm_within = 1.day and confirmation_sent_at = today
113
+ # confirmation_period_valid? # returns true
114
+ #
115
+ # # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
116
+ # confirmation_period_valid? # returns true
117
+ #
118
+ # # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
119
+ # confirmation_period_valid? # returns false
120
+ #
121
+ # # confirm_within = 0.days
122
+ # confirmation_period_valid? # will always return false
123
+ #
124
+ def confirmation_period_valid?
125
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.confirm_within.ago
126
+ end
127
+
128
+ # Checks whether the record is confirmed or not, yielding to the block
129
+ # if it's already confirmed, otherwise adds an error to email.
130
+ def unless_confirmed
131
+ unless confirmed?
132
+ yield
133
+ else
134
+ self.class.add_error_on(self, :email, :already_confirmed)
135
+ false
136
+ end
137
+ end
138
+
139
+ # Generates a new random token for confirmation, and stores the time
140
+ # this token is being generated
141
+ def generate_confirmation_token
142
+ self.confirmed_at = nil
143
+ self.confirmation_token = Devise.friendly_token
144
+ self.confirmation_sent_at = Time.now.utc
145
+ end
146
+
147
+ module ClassMethods
148
+ # Attempt to find a user by it's email. If a record is found, send new
149
+ # confirmation instructions to it. If not user is found, returns a new user
150
+ # with an email not found error.
151
+ # Options must contain the user email
152
+ def send_confirmation_instructions(attributes={})
153
+ confirmable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
154
+ confirmable.resend_confirmation! unless confirmable.new_record?
155
+ confirmable
156
+ end
157
+
158
+ # Find a user by it's confirmation token and try to confirm it.
159
+ # If no user is found, returns a new user with an error.
160
+ # If the user is already confirmed, create an error for the user
161
+ # Options must have the confirmation_token
162
+ def confirm!(attributes={})
163
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, attributes[:confirmation_token])
164
+ confirmable.confirm! unless confirmable.new_record?
165
+ confirmable
166
+ end
167
+
168
+ Devise::Models.config(self, :confirm_within)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,21 @@
1
+ require 'devise/strategies/http_authenticatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # Adds HttpAuthenticatable behavior to your model. It expects that your
6
+ # model class responds to authenticate and authentication_keys methods
7
+ # (which for example are defined in authenticatable).
8
+ module HttpAuthenticatable
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ module ClassMethods
14
+ # Authenticate an user using http.
15
+ def authenticate_with_http(username, password)
16
+ authenticate(authentication_keys.first => username, :password => password)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,160 @@
1
+ require 'devise/models/activatable'
2
+
3
+ module Devise
4
+ module Models
5
+
6
+ # Handles blocking a user access after a certain number of attempts.
7
+ # Lockable accepts two different strategies to unlock a user after it's
8
+ # blocked: email and time. The former will send an email to the user when
9
+ # the lock happens, containing a link to unlock it's account. The second
10
+ # will unlock the user automatically after some configured time (ie 2.hours).
11
+ # It's also possible to setup lockable to use both email and time strategies.
12
+ #
13
+ # Configuration:
14
+ #
15
+ # maximum_attempts: how many attempts should be accepted before blocking the user.
16
+ # unlock_strategy: unlock the user account by :time, :email or :both.
17
+ # unlock_in: the time you want to lock the user after to lock happens. Only
18
+ # available when unlock_strategy is :time or :both.
19
+ #
20
+ module Lockable
21
+ include Devise::Models::Activatable
22
+
23
+ def self.included(base)
24
+ base.class_eval do
25
+ extend ClassMethods
26
+ end
27
+ end
28
+
29
+ # Lock an user setting it's locked_at to actual time.
30
+ def lock
31
+ self.locked_at = Time.now
32
+ if unlock_strategy_enabled?(:email)
33
+ generate_unlock_token
34
+ send_unlock_instructions
35
+ end
36
+ end
37
+
38
+ # Lock an user also saving the record.
39
+ def lock!
40
+ lock
41
+ save(false)
42
+ end
43
+
44
+ # Unlock an user by cleaning locket_at and failed_attempts.
45
+ def unlock!
46
+ if_locked do
47
+ self.locked_at = nil
48
+ self.failed_attempts = 0
49
+ self.unlock_token = nil
50
+ save(false)
51
+ end
52
+ end
53
+
54
+ # Verifies whether a user is locked or not.
55
+ def locked?
56
+ locked_at && !lock_expired?
57
+ end
58
+
59
+ # Send unlock instructions by email
60
+ def send_unlock_instructions
61
+ ::DeviseMailer.deliver_unlock_instructions(self)
62
+ end
63
+
64
+ # Resend the unlock instructions if the user is locked.
65
+ def resend_unlock!
66
+ if_locked do
67
+ generate_unlock_token unless unlock_token.present?
68
+ save(false)
69
+ send_unlock_instructions
70
+ end
71
+ end
72
+
73
+ # Overwrites active? from Devise::Models::Activatable for locking purposes
74
+ # by verifying whether an user is active to sign in or not based on locked?
75
+ def active?
76
+ super && !locked?
77
+ end
78
+
79
+ # Overwrites invalid_message from Devise::Models::Authenticatable to define
80
+ # the correct reason for blocking the sign in.
81
+ def inactive_message
82
+ if locked?
83
+ :locked
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
90
+ # for verifying whether an user is allowed to sign in or not. If the user
91
+ # is locked, it should never be allowed.
92
+ def valid_for_authentication?(attributes)
93
+ if result = super
94
+ self.failed_attempts = 0
95
+ else
96
+ self.failed_attempts += 1
97
+ lock if failed_attempts > self.class.maximum_attempts
98
+ end
99
+ save(false)
100
+ result
101
+ end
102
+
103
+ protected
104
+
105
+ # Generates unlock token
106
+ def generate_unlock_token
107
+ self.unlock_token = Devise.friendly_token
108
+ end
109
+
110
+ # Tells if the lock is expired if :time unlock strategy is active
111
+ def lock_expired?
112
+ if unlock_strategy_enabled?(:time)
113
+ locked_at && locked_at < self.class.unlock_in.ago
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ # Checks whether the record is locked or not, yielding to the block
120
+ # if it's locked, otherwise adds an error to email.
121
+ def if_locked
122
+ if locked?
123
+ yield
124
+ else
125
+ self.class.add_error_on(self, :email, :not_locked)
126
+ false
127
+ end
128
+ end
129
+
130
+ # Is the unlock enabled for the given unlock strategy?
131
+ def unlock_strategy_enabled?(strategy)
132
+ [:both, strategy].include?(self.class.unlock_strategy)
133
+ end
134
+
135
+ module ClassMethods
136
+ # Attempt to find a user by it's email. If a record is found, send new
137
+ # unlock instructions to it. If not user is found, returns a new user
138
+ # with an email not found error.
139
+ # Options must contain the user email
140
+ def send_unlock_instructions(attributes={})
141
+ lockable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
142
+ lockable.resend_unlock! unless lockable.new_record?
143
+ lockable
144
+ end
145
+
146
+ # Find a user by it's unlock token and try to unlock it.
147
+ # If no user is found, returns a new user with an error.
148
+ # If the user is not locked, creates an error for the user
149
+ # Options must have the unlock_token
150
+ def unlock!(attributes={})
151
+ lockable = find_or_initialize_with_error_by(:unlock_token, attributes[:unlock_token])
152
+ lockable.unlock! unless lockable.new_record?
153
+ lockable
154
+ end
155
+
156
+ Devise::Models.config(self, :maximum_attempts, :unlock_strategy, :unlock_in)
157
+ end
158
+ end
159
+ end
160
+ end