devise 3.2.4 → 4.7.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 (235) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +259 -994
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +336 -99
  5. data/app/controllers/devise/confirmations_controller.rb +9 -3
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +12 -6
  7. data/app/controllers/devise/passwords_controller.rb +19 -6
  8. data/app/controllers/devise/registrations_controller.rb +55 -22
  9. data/app/controllers/devise/sessions_controller.rb +44 -14
  10. data/app/controllers/devise/unlocks_controller.rb +7 -2
  11. data/app/controllers/devise_controller.rb +65 -29
  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 +8 -4
  15. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  16. data/app/views/devise/mailer/password_change.html.erb +3 -0
  17. data/app/views/devise/passwords/edit.html.erb +15 -6
  18. data/app/views/devise/passwords/new.html.erb +8 -4
  19. data/app/views/devise/registrations/edit.html.erb +27 -13
  20. data/app/views/devise/registrations/new.html.erb +19 -8
  21. data/app/views/devise/sessions/new.html.erb +18 -9
  22. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  23. data/app/views/devise/shared/{_links.erb → _links.html.erb} +9 -9
  24. data/app/views/devise/unlocks/new.html.erb +8 -4
  25. data/config/locales/en.yml +22 -16
  26. data/lib/devise/controllers/helpers.rb +109 -29
  27. data/lib/devise/controllers/rememberable.rb +12 -3
  28. data/lib/devise/controllers/scoped_views.rb +2 -0
  29. data/lib/devise/controllers/sign_in_out.rb +36 -20
  30. data/lib/devise/controllers/store_location.rb +31 -5
  31. data/lib/devise/controllers/url_helpers.rb +9 -7
  32. data/lib/devise/delegator.rb +2 -0
  33. data/lib/devise/encryptor.rb +24 -0
  34. data/lib/devise/failure_app.rb +116 -36
  35. data/lib/devise/hooks/activatable.rb +5 -4
  36. data/lib/devise/hooks/csrf_cleaner.rb +5 -1
  37. data/lib/devise/hooks/forgetable.rb +2 -0
  38. data/lib/devise/hooks/lockable.rb +6 -1
  39. data/lib/devise/hooks/proxy.rb +3 -1
  40. data/lib/devise/hooks/rememberable.rb +2 -0
  41. data/lib/devise/hooks/timeoutable.rb +15 -8
  42. data/lib/devise/hooks/trackable.rb +2 -0
  43. data/lib/devise/mailers/helpers.rb +7 -4
  44. data/lib/devise/mapping.rb +8 -2
  45. data/lib/devise/models/authenticatable.rb +76 -51
  46. data/lib/devise/models/confirmable.rb +129 -34
  47. data/lib/devise/models/database_authenticatable.rb +107 -30
  48. data/lib/devise/models/lockable.rb +19 -9
  49. data/lib/devise/models/omniauthable.rb +2 -0
  50. data/lib/devise/models/recoverable.rb +62 -26
  51. data/lib/devise/models/registerable.rb +4 -0
  52. data/lib/devise/models/rememberable.rb +58 -29
  53. data/lib/devise/models/timeoutable.rb +2 -6
  54. data/lib/devise/models/trackable.rb +20 -4
  55. data/lib/devise/models/validatable.rb +12 -5
  56. data/lib/devise/models.rb +3 -1
  57. data/lib/devise/modules.rb +2 -0
  58. data/lib/devise/omniauth/config.rb +2 -0
  59. data/lib/devise/omniauth/url_helpers.rb +14 -5
  60. data/lib/devise/omniauth.rb +2 -0
  61. data/lib/devise/orm/active_record.rb +5 -1
  62. data/lib/devise/orm/mongoid.rb +6 -2
  63. data/lib/devise/parameter_filter.rb +4 -0
  64. data/lib/devise/parameter_sanitizer.rb +139 -65
  65. data/lib/devise/rails/routes.rb +80 -61
  66. data/lib/devise/rails/warden_compat.rb +3 -10
  67. data/lib/devise/rails.rb +8 -17
  68. data/lib/devise/secret_key_finder.rb +27 -0
  69. data/lib/devise/strategies/authenticatable.rb +18 -7
  70. data/lib/devise/strategies/base.rb +2 -0
  71. data/lib/devise/strategies/database_authenticatable.rb +13 -5
  72. data/lib/devise/strategies/rememberable.rb +15 -3
  73. data/lib/devise/test/controller_helpers.rb +165 -0
  74. data/lib/devise/test/integration_helpers.rb +63 -0
  75. data/lib/devise/test_helpers.rb +7 -124
  76. data/lib/devise/time_inflector.rb +2 -0
  77. data/lib/devise/token_generator.rb +3 -41
  78. data/lib/devise/version.rb +3 -1
  79. data/lib/devise.rb +106 -79
  80. data/lib/generators/active_record/devise_generator.rb +44 -7
  81. data/lib/generators/active_record/templates/migration.rb +5 -3
  82. data/lib/generators/active_record/templates/migration_existing.rb +5 -3
  83. data/lib/generators/devise/controllers_generator.rb +46 -0
  84. data/lib/generators/devise/devise_generator.rb +4 -2
  85. data/lib/generators/devise/install_generator.rb +17 -0
  86. data/lib/generators/devise/orm_helpers.rb +10 -21
  87. data/lib/generators/devise/views_generator.rb +21 -11
  88. data/lib/generators/mongoid/devise_generator.rb +7 -5
  89. data/lib/generators/templates/README +2 -9
  90. data/lib/generators/templates/controllers/README +14 -0
  91. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  92. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  93. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  94. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  95. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  96. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  97. data/lib/generators/templates/devise.rb +69 -30
  98. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  99. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  100. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  101. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  102. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  103. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +5 -1
  104. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +10 -2
  105. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +4 -1
  106. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +11 -3
  107. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +11 -3
  108. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +9 -4
  109. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +4 -1
  110. metadata +31 -259
  111. data/.gitignore +0 -11
  112. data/.travis.yml +0 -28
  113. data/.yardopts +0 -9
  114. data/CONTRIBUTING.md +0 -14
  115. data/Gemfile +0 -29
  116. data/Gemfile.lock +0 -160
  117. data/Rakefile +0 -35
  118. data/devise.gemspec +0 -27
  119. data/devise.png +0 -0
  120. data/gemfiles/Gemfile.rails-3.2-stable +0 -29
  121. data/gemfiles/Gemfile.rails-4.0-stable +0 -29
  122. data/gemfiles/Gemfile.rails-head +0 -29
  123. data/test/controllers/custom_strategy_test.rb +0 -62
  124. data/test/controllers/helpers_test.rb +0 -276
  125. data/test/controllers/internal_helpers_test.rb +0 -123
  126. data/test/controllers/passwords_controller_test.rb +0 -31
  127. data/test/controllers/sessions_controller_test.rb +0 -103
  128. data/test/controllers/url_helpers_test.rb +0 -59
  129. data/test/delegator_test.rb +0 -19
  130. data/test/devise_test.rb +0 -94
  131. data/test/failure_app_test.rb +0 -232
  132. data/test/generators/active_record_generator_test.rb +0 -103
  133. data/test/generators/devise_generator_test.rb +0 -39
  134. data/test/generators/install_generator_test.rb +0 -13
  135. data/test/generators/mongoid_generator_test.rb +0 -23
  136. data/test/generators/views_generator_test.rb +0 -96
  137. data/test/helpers/devise_helper_test.rb +0 -51
  138. data/test/integration/authenticatable_test.rb +0 -713
  139. data/test/integration/confirmable_test.rb +0 -284
  140. data/test/integration/database_authenticatable_test.rb +0 -84
  141. data/test/integration/http_authenticatable_test.rb +0 -105
  142. data/test/integration/lockable_test.rb +0 -239
  143. data/test/integration/omniauthable_test.rb +0 -133
  144. data/test/integration/recoverable_test.rb +0 -334
  145. data/test/integration/registerable_test.rb +0 -349
  146. data/test/integration/rememberable_test.rb +0 -167
  147. data/test/integration/timeoutable_test.rb +0 -183
  148. data/test/integration/trackable_test.rb +0 -92
  149. data/test/mailers/confirmation_instructions_test.rb +0 -115
  150. data/test/mailers/reset_password_instructions_test.rb +0 -96
  151. data/test/mailers/unlock_instructions_test.rb +0 -91
  152. data/test/mapping_test.rb +0 -127
  153. data/test/models/authenticatable_test.rb +0 -13
  154. data/test/models/confirmable_test.rb +0 -454
  155. data/test/models/database_authenticatable_test.rb +0 -249
  156. data/test/models/lockable_test.rb +0 -316
  157. data/test/models/omniauthable_test.rb +0 -7
  158. data/test/models/recoverable_test.rb +0 -184
  159. data/test/models/registerable_test.rb +0 -7
  160. data/test/models/rememberable_test.rb +0 -183
  161. data/test/models/serializable_test.rb +0 -49
  162. data/test/models/timeoutable_test.rb +0 -51
  163. data/test/models/trackable_test.rb +0 -13
  164. data/test/models/validatable_test.rb +0 -127
  165. data/test/models_test.rb +0 -144
  166. data/test/omniauth/config_test.rb +0 -57
  167. data/test/omniauth/url_helpers_test.rb +0 -54
  168. data/test/orm/active_record.rb +0 -10
  169. data/test/orm/mongoid.rb +0 -13
  170. data/test/parameter_sanitizer_test.rb +0 -81
  171. data/test/rails_app/Rakefile +0 -6
  172. data/test/rails_app/app/active_record/admin.rb +0 -6
  173. data/test/rails_app/app/active_record/shim.rb +0 -2
  174. data/test/rails_app/app/active_record/user.rb +0 -6
  175. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  176. data/test/rails_app/app/controllers/admins_controller.rb +0 -11
  177. data/test/rails_app/app/controllers/application_controller.rb +0 -9
  178. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  179. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  180. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  181. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  182. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  183. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  184. data/test/rails_app/app/mailers/users/mailer.rb +0 -12
  185. data/test/rails_app/app/mongoid/admin.rb +0 -29
  186. data/test/rails_app/app/mongoid/shim.rb +0 -23
  187. data/test/rails_app/app/mongoid/user.rb +0 -39
  188. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  189. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  190. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  191. data/test/rails_app/app/views/home/index.html.erb +0 -1
  192. data/test/rails_app/app/views/home/join.html.erb +0 -1
  193. data/test/rails_app/app/views/home/private.html.erb +0 -1
  194. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  195. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  196. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  197. data/test/rails_app/app/views/users/index.html.erb +0 -1
  198. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  199. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  200. data/test/rails_app/bin/bundle +0 -3
  201. data/test/rails_app/bin/rails +0 -4
  202. data/test/rails_app/bin/rake +0 -4
  203. data/test/rails_app/config/application.rb +0 -40
  204. data/test/rails_app/config/boot.rb +0 -14
  205. data/test/rails_app/config/database.yml +0 -18
  206. data/test/rails_app/config/environment.rb +0 -5
  207. data/test/rails_app/config/environments/development.rb +0 -30
  208. data/test/rails_app/config/environments/production.rb +0 -80
  209. data/test/rails_app/config/environments/test.rb +0 -36
  210. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  211. data/test/rails_app/config/initializers/devise.rb +0 -181
  212. data/test/rails_app/config/initializers/inflections.rb +0 -2
  213. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  214. data/test/rails_app/config/initializers/session_store.rb +0 -1
  215. data/test/rails_app/config/routes.rb +0 -105
  216. data/test/rails_app/config.ru +0 -4
  217. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  218. data/test/rails_app/db/schema.rb +0 -55
  219. data/test/rails_app/lib/shared_admin.rb +0 -17
  220. data/test/rails_app/lib/shared_user.rb +0 -29
  221. data/test/rails_app/public/404.html +0 -26
  222. data/test/rails_app/public/422.html +0 -26
  223. data/test/rails_app/public/500.html +0 -26
  224. data/test/rails_app/public/favicon.ico +0 -0
  225. data/test/routes_test.rb +0 -262
  226. data/test/support/action_controller/record_identifier.rb +0 -10
  227. data/test/support/assertions.rb +0 -40
  228. data/test/support/helpers.rb +0 -70
  229. data/test/support/integration.rb +0 -92
  230. data/test/support/locale/en.yml +0 -8
  231. data/test/support/mongoid.yml +0 -6
  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,26 +28,44 @@ 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
