devise 3.2.2 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

Files changed (235) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +242 -978
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +371 -100
  5. data/app/controllers/devise/confirmations_controller.rb +11 -5
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +12 -6
  7. data/app/controllers/devise/passwords_controller.rb +21 -8
  8. data/app/controllers/devise/registrations_controller.rb +59 -26
  9. data/app/controllers/devise/sessions_controller.rb +47 -17
  10. data/app/controllers/devise/unlocks_controller.rb +9 -4
  11. data/app/controllers/devise_controller.rb +67 -31
  12. data/app/helpers/devise_helper.rb +12 -19
  13. data/app/mailers/devise/mailer.rb +10 -0
  14. data/app/views/devise/confirmations/new.html.erb +9 -5
  15. data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
  16. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  17. data/app/views/devise/mailer/password_change.html.erb +3 -0
  18. data/app/views/devise/mailer/reset_password_instructions.html.erb +1 -1
  19. data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
  20. data/app/views/devise/passwords/edit.html.erb +16 -7
  21. data/app/views/devise/passwords/new.html.erb +9 -5
  22. data/app/views/devise/registrations/edit.html.erb +29 -15
  23. data/app/views/devise/registrations/new.html.erb +20 -9
  24. data/app/views/devise/sessions/new.html.erb +19 -10
  25. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  26. data/app/views/devise/shared/{_links.erb → _links.html.erb} +9 -9
  27. data/app/views/devise/unlocks/new.html.erb +9 -5
  28. data/config/locales/en.yml +23 -17
  29. data/lib/devise/controllers/helpers.rb +112 -32
  30. data/lib/devise/controllers/rememberable.rb +15 -6
  31. data/lib/devise/controllers/scoped_views.rb +3 -1
  32. data/lib/devise/controllers/sign_in_out.rb +42 -26
  33. data/lib/devise/controllers/store_location.rb +31 -5
  34. data/lib/devise/controllers/url_helpers.rb +9 -7
  35. data/lib/devise/delegator.rb +2 -0
  36. data/lib/devise/encryptor.rb +24 -0
  37. data/lib/devise/failure_app.rb +125 -39
  38. data/lib/devise/hooks/activatable.rb +7 -6
  39. data/lib/devise/hooks/csrf_cleaner.rb +5 -1
  40. data/lib/devise/hooks/forgetable.rb +2 -0
  41. data/lib/devise/hooks/lockable.rb +7 -2
  42. data/lib/devise/hooks/proxy.rb +4 -2
  43. data/lib/devise/hooks/rememberable.rb +4 -2
  44. data/lib/devise/hooks/timeoutable.rb +16 -9
  45. data/lib/devise/hooks/trackable.rb +3 -1
  46. data/lib/devise/mailers/helpers.rb +15 -12
  47. data/lib/devise/mapping.rb +8 -2
  48. data/lib/devise/models/authenticatable.rb +82 -56
  49. data/lib/devise/models/confirmable.rb +125 -42
  50. data/lib/devise/models/database_authenticatable.rb +110 -32
  51. data/lib/devise/models/lockable.rb +30 -17
  52. data/lib/devise/models/omniauthable.rb +3 -1
  53. data/lib/devise/models/recoverable.rb +62 -26
  54. data/lib/devise/models/registerable.rb +4 -0
  55. data/lib/devise/models/rememberable.rb +62 -33
  56. data/lib/devise/models/timeoutable.rb +4 -8
  57. data/lib/devise/models/trackable.rb +20 -4
  58. data/lib/devise/models/validatable.rb +16 -9
  59. data/lib/devise/models.rb +3 -1
  60. data/lib/devise/modules.rb +12 -10
  61. data/lib/devise/omniauth/config.rb +2 -0
  62. data/lib/devise/omniauth/url_helpers.rb +14 -5
  63. data/lib/devise/omniauth.rb +2 -0
  64. data/lib/devise/orm/active_record.rb +5 -1
  65. data/lib/devise/orm/mongoid.rb +6 -2
  66. data/lib/devise/parameter_filter.rb +4 -0
  67. data/lib/devise/parameter_sanitizer.rb +139 -65
  68. data/lib/devise/rails/routes.rb +147 -116
  69. data/lib/devise/rails/warden_compat.rb +3 -10
  70. data/lib/devise/rails.rb +10 -13
  71. data/lib/devise/secret_key_finder.rb +27 -0
  72. data/lib/devise/strategies/authenticatable.rb +20 -9
  73. data/lib/devise/strategies/base.rb +3 -1
  74. data/lib/devise/strategies/database_authenticatable.rb +14 -6
  75. data/lib/devise/strategies/rememberable.rb +15 -3
  76. data/lib/devise/test/controller_helpers.rb +165 -0
  77. data/lib/devise/test/integration_helpers.rb +63 -0
  78. data/lib/devise/test_helpers.rb +7 -124
  79. data/lib/devise/time_inflector.rb +4 -2
  80. data/lib/devise/token_generator.rb +3 -41
  81. data/lib/devise/version.rb +3 -1
  82. data/lib/devise.rb +111 -84
  83. data/lib/generators/active_record/devise_generator.rb +49 -12
  84. data/lib/generators/active_record/templates/migration.rb +9 -7
  85. data/lib/generators/active_record/templates/migration_existing.rb +9 -7
  86. data/lib/generators/devise/controllers_generator.rb +46 -0
  87. data/lib/generators/devise/devise_generator.rb +7 -5
  88. data/lib/generators/devise/install_generator.rb +21 -0
  89. data/lib/generators/devise/orm_helpers.rb +10 -21
  90. data/lib/generators/devise/views_generator.rb +49 -28
  91. data/lib/generators/mongoid/devise_generator.rb +21 -19
  92. data/lib/generators/templates/README +5 -12
  93. data/lib/generators/templates/controllers/README +14 -0
  94. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  95. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  96. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  97. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  98. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  99. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  100. data/lib/generators/templates/devise.rb +81 -36
  101. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  102. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  103. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  104. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  105. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  106. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +6 -2
  107. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +9 -4
  108. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +5 -2
  109. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +14 -6
  110. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +12 -4
  111. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +11 -6
  112. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +5 -2
  113. metadata +52 -280
  114. data/.gitignore +0 -10
  115. data/.travis.yml +0 -20
  116. data/.yardopts +0 -9
  117. data/CONTRIBUTING.md +0 -14
  118. data/Gemfile +0 -31
  119. data/Gemfile.lock +0 -160
  120. data/Rakefile +0 -35
  121. data/devise.gemspec +0 -27
  122. data/devise.png +0 -0
  123. data/gemfiles/Gemfile.rails-3.2.x +0 -31
  124. data/gemfiles/Gemfile.rails-3.2.x.lock +0 -159
  125. data/test/controllers/custom_strategy_test.rb +0 -62
  126. data/test/controllers/helpers_test.rb +0 -276
  127. data/test/controllers/internal_helpers_test.rb +0 -120
  128. data/test/controllers/passwords_controller_test.rb +0 -31
  129. data/test/controllers/sessions_controller_test.rb +0 -99
  130. data/test/controllers/url_helpers_test.rb +0 -59
  131. data/test/delegator_test.rb +0 -19
  132. data/test/devise_test.rb +0 -94
  133. data/test/failure_app_test.rb +0 -232
  134. data/test/generators/active_record_generator_test.rb +0 -103
  135. data/test/generators/devise_generator_test.rb +0 -39
  136. data/test/generators/install_generator_test.rb +0 -13
  137. data/test/generators/mongoid_generator_test.rb +0 -23
  138. data/test/generators/views_generator_test.rb +0 -67
  139. data/test/helpers/devise_helper_test.rb +0 -51
  140. data/test/integration/authenticatable_test.rb +0 -713
  141. data/test/integration/confirmable_test.rb +0 -284
  142. data/test/integration/database_authenticatable_test.rb +0 -84
  143. data/test/integration/http_authenticatable_test.rb +0 -105
  144. data/test/integration/lockable_test.rb +0 -239
  145. data/test/integration/omniauthable_test.rb +0 -133
  146. data/test/integration/recoverable_test.rb +0 -334
  147. data/test/integration/registerable_test.rb +0 -349
  148. data/test/integration/rememberable_test.rb +0 -167
  149. data/test/integration/timeoutable_test.rb +0 -183
  150. data/test/integration/trackable_test.rb +0 -92
  151. data/test/mailers/confirmation_instructions_test.rb +0 -115
  152. data/test/mailers/reset_password_instructions_test.rb +0 -96
  153. data/test/mailers/unlock_instructions_test.rb +0 -91
  154. data/test/mapping_test.rb +0 -127
  155. data/test/models/authenticatable_test.rb +0 -13
  156. data/test/models/confirmable_test.rb +0 -454
  157. data/test/models/database_authenticatable_test.rb +0 -249
  158. data/test/models/lockable_test.rb +0 -298
  159. data/test/models/omniauthable_test.rb +0 -7
  160. data/test/models/recoverable_test.rb +0 -184
  161. data/test/models/registerable_test.rb +0 -7
  162. data/test/models/rememberable_test.rb +0 -183
  163. data/test/models/serializable_test.rb +0 -49
  164. data/test/models/timeoutable_test.rb +0 -51
  165. data/test/models/trackable_test.rb +0 -13
  166. data/test/models/validatable_test.rb +0 -127
  167. data/test/models_test.rb +0 -144
  168. data/test/omniauth/config_test.rb +0 -57
  169. data/test/omniauth/url_helpers_test.rb +0 -54
  170. data/test/orm/active_record.rb +0 -10
  171. data/test/orm/mongoid.rb +0 -13
  172. data/test/parameter_sanitizer_test.rb +0 -81
  173. data/test/rails_app/Rakefile +0 -6
  174. data/test/rails_app/app/active_record/admin.rb +0 -6
  175. data/test/rails_app/app/active_record/shim.rb +0 -2
  176. data/test/rails_app/app/active_record/user.rb +0 -6
  177. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  178. data/test/rails_app/app/controllers/admins_controller.rb +0 -11
  179. data/test/rails_app/app/controllers/application_controller.rb +0 -9
  180. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  181. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  182. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  183. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  184. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  185. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  186. data/test/rails_app/app/mailers/users/mailer.rb +0 -12
  187. data/test/rails_app/app/mongoid/admin.rb +0 -29
  188. data/test/rails_app/app/mongoid/shim.rb +0 -23
  189. data/test/rails_app/app/mongoid/user.rb +0 -39
  190. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  191. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  192. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  193. data/test/rails_app/app/views/home/index.html.erb +0 -1
  194. data/test/rails_app/app/views/home/join.html.erb +0 -1
  195. data/test/rails_app/app/views/home/private.html.erb +0 -1
  196. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  197. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  198. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  199. data/test/rails_app/app/views/users/index.html.erb +0 -1
  200. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  201. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  202. data/test/rails_app/bin/bundle +0 -3
  203. data/test/rails_app/bin/rails +0 -4
  204. data/test/rails_app/bin/rake +0 -4
  205. data/test/rails_app/config/application.rb +0 -40
  206. data/test/rails_app/config/boot.rb +0 -14
  207. data/test/rails_app/config/database.yml +0 -18
  208. data/test/rails_app/config/environment.rb +0 -5
  209. data/test/rails_app/config/environments/development.rb +0 -30
  210. data/test/rails_app/config/environments/production.rb +0 -80
  211. data/test/rails_app/config/environments/test.rb +0 -36
  212. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  213. data/test/rails_app/config/initializers/devise.rb +0 -181
  214. data/test/rails_app/config/initializers/inflections.rb +0 -2
  215. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  216. data/test/rails_app/config/initializers/session_store.rb +0 -1
  217. data/test/rails_app/config/routes.rb +0 -104
  218. data/test/rails_app/config.ru +0 -4
  219. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  220. data/test/rails_app/db/schema.rb +0 -55
  221. data/test/rails_app/lib/shared_admin.rb +0 -17
  222. data/test/rails_app/lib/shared_user.rb +0 -29
  223. data/test/rails_app/public/404.html +0 -26
  224. data/test/rails_app/public/422.html +0 -26
  225. data/test/rails_app/public/500.html +0 -26
  226. data/test/rails_app/public/favicon.ico +0 -0
  227. data/test/routes_test.rb +0 -250
  228. data/test/support/assertions.rb +0 -40
  229. data/test/support/helpers.rb +0 -70
  230. data/test/support/integration.rb +0 -92
  231. data/test/support/locale/en.yml +0 -8
  232. data/test/support/webrat/integrations/rails.rb +0 -24
  233. data/test/test_helper.rb +0 -27
  234. data/test/test_helpers_test.rb +0 -173
  235. data/test/test_models.rb +0 -33