63
  self.encrypted_password = password_digest(@password) if @password.present?
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
@@ -55,10 +73,23 @@ module Devise
55
73
  self.password = self.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,56 @@ 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
+ Devise::Encryptor.digest(self.class, password)
202
+ end
203
+
204
+ if Devise.activerecord51?
205
+ def send_email_changed_notification?
206
+ self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
207
+ end
208
+ else
209
+ def send_email_changed_notification?
210
+ self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
211
+ end
212
+ end
213
+
214
+ if Devise.activerecord51?
215
+ def send_password_change_notification?
216
+ self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
217
+ end
218
+ else
219
+ def send_password_change_notification?
220
+ self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
221
+ end
145
222
  end
146
223
 
147
224
  module ClassMethods
148
- Devise::Models.config(self, :pepper, :stretches)
225
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
149
226
 
150
227
  # We assume this method already gets the sanitized values from the
151
228
  # 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
  #
@@ -64,7 +66,7 @@ module Devise
64
66
  def send_unlock_instructions
65
67
  raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
66
68
  self.unlock_token = enc
67
- self.save(validate: false)
69
+ save(validate: false)
68
70
  send_devise_notification(:unlock_instructions, raw, {})
69
71
  raw
70
72
  end
@@ -99,8 +101,7 @@ module Devise
99
101
  if super && !access_locked?
100
102
  true
101
103
  else
102
- self.failed_attempts ||= 0
103
- self.failed_attempts += 1
104
+ increment_failed_attempts
104
105
  if attempts_exceeded?
105
106
  lock_access! unless access_locked?
106
107
  else
@@ -109,16 +110,21 @@ module Devise
109
110
  false
110
111
  end
111
112
  end
113
+
114
+ def increment_failed_attempts
115
+ self.class.increment_counter(:failed_attempts, id)
116
+ reload
117
+ end
112
118
 
113
119
  def unauthenticated_message
114
120
  # If set to paranoid mode, do not show the locked message because it
115
121
  # leaks the existence of an account.
116
122
  if Devise.paranoid
117
123
  super
118
- elsif lock_strategy_enabled?(:failed_attempts) && last_attempt?
119
- :last_attempt
120
- elsif lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
124
+ elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
121
125
  :locked
126
+ elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning
127
+ :last_attempt
122
128
  else
123
129
  super
124
130
  end
@@ -155,6 +161,9 @@ module Devise
155
161
  end
156
162
 
157
163
  module ClassMethods
164
+ # List of strategies that are enabled/supported if :both is used.
165
+ BOTH_STRATEGIES = [:time, :email]
166
+
158
167
  # Attempt to find a user by its unlock keys. If a record is found, send new