@@ -1,25 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/database_authenticatable'
2
- require 'bcrypt'
3
4
 
4
5
  module Devise
5
- # Digests the password using bcrypt.
6
- def self.bcrypt(klass, password)
7
- ::BCrypt::Password.create("#{password}#{klass.pepper}", :cost => klass.stretches).to_s
8
- end
9
-
10
6
  module Models
11
- # Authenticatable Module, responsible for encrypting password and validating
12
- # authenticity of a user while signing in.
7
+ # Authenticatable Module, responsible for hashing the password and
8
+ # validating the authenticity of a user while signing in.
13
9
  #
14
10
  # == Options
15
11
  #
16
- # DatabaseAuthenticable adds the following options to devise_for:
12
+ # DatabaseAuthenticatable adds the following options to devise_for:
17
13
  #
18
14
  # * +pepper+: a random string used to provide a more secure hash. Use
19
- # `rake secret` to generate new keys.
15
+ # `rails secret` to generate new keys.
20
16
  #
21
17
  # * +stretches+: the cost given to bcrypt.
22
18
  #
19
+ # * +send_email_changed_notification+: notify original email when it changes.
20
+ #
21
+ # * +send_password_change_notification+: notify email when password changes.
22
+ #
23
23
  # == Examples
24
24
  #