159
168
  # unlock instructions to it. If not user is found, returns a new user
160
169
  # with an email not found error.
@@ -181,7 +190,8 @@ module Devise
181
190
 
182
191
  # Is the unlock enabled for the given unlock strategy?
183
192
  def unlock_strategy_enabled?(strategy)
184
- [:both, strategy].include?(self.unlock_strategy)
193
+ self.unlock_strategy == strategy ||
194
+ (self.unlock_strategy == :both && BOTH_STRATEGIES.include?(strategy))
185
195
  end
186
196
 
187
197
  # Is the lock enabled for the given lock strategy?
@@ -189,7 +199,7 @@ module Devise
189
199
  self.lock_strategy == strategy
190
200
  end
191
201
 
192
- 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)
193
203
  end
194
204
  end
195
205
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/omniauth'
2
4
 
3
5
  module Devise
@@ -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
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/rememberable'
2
4
  require 'devise/hooks/rememberable'
3
5
  require 'devise/hooks/forgetable'
4
6
 
5
7
  module Devise
6
8
  module Models
7
- # Rememberable manages generating and clearing token for remember the user
9
+ # Rememberable manages generating and clearing token for remembering the user
8
10
  # from a saved cookie. Rememberable also has utility methods for dealing
9
11
  # with serializing the user into the cookie and back from the cookie, trying
10
12
  # to lookup the record based on the saved information.
@@ -39,17 +41,15 @@ module Devise
39
41
  module Rememberable
40
42
  extend ActiveSupport::Concern
41
43
 
42
- attr_accessor :remember_me, :extend_remember_period
44
+ attr_accessor :remember_me
43
45
 
44
46
  def self.required_fields(klass)
45
47
  [:remember_created_at]
46
48
  end
47
49
 
48
- # Generate a new remember token and save the record without validations
49
- # unless remember_across_browsers is true and the user already has a valid token.
50
- def remember_me!(extend_period=false)
51
- self.remember_token = self.class.remember_token if generate_remember_token?
52
- self.remember_created_at = Time.now.utc if generate_remember_timestamp?(extend_period)
50
+ def remember_me!
51
+ self.remember_token ||= self.class.remember_token if respond_to?(:remember_token)
52
+ self.remember_created_at ||= Time.now.utc
53
53
  save(validate: false) if self.changed?
54
54
  end
55
55
 
@@ -57,28 +57,26 @@ module Devise
57
57
  # it exists), and save the record without validations.
58
58
  def forget_me!
59
59
  return unless persisted?
60
- self.remember_token = nil if respond_to?(:remember_token=)
61
- self.remember_created_at = nil
60
+ self.remember_token = nil if respond_to?(:remember_token)
61
+ self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out
62
62
  save(validate: false)
63
63
  end
64
64
 
65
- # Remember token should be expired if expiration time not overpass now.
66
- def remember_expired?
67
- remember_created_at.nil? || (remember_expires_at <= Time.now.utc)
65
+ def remember_expires_at
66
+ self.class.remember_for.from_now
68
67
  end
69
68
 
70
- # Remember token expires at created time + remember_for configuration
71
- def remember_expires_at
72
- remember_created_at + self.class.remember_for
69
+ def extend_remember_period
70
+ self.class.extend_remember_period
73
71
  end
74
72
 
75
73
  def rememberable_value
76
74
  if respond_to?(:remember_token)
77
75
  remember_token
78
- elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt)
76
+ elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt.presence)
79
77
  salt
80
78
  else
81
- raise "authenticable_salt returned nil for the #{self.class.name} model. " \
79
+ raise "authenticatable_salt returned nil for the #{self.class.name} model. " \
82
80
  "In order to use rememberable, you must ensure a password is always set " \
83
81
  "or have a remember_token column in your model or implement your own " \
84
82
  "rememberable_value in the model with custom logic."
@@ -89,29 +87,60 @@ module Devise
89
87
  self.class.rememberable_options
90
88
  end
91
89
 