25
25
  # User.find(1).valid_password?('password123') # returns true/false
@@ -28,37 +28,68 @@ module Devise
28
28
  extend ActiveSupport::Concern
29
29
 
30
30
  included do
31
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
32
+ after_update :send_password_change_notification, if: :send_password_change_notification?
33
+
31
34
  attr_reader :password, :current_password
32
35
  attr_accessor :password_confirmation
33
36
  end
34
37
 
38
+ def initialize(*args, &block)
39
+ @skip_email_changed_notification = false
40
+ @skip_password_change_notification = false
41
+ super
42
+ end
43
+
44
+ # Skips sending the email changed notification after_update
45
+ def skip_email_changed_notification!
46
+ @skip_email_changed_notification = true
47
+ end
48
+
49
+ # Skips sending the password change notification after_update
50
+ def skip_password_change_notification!
51
+ @skip_password_change_notification = true
52
+ end
53
+
35
54
  def self.required_fields(klass)
36
55
  [:encrypted_password] + klass.authentication_keys
37
56
  end
38
57
 
39
- # Generates password encryption based on the given value.
58
+ # Generates a hashed password based on the given value.
59
+ # For legacy reasons, we use `encrypted_password` to store
60
+ # the hashed password.
40
61
  def password=(new_password)
41
62
  @password = new_password
42
- self.encrypted_password = password_digest(@password) if @password.present?
63
+ self.encrypted_password = password_digest(@password)
43
64
  end
44
65
 
45
- # Verifies whether an password (ie from sign in) is the user password.
66
+ # Verifies whether a password (ie from sign in) is the user password.
46
67
  def valid_password?(password)
47
- return false if encrypted_password.blank?
48
- bcrypt = ::BCrypt::Password.new(encrypted_password)
49
- password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
50
- Devise.secure_compare(password, encrypted_password)
68
+ Devise::Encryptor.compare(self.class, encrypted_password, password)
51
69
  end
52
70
 
53
71
  # Set password and password confirmation to nil
54
72
  def clean_up_passwords
55
- self.password = self.password_confirmation = nil
73
+ @password = @password_confirmation = nil
56
74
  end
57
75
 
58
- # Update record attributes when :current_password matches, otherwise returns
59
- # error on :current_password. It also automatically rejects :password and
60
- # :password_confirmation if they are blank.
76
+ # Update record attributes when :current_password matches, otherwise
77
+ # returns error on :current_password.
78
+ #
79
+ # This method also rejects the password field if it is blank (allowing
80
+ # users to change relevant information like the e-mail without changing
81
+ # their password). In case the password field is rejected, the confirmation
82
+ # is also rejected as long as it is also blank.
61
83
  def update_with_password(params, *options)