92
- protected
90
+ # A callback initiated after successfully being remembered. This can be
91
+ # used to insert your own logic that is only run after the user is
92
+ # remembered.
93
+ #
94
+ # Example:
95
+ #
96
+ # def after_remembered
97
+ # self.update_attribute(:invite_code, nil)
98
+ # end
99
+ #
100
+ def after_remembered
101
+ end
102
+
103
+ def remember_me?(token, generated_at)
104
+ # TODO: Normalize the JSON type coercion along with the Timeoutable hook
105
+ # in a single place https://github.com/plataformatec/devise/blob/ffe9d6d406e79108cf32a2c6a1d0b3828849c40b/lib/devise/hooks/timeoutable.rb#L14-L18
106
+ if generated_at.is_a?(String)
107
+ generated_at = time_from_json(generated_at)
108
+ end
93
109
 
94
- def generate_remember_token? #:nodoc:
95
- respond_to?(:remember_token) && remember_expired?
110
+ # The token is only valid if:
111
+ # 1. we have a date
112
+ # 2. the current time does not pass the expiry period
113
+ # 3. the record has a remember_created_at date
114
+ # 4. the token date is bigger than the remember_created_at
115
+ # 5. the token matches
116
+ generated_at.is_a?(Time) &&
117
+ (self.class.remember_for.ago < generated_at) &&
118
+ (generated_at > (remember_created_at || Time.now).utc) &&
119
+ Devise.secure_compare(rememberable_value, token)
96
120
  end
97
121
 
98
- # Generate a timestamp if extend_remember_period is true, if no remember_token
99
- # exists, or if an existing remember token has expired.
100
- def generate_remember_timestamp?(extend_period) #:nodoc:
101
- extend_period || remember_created_at.nil? || remember_expired?
122
+ private
123
+
124
+ def time_from_json(value)
125
+ if value =~ /\A\d+\.\d+\Z/
126
+ Time.at(value.to_f)
127
+ else
128
+ Time.parse(value) rescue nil
129
+ end
102
130
  end
103
131
 
104
132
  module ClassMethods
105
133
  # Create the cookie key using the record id and remember_token
106
134
  def serialize_into_cookie(record)
107
- [record.to_key, record.rememberable_value]
135
+ [record.to_key, record.rememberable_value, Time.now.utc.to_f.to_s]
108
136
  end
109
137
 
110
138
  # Recreate the user based on the stored cookie
111
- def serialize_from_cookie(id, remember_token)
139
+ def serialize_from_cookie(*args)
140
+ id, token, generated_at = *args
141
+
112
142
  record = to_adapter.get(id)
113
- record if record && !record.remember_expired? &&
114
- Devise.secure_compare(record.rememberable_value, remember_token)
143
+ record if record && record.remember_me?(token, generated_at)
115
144
  end
116
145
 
117
146
  # Generate a token checking if one does not already exist in the database.
@@ -122,7 +151,7 @@ module Devise
122
151
  end
123
152
  end
124
153
 
125
- Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options)
154
+ Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options, :expire_all_remember_me_on_sign_out)
126
155
  end
127
156
  end
128
157
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/hooks/timeoutable'
2
4
 
3
5
  module Devise
@@ -26,7 +28,6 @@ module Devise
26
28
 
27
29
  # Checks whether the user session has expired based on configured time.
28
30
  def timedout?(last_access)
29
- return false if remember_exists_and_not_expired?
30
31
  !timeout_in.nil? && last_access && last_access <= timeout_in.ago
31
32
  end
32
33
 
@@ -36,11 +37,6 @@ module Devise
36
37
 
37
38
  private
38
39
 
39
- def remember_exists_and_not_expired?
40
- return false unless respond_to?(:remember_created_at) && respond_to?(:remember_expired?)
41
- remember_created_at && !remember_expired?
42
- end
43
-
44
40
  module ClassMethods
45
41
  Devise::Models.config(self, :timeout_in)
46
42
  end