84
+ if options.present?
85
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
86
+ [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
87
+ (`options`) is deprecated and it will be removed in the next major version.
88
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
89
+ from your code.
90
+ DEPRECATION
91
+ end
92
+
62
93
  current_password = params.delete(:current_password)
63
94
 
64
95
  if params[:password].blank?
@@ -67,11 +98,11 @@ module Devise
67
98
  end
68
99
 
69
100
  result = if valid_password?(current_password)
70
- update_attributes(params, *options)
101
+ update(params, *options)
71
102
  else
72
- self.assign_attributes(params, *options)
73
- self.valid?
74
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
103
+ assign_attributes(params, *options)
104
+ valid?
105
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
75
106
  false
76
107
  end
77
108
 
@@ -92,10 +123,19 @@ module Devise
92
123
  # end
93
124
  #
94
125
  def update_without_password(params, *options)
126
+ if options.present?
127
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
128
+ [Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
129
+ (`options`) is deprecated and it will be removed in the next major version.
130
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
131
+ from your code.
132
+ DEPRECATION
133
+ end
134
+
95
135
  params.delete(:password)
96
136
  params.delete(:password_confirmation)
97
137
 
98
- result = update_attributes(params, *options)
138
+ result = update(params, *options)
99
139
  clean_up_passwords
100
140
  result
101
141
  end
@@ -107,8 +147,8 @@ module Devise
107
147
  result = if valid_password?(current_password)
108
148
  destroy
109
149
  else
110
- self.valid?
111
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
150
+ valid?
151
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
112
152
  false
113
153
  end
114
154
 
@@ -133,19 +173,57 @@ module Devise
133
173
  encrypted_password[0,29] if encrypted_password
134
174
  end
135
175
 
176
+ if Devise.activerecord51?
177
+ # Send notification to user when email changes.
178
+ def send_email_changed_notification
179
+ send_devise_notification(:email_changed, to: email_before_last_save)
180
+ end
181
+ else
182
+ # Send notification to user when email changes.
183
+ def send_email_changed_notification
184
+ send_devise_notification(:email_changed, to: email_was)
185
+ end
186
+ end
187
+
188
+ # Send notification to user when password changes.
189
+ def send_password_change_notification
190
+ send_devise_notification(:password_change)
191
+ end
192
+
136
193
  protected
137
194
 
138
- # Digests the password using bcrypt. Custom encryption should override
195
+ # Hashes the password using bcrypt. Custom hash functions should override
139
196
  # this method to apply their own algorithm.
140
197
  #
141
198
  # See https://github.com/plataformatec/devise-encryptable for examples
142
- # of other encryption engines.
199
+ # of other hashing engines.
143
200
  def password_digest(password)
144
- Devise.bcrypt(self.class, password)
201
+ return if password.blank?
202
+ Devise::Encryptor.digest(self.class, password)
203
+ end
204
+
205
+ if Devise.activerecord51?
206
+ def send_email_changed_notification?
207
+ self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
208
+ end
209
+ else
210
+ def send_email_changed_notification?
211
+ self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
212
+ end
213
+ end
214
+
215
+ if Devise.activerecord51?
216
+ def send_password_change_notification?
217
+ self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
218
+ end
219
+ else
220
+ def send_password_change_notification?
221
+ self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
222
+ end
145
223
  end
146
224
 
147
225
  module ClassMethods
148
- Devise::Models.config(self, :pepper, :stretches)
226
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
149
227
 
150
228
  # We assume this method already gets the sanitized values from the
151
229
  # DatabaseAuthenticatable strategy. If you are using this method on
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "devise/hooks/lockable"
2
4
 
3
5
  module Devise
@@ -7,7 +9,7 @@ module Devise
7
9
  # blocked: email and time. The former will send an email to the user when
8
10
  # the lock happens, containing a link to unlock its account. The second
9
11
  # will unlock the user automatically after some configured time (ie 2.hours).
10
- # It's also possible to setup lockable to use both email and time strategies.
12
+ # It's also possible to set up lockable to use both email and time strategies.
11
13
  #
12
14
  # == Options
13
15
  #
@@ -22,7 +24,7 @@ module Devise
22
24
  module Lockable
23
25
  extend ActiveSupport::Concern
24
26
 
25
- delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
27
+ delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, to: "self.class"
26
28
 
27
29
  def self.required_fields(klass)
28
30
  attributes = []
@@ -34,13 +36,16 @@ module Devise
34
36
  end
35
37
 
36
38
  # Lock a user setting its locked_at to actual time.
37
- def lock_access!
39
+ # * +opts+: Hash options if you don't want to send email
40
+ # when you lock access, you could pass the next hash
41
+ # `{ send_instructions: false } as option`.
42
+ def lock_access!(opts = { })
38
43
  self.locked_at = Time.now.utc
39
44
 
40
- if unlock_strategy_enabled?(:email)
45
+ if unlock_strategy_enabled?(:email) && opts.fetch(:send_instructions, true)
41
46
  send_unlock_instructions
42
47
  else
43
- save(:validate => false)
48
+ save(validate: false)
44
49
  end
45
50
  end
46
51
 
@@ -49,7 +54,7 @@ module Devise
49
54
  self.locked_at = nil
50
55
  self.failed_attempts = 0 if respond_to?(:failed_attempts=)
51
56
  self.unlock_token = nil if respond_to?(:unlock_token=)
52
- save(:validate => false)
57
+ save(validate: false)
53
58
  end
54
59
 
55
60
  # Verifies whether a user is locked or not.
@@ -61,7 +66,7 @@ module Devise
61
66
  def send_unlock_instructions
62
67
  raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
63
68
  self.unlock_token = enc
64
- self.save(:validate => false)
69
+ save(validate: false)
65
70
  send_devise_notification(:unlock_instructions, raw, {})
66
71
  raw
67
72
  end
@@ -96,26 +101,30 @@ module Devise
96
101
  if super && !access_locked?
97
102
  true
98
103
  else
99
- self.failed_attempts ||= 0
100
- self.failed_attempts += 1
104
+ increment_failed_attempts
101
105
  if attempts_exceeded?
102
106
  lock_access! unless access_locked?
103
107
  else
104
- save(:validate => false)
108
+ save(validate: false)
105
109
  end
106
110
  false
107
111
  end
108
112
  end
113
+
114
+ def increment_failed_attempts
115
+ self.class.increment_counter(:failed_attempts, id)
116
+ reload
117
+ end
109
118
 
110
119
  def unauthenticated_message
111
120
  # If set to paranoid mode, do not show the locked message because it
112
121
  # leaks the existence of an account.
113
122
  if Devise.paranoid
114
123
  super
115
- elsif lock_strategy_enabled?(:failed_attempts) && last_attempt?
116
- :last_attempt
117
- elsif lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
124
+ elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
118
125
  :locked
126
+ elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning
127
+ :last_attempt
119
128
  else
120
129
  super
121
130
  end
@@ -124,11 +133,11 @@ module Devise
124
133
  protected
125
134
 
126
135
  def attempts_exceeded?
127
- self.failed_attempts > self.class.maximum_attempts
136
+ self.failed_attempts >= self.class.maximum_attempts
128
137
  end
129
138
 
130
139
  def last_attempt?
131
- self.failed_attempts == self.class.maximum_attempts
140
+ self.failed_attempts == self.class.maximum_attempts - 1
132
141
  end
133
142
 
134
143
  # Tells if the lock is expired if :time unlock strategy is active
@@ -152,6 +161,9 @@ module Devise
152
161
  end
153
162
 
154
163
  module ClassMethods
164
+ # List of strategies that are enabled/supported if :both is used.
165
+ BOTH_STRATEGIES = [:time, :email]
166
+
155
167
  # Attempt to find a user by its unlock keys. If a record is found, send new
156
168
  # unlock instructions to it. If not user is found, returns a new user
157
169
  # with an email not found error.
@@ -178,7 +190,8 @@ module Devise
178
190
 
179
191
  # Is the unlock enabled for the given unlock strategy?
180
192
  def unlock_strategy_enabled?(strategy)
181
- [:both, strategy].include?(self.unlock_strategy)
193
+ self.unlock_strategy == strategy ||
194
+ (self.unlock_strategy == :both && BOTH_STRATEGIES.include?(strategy))
182
195
  end
183
196
 
184
197
  # Is the lock enabled for the given lock strategy?
@@ -186,7 +199,7 @@ module Devise
186
199
  self.lock_strategy == strategy
187
200
  end
188
201
 
189
- Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
202
+ Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys, :last_attempt_warning)
190
203
  end
191
204
  end
192
205
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/omniauth'
2
4
 
3
5
  module Devise
@@ -10,7 +12,7 @@ module Devise
10
12
  #
11
13
  # * +omniauth_providers+: Which providers are available to this model. It expects an array:
12
14
  #
13
- # devise_for :database_authenticatable, :omniauthable, :omniauth_providers => [:twitter]
15
+ # devise_for :database_authenticatable, :omniauthable, omniauth_providers: [:twitter]
14
16
  #
15
17
  module Omniauthable
16
18
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
 
@@ -8,15 +10,13 @@ module Devise
8
10
  # Recoverable adds the following options to devise_for:
9
11
  #
10
12
  # * +reset_password_keys+: the keys you want to use when recovering the password for an account
13
+ # * +reset_password_within+: the time period within which the password must be reset or the token expires.
14
+ # * +sign_in_after_reset_password+: whether or not to sign in the user automatically after a password reset.
11
15
  #
12
16
  # == Examples
13
17
  #
14
18
  # # resets the user password and save the record, true if valid passwords are given, otherwise false
15
- # User.find(1).reset_password!('password123', 'password123')
16
- #
17
- # # only resets the user password, without saving the record
18
- # user = User.find(1)
19
- # user.reset_password('password123', 'password123')
19
+ # User.find(1).reset_password('password123', 'password123')
20
20
  #
21
21
  # # creates a new token and send it with instructions about how to reset the password
22
22
  # User.find(1).send_reset_password_instructions
@@ -28,31 +28,30 @@ module Devise
28
28
  [:reset_password_sent_at, :reset_password_token]
29
29
  end
30
30
 
31
+ included do
32
+ before_update :clear_reset_password_token, if: :clear_reset_password_token?
33
+ end
34
+
31
35
  # Update password saving the record and clearing token. Returns true if
32
36
  # the passwords are valid and the record was saved, false otherwise.
33
- def reset_password!(new_password, new_password_confirmation)
34
- self.password = new_password
35
- self.password_confirmation = new_password_confirmation
36
-
37
- if valid?
38
- clear_reset_password_token
39
- after_password_reset
37
+ def reset_password(new_password, new_password_confirmation)
38
+ if new_password.present?
39
+ self.password = new_password
40
+ self.password_confirmation = new_password_confirmation
41
+ save
42
+ else
43
+ errors.add(:password, :blank)
44
+ false
40
45
  end
41
-
42
- save
43
46
  end
44
47
 
45
48
  # Resets reset password token and send reset password instructions by email.
46
49
  # Returns the token sent in the e-mail.
47
50
  def send_reset_password_instructions
48
- raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
49
-
50
- self.reset_password_token = enc
51
- self.reset_password_sent_at = Time.now.utc
52
- self.save(:validate => false)
51
+ token = set_reset_password_token
52
+ send_reset_password_instructions_notification(token)
53
53
 
54
- send_devise_notification(:reset_password_instructions, raw, {})
55
- raw
54
+ token
56
55
  end
57
56
 
58
57
  # Checks if the reset password token sent is within the limit time.
@@ -76,7 +75,7 @@ module Devise
76
75
  # reset_password_period_valid? # will always return false
77
76
  #
78
77
  def reset_password_period_valid?
79
- reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
78
+ reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago.utc
80
79
  end
81
80
 
82
81
  protected
@@ -87,10 +86,47 @@ module Devise
87
86
  self.reset_password_sent_at = nil
88
87
  end
89
88
 
90
- def after_password_reset
89
+ def set_reset_password_token
90
+ raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
91
+
92
+ self.reset_password_token = enc
93
+ self.reset_password_sent_at = Time.now.utc
94
+ save(validate: false)
95
+ raw
96
+ end
97
+
98
+ def send_reset_password_instructions_notification(token)
99
+ send_devise_notification(:reset_password_instructions, token, {})
100
+ end
101
+
102
+ if Devise.activerecord51?
103
+ def clear_reset_password_token?
104
+ encrypted_password_changed = respond_to?(:will_save_change_to_encrypted_password?) && will_save_change_to_encrypted_password?
105
+ authentication_keys_changed = self.class.authentication_keys.any? do |attribute|
106
+ respond_to?("will_save_change_to_#{attribute}?") && send("will_save_change_to_#{attribute}?")
107
+ end
108
+
109
+ authentication_keys_changed || encrypted_password_changed
110
+ end
111
+ else
112
+ def clear_reset_password_token?
113
+ encrypted_password_changed = respond_to?(:encrypted_password_changed?) && encrypted_password_changed?
114
+ authentication_keys_changed = self.class.authentication_keys.any? do |attribute|
115
+ respond_to?("#{attribute}_changed?") && send("#{attribute}_changed?")
116
+ end
117
+
118
+ authentication_keys_changed || encrypted_password_changed
119
+ end
91
120
  end
92
121
 
93
122
  module ClassMethods
123
+ # Attempt to find a user by password reset token. If a user is found, return it
124
+ # If a user is not found, return nil
125
+ def with_reset_password_token(token)
126
+ reset_password_token = Devise.token_generator.digest(self, :reset_password_token, token)
127
+ to_adapter.find_first(reset_password_token: reset_password_token)
128
+ end
129
+
94
130
  # Attempt to find a user by its email. If a record is found, send new
95
131
  # password instructions to it. If user is not found, returns a new user
96
132
  # with an email not found error.
@@ -114,17 +150,17 @@ module Devise
114
150
 
115
151
  if recoverable.persisted?
116
152
  if recoverable.reset_password_period_valid?
117
- recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
153
+ recoverable.reset_password(attributes[:password], attributes[:password_confirmation])
118
154
  else
119
155
  recoverable.errors.add(:reset_password_token, :expired)
120
156
  end
121
157
  end
122
158
 
123
- recoverable.reset_password_token = original_token
159
+ recoverable.reset_password_token = original_token if recoverable.reset_password_token.present?
124
160
  recoverable
125
161
  end
126
162
 
127
- Devise::Models.config(self, :reset_password_keys, :reset_password_within)
163
+ Devise::Models.config(self, :reset_password_keys, :reset_password_within, :sign_in_after_reset_password)
128
164
  end
129
165
  end
130
166
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  # Registerable is responsible for everything related to registering a new
@@ -19,6 +21,8 @@ module Devise
19
21
  def new_with_session(params, session)
20
22
  new(params)
21
23
  end
24
+
25
+ Devise::Models.config(self, :sign_in_after_change_password)
22
26
  end
23
27
  end
24
28
  end