devise 3.5.1 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +281 -1066
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +292 -97
  5. data/app/controllers/devise/confirmations_controller.rb +3 -1
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +8 -6
  7. data/app/controllers/devise/passwords_controller.rb +10 -7
  8. data/app/controllers/devise/registrations_controller.rb +39 -18
  9. data/app/controllers/devise/sessions_controller.rb +9 -7
  10. data/app/controllers/devise/unlocks_controller.rb +4 -2
  11. data/app/controllers/devise_controller.rb +25 -12
  12. data/app/helpers/devise_helper.rb +23 -18
  13. data/app/mailers/devise/mailer.rb +13 -3
  14. data/app/views/devise/confirmations/new.html.erb +2 -2
  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 +5 -5
  18. data/app/views/devise/passwords/new.html.erb +2 -2
  19. data/app/views/devise/registrations/edit.html.erb +9 -5
  20. data/app/views/devise/registrations/new.html.erb +4 -4
  21. data/app/views/devise/sessions/new.html.erb +4 -4
  22. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  23. data/app/views/devise/shared/_links.html.erb +8 -8
  24. data/app/views/devise/unlocks/new.html.erb +2 -2
  25. data/config/locales/en.yml +7 -2
  26. data/lib/devise/controllers/helpers.rb +42 -33
  27. data/lib/devise/controllers/rememberable.rb +11 -2
  28. data/lib/devise/controllers/scoped_views.rb +2 -0
  29. data/lib/devise/controllers/sign_in_out.rb +40 -21
  30. data/lib/devise/controllers/store_location.rb +25 -7
  31. data/lib/devise/controllers/url_helpers.rb +3 -1
  32. data/lib/devise/delegator.rb +2 -0
  33. data/lib/devise/encryptor.rb +6 -4
  34. data/lib/devise/failure_app.rb +84 -28
  35. data/lib/devise/hooks/activatable.rb +2 -0
  36. data/lib/devise/hooks/csrf_cleaner.rb +2 -0
  37. data/lib/devise/hooks/forgetable.rb +2 -0
  38. data/lib/devise/hooks/lockable.rb +4 -2
  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 +7 -7
  42. data/lib/devise/hooks/trackable.rb +2 -0
  43. data/lib/devise/mailers/helpers.rb +7 -4
  44. data/lib/devise/mapping.rb +3 -1
  45. data/lib/devise/models/authenticatable.rb +63 -33
  46. data/lib/devise/models/confirmable.rb +108 -35
  47. data/lib/devise/models/database_authenticatable.rb +102 -22
  48. data/lib/devise/models/lockable.rb +24 -6
  49. data/lib/devise/models/omniauthable.rb +2 -0
  50. data/lib/devise/models/recoverable.rb +34 -26
  51. data/lib/devise/models/registerable.rb +4 -0
  52. data/lib/devise/models/rememberable.rb +42 -26
  53. data/lib/devise/models/timeoutable.rb +2 -6
  54. data/lib/devise/models/trackable.rb +15 -1
  55. data/lib/devise/models/validatable.rb +10 -3
  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 +4 -5
  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/deprecated_constant_accessor.rb +39 -0
  66. data/lib/devise/rails/routes.rb +71 -51
  67. data/lib/devise/rails/warden_compat.rb +3 -10
  68. data/lib/devise/rails.rb +7 -16
  69. data/lib/devise/secret_key_finder.rb +27 -0
  70. data/lib/devise/strategies/authenticatable.rb +5 -3
  71. data/lib/devise/strategies/base.rb +2 -0
  72. data/lib/devise/strategies/database_authenticatable.rb +11 -4
  73. data/lib/devise/strategies/rememberable.rb +5 -6
  74. data/lib/devise/test/controller_helpers.rb +167 -0
  75. data/lib/devise/test/integration_helpers.rb +63 -0
  76. data/lib/devise/test_helpers.rb +7 -124
  77. data/lib/devise/time_inflector.rb +2 -0
  78. data/lib/devise/token_generator.rb +3 -41
  79. data/lib/devise/version.rb +3 -1
  80. data/lib/devise.rb +69 -46
  81. data/lib/generators/active_record/devise_generator.rb +46 -12
  82. data/lib/generators/active_record/templates/migration.rb +4 -2
  83. data/lib/generators/active_record/templates/migration_existing.rb +4 -2
  84. data/lib/generators/devise/controllers_generator.rb +3 -1
  85. data/lib/generators/devise/devise_generator.rb +5 -3
  86. data/lib/generators/devise/install_generator.rb +18 -5
  87. data/lib/generators/devise/orm_helpers.rb +10 -21
  88. data/lib/generators/devise/views_generator.rb +21 -11
  89. data/lib/generators/mongoid/devise_generator.rb +7 -5
  90. data/lib/generators/templates/README +9 -8
  91. data/lib/generators/templates/controllers/README +1 -1
  92. data/lib/generators/templates/controllers/confirmations_controller.rb +2 -0
  93. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +3 -1
  94. data/lib/generators/templates/controllers/passwords_controller.rb +2 -0
  95. data/lib/generators/templates/controllers/registrations_controller.rb +6 -4
  96. data/lib/generators/templates/controllers/sessions_controller.rb +4 -2
  97. data/lib/generators/templates/controllers/unlocks_controller.rb +2 -0
  98. data/lib/generators/templates/devise.rb +65 -23
  99. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  100. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  101. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  102. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  103. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  104. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +5 -1
  105. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +10 -2
  106. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +4 -1
  107. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +11 -3
  108. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +11 -3
  109. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +7 -2
  110. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +4 -1
  111. metadata +21 -306
  112. data/.gitignore +0 -10
  113. data/.travis.yml +0 -45
  114. data/.yardopts +0 -9
  115. data/CONTRIBUTING.md +0 -14
  116. data/Gemfile +0 -29
  117. data/Gemfile.lock +0 -191
  118. data/Rakefile +0 -36
  119. data/devise.gemspec +0 -29
  120. data/devise.png +0 -0
  121. data/gemfiles/Gemfile.rails-3.2-stable +0 -29
  122. data/gemfiles/Gemfile.rails-3.2-stable.lock +0 -169
  123. data/gemfiles/Gemfile.rails-4.0-stable +0 -29
  124. data/gemfiles/Gemfile.rails-4.0-stable.lock +0 -163
  125. data/gemfiles/Gemfile.rails-4.1-stable +0 -29
  126. data/gemfiles/Gemfile.rails-4.1-stable.lock +0 -169
  127. data/gemfiles/Gemfile.rails-4.2-stable +0 -29
  128. data/gemfiles/Gemfile.rails-4.2-stable.lock +0 -191
  129. data/script/cached-bundle +0 -49
  130. data/script/s3-put +0 -71
  131. data/test/controllers/custom_registrations_controller_test.rb +0 -40
  132. data/test/controllers/custom_strategy_test.rb +0 -62
  133. data/test/controllers/helpers_test.rb +0 -316
  134. data/test/controllers/inherited_controller_i18n_messages_test.rb +0 -51
  135. data/test/controllers/internal_helpers_test.rb +0 -129
  136. data/test/controllers/load_hooks_controller_test.rb +0 -19
  137. data/test/controllers/passwords_controller_test.rb +0 -31
  138. data/test/controllers/sessions_controller_test.rb +0 -103
  139. data/test/controllers/url_helpers_test.rb +0 -65
  140. data/test/delegator_test.rb +0 -19
  141. data/test/devise_test.rb +0 -107
  142. data/test/failure_app_test.rb +0 -298
  143. data/test/generators/active_record_generator_test.rb +0 -109
  144. data/test/generators/controllers_generator_test.rb +0 -48
  145. data/test/generators/devise_generator_test.rb +0 -39
  146. data/test/generators/install_generator_test.rb +0 -13
  147. data/test/generators/mongoid_generator_test.rb +0 -23
  148. data/test/generators/views_generator_test.rb +0 -96
  149. data/test/helpers/devise_helper_test.rb +0 -49
  150. data/test/integration/authenticatable_test.rb +0 -729
  151. data/test/integration/confirmable_test.rb +0 -324
  152. data/test/integration/database_authenticatable_test.rb +0 -95
  153. data/test/integration/http_authenticatable_test.rb +0 -105
  154. data/test/integration/lockable_test.rb +0 -239
  155. data/test/integration/omniauthable_test.rb +0 -133
  156. data/test/integration/recoverable_test.rb +0 -347
  157. data/test/integration/registerable_test.rb +0 -359
  158. data/test/integration/rememberable_test.rb +0 -176
  159. data/test/integration/timeoutable_test.rb +0 -189
  160. data/test/integration/trackable_test.rb +0 -92
  161. data/test/mailers/confirmation_instructions_test.rb +0 -115
  162. data/test/mailers/reset_password_instructions_test.rb +0 -96
  163. data/test/mailers/unlock_instructions_test.rb +0 -91
  164. data/test/mapping_test.rb +0 -134
  165. data/test/models/authenticatable_test.rb +0 -23
  166. data/test/models/confirmable_test.rb +0 -468
  167. data/test/models/database_authenticatable_test.rb +0 -249
  168. data/test/models/lockable_test.rb +0 -328
  169. data/test/models/omniauthable_test.rb +0 -7
  170. data/test/models/recoverable_test.rb +0 -228
  171. data/test/models/registerable_test.rb +0 -7
  172. data/test/models/rememberable_test.rb +0 -204
  173. data/test/models/serializable_test.rb +0 -49
  174. data/test/models/timeoutable_test.rb +0 -51
  175. data/test/models/trackable_test.rb +0 -41
  176. data/test/models/validatable_test.rb +0 -127
  177. data/test/models_test.rb +0 -144
  178. data/test/omniauth/config_test.rb +0 -57
  179. data/test/omniauth/url_helpers_test.rb +0 -54
  180. data/test/orm/active_record.rb +0 -10
  181. data/test/orm/mongoid.rb +0 -13
  182. data/test/parameter_sanitizer_test.rb +0 -81
  183. data/test/rails_app/Rakefile +0 -6
  184. data/test/rails_app/app/active_record/admin.rb +0 -6
  185. data/test/rails_app/app/active_record/shim.rb +0 -2
  186. data/test/rails_app/app/active_record/user.rb +0 -6
  187. data/test/rails_app/app/active_record/user_on_engine.rb +0 -7
  188. data/test/rails_app/app/active_record/user_on_main_app.rb +0 -7
  189. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  190. data/test/rails_app/app/controllers/admins_controller.rb +0 -11
  191. data/test/rails_app/app/controllers/application_controller.rb +0 -12
  192. data/test/rails_app/app/controllers/application_with_fake_engine.rb +0 -30
  193. data/test/rails_app/app/controllers/custom/registrations_controller.rb +0 -31
  194. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  195. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  196. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  197. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  198. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  199. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  200. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +0 -3
  201. data/test/rails_app/app/mailers/users/mailer.rb +0 -3
  202. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +0 -4
  203. data/test/rails_app/app/mongoid/admin.rb +0 -29
  204. data/test/rails_app/app/mongoid/shim.rb +0 -23
  205. data/test/rails_app/app/mongoid/user.rb +0 -39
  206. data/test/rails_app/app/mongoid/user_on_engine.rb +0 -39
  207. data/test/rails_app/app/mongoid/user_on_main_app.rb +0 -39
  208. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  209. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  210. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  211. data/test/rails_app/app/views/home/index.html.erb +0 -1
  212. data/test/rails_app/app/views/home/join.html.erb +0 -1
  213. data/test/rails_app/app/views/home/private.html.erb +0 -1
  214. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  215. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  216. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  217. data/test/rails_app/app/views/users/index.html.erb +0 -1
  218. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  219. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  220. data/test/rails_app/bin/bundle +0 -3
  221. data/test/rails_app/bin/rails +0 -4
  222. data/test/rails_app/bin/rake +0 -4
  223. data/test/rails_app/config/application.rb +0 -40
  224. data/test/rails_app/config/boot.rb +0 -14
  225. data/test/rails_app/config/database.yml +0 -18
  226. data/test/rails_app/config/environment.rb +0 -5
  227. data/test/rails_app/config/environments/development.rb +0 -30
  228. data/test/rails_app/config/environments/production.rb +0 -84
  229. data/test/rails_app/config/environments/test.rb +0 -41
  230. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  231. data/test/rails_app/config/initializers/devise.rb +0 -180
  232. data/test/rails_app/config/initializers/inflections.rb +0 -2
  233. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  234. data/test/rails_app/config/initializers/session_store.rb +0 -1
  235. data/test/rails_app/config/routes.rb +0 -122
  236. data/test/rails_app/config.ru +0 -4
  237. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  238. data/test/rails_app/db/schema.rb +0 -55
  239. data/test/rails_app/lib/shared_admin.rb +0 -17
  240. data/test/rails_app/lib/shared_user.rb +0 -29
  241. data/test/rails_app/lib/shared_user_without_omniauth.rb +0 -13
  242. data/test/rails_app/public/404.html +0 -26
  243. data/test/rails_app/public/422.html +0 -26
  244. data/test/rails_app/public/500.html +0 -26
  245. data/test/rails_app/public/favicon.ico +0 -0
  246. data/test/rails_test.rb +0 -9
  247. data/test/routes_test.rb +0 -264
  248. data/test/support/action_controller/record_identifier.rb +0 -10
  249. data/test/support/assertions.rb +0 -39
  250. data/test/support/helpers.rb +0 -73
  251. data/test/support/integration.rb +0 -92
  252. data/test/support/locale/en.yml +0 -8
  253. data/test/support/mongoid.yml +0 -6
  254. data/test/support/webrat/integrations/rails.rb +0 -24
  255. data/test/test_helper.rb +0 -34
  256. data/test/test_helpers_test.rb +0 -178
  257. data/test/test_models.rb +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  # Confirmable is responsible to verify if an account is already confirmed to
@@ -7,7 +9,7 @@ module Devise
7
9
  #
8
10
  # Confirmable tracks the following columns:
9
11
  #
10
- # * confirmation_token - An OpenSSL::HMAC.hexdigest of @raw_confirmation_token
12
+ # * confirmation_token - A unique random token
11
13
  # * confirmed_at - A timestamp when the user clicked the confirmation link
12
14
  # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
13
15
  # * unconfirmed_email - An email address copied from the email attr. After confirmation
@@ -24,11 +26,15 @@ module Devise
24
26
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
25
27
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
26
28
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
27
- # db field to be setup (t.reconfirmable in migrations). Until confirmed new email is
29
+ # db field to be set up (t.reconfirmable in migrations). Until confirmed, new email is
28
30
  # stored in unconfirmed email column, and copied to email column on successful
29
- # confirmation.
31
+ # confirmation. Also, when used in conjunction with `send_email_changed_notification`,
32
+ # the notification is sent to the original email when the change is requested,
33
+ # not when the unconfirmed email is confirmed.
30
34
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
31
35
  # You can use this to force the user to confirm within a set period of time.
36
+ # Confirmable will not generate a new token if a repeat confirmation is requested
37
+ # during this time frame, unless the user's email changed too.
32
38
  #
33
39
  # == Examples
34
40
  #
@@ -38,17 +44,23 @@ module Devise
38
44
  #
39
45
  module Confirmable
40
46
  extend ActiveSupport::Concern
41
- include ActionView::Helpers::DateHelper
42
47
 
43
48
  included do
44
49
  before_create :generate_confirmation_token, if: :confirmation_required?
45
- after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
50
+ after_create :skip_reconfirmation_in_callback!, if: :send_confirmation_notification?
51
+ if defined?(ActiveRecord) && self < ActiveRecord::Base # ActiveRecord
52
+ after_commit :send_on_create_confirmation_instructions, on: :create, if: :send_confirmation_notification?
53
+ after_commit :send_reconfirmation_instructions, on: :update, if: :reconfirmation_required?
54
+ else # Mongoid
55
+ after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
56
+ after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
57
+ end
46
58
  before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
47
- after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
48
59
  end
49
60
 
50
61
  def initialize(*args, &block)
51
62
  @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
52
64
  @reconfirmation_required = false
53
65
  @skip_confirmation_notification = false
54
66
  @raw_confirmation_token = nil
@@ -64,7 +76,7 @@ module Devise
64
76
  # Confirm a user by setting it's confirmed_at to actual time. If the user
65
77
  # is already confirmed, add an error to email field. If the user is invalid
66
78
  # add errors
67
- def confirm(args={})
79
+ def confirm(args = {})
68
80
  pending_any_confirmation do
69
81
  if confirmation_period_expired?
70
82
  self.errors.add(:email, :confirmation_period_expired,
@@ -74,7 +86,7 @@ module Devise
74
86
 
75
87
  self.confirmed_at = Time.now.utc
76
88
 
77
- saved = if self.class.reconfirmable && unconfirmed_email.present?
89
+ saved = if pending_reconfirmation?
78
90
  skip_reconfirmation!
79
91
  self.email = unconfirmed_email
80
92
  self.unconfirmed_email = nil
@@ -90,11 +102,6 @@ module Devise
90
102
  end
91
103
  end
92
104
 
93
- def confirm!(args={})
94
- ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
95
- confirm(args)
96
- end
97
-
98
105
  # Verifies whether a user is confirmed or not
99
106
  def confirmed?
100
107
  !!confirmed_at
@@ -163,6 +170,12 @@ module Devise
163
170
 
164
171
  protected
165
172
 
173
+ # To not require reconfirmation after creating with #save called in a
174
+ # callback call skip_create_confirmation!
175
+ def skip_reconfirmation_in_callback!
176
+ @skip_reconfirmation_in_callback = true
177
+ end
178
+
166
179
  # A callback method used to deliver confirmation
167
180
  # instructions on creation. This can be overridden
168
181
  # in models to map to a nice sign up e-mail.
@@ -178,7 +191,7 @@ module Devise
178
191
  # Checks if the confirmation for the user is within the limit time.
179
192
  # We do this by calculating if the difference between today and the
180
193
  # confirmation sent date does not exceed the confirm in time configured.
181
- # Confirm_within is a model configuration, must always be an integer value.
194
+ # allow_unconfirmed_access_for is a model configuration, must always be an integer value.
182
195
  #
183
196
  # Example:
184
197
  #
@@ -198,7 +211,10 @@ module Devise
198
211
  # confirmation_period_valid? # will always return true
199
212
  #
200
213
  def confirmation_period_valid?
201
- self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
214
+ return true if self.class.allow_unconfirmed_access_for.nil?
215
+ return false if self.class.allow_unconfirmed_access_for == 0.days
216
+
217
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
202
218
  end
203
219
 
204
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -214,7 +230,7 @@ module Devise
214
230
  # confirmation_period_expired? # will always return false
215
231
  #
216
232
  def confirmation_period_expired?
217
- self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
233
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now.utc > self.confirmation_sent_at.utc + self.class.confirm_within)
218
234
  end
219
235
 
220
236
  # Checks whether the record requires any confirmation.
@@ -230,37 +246,76 @@ module Devise
230
246
  # Generates a new random token for confirmation, and stores
231
247
  # the time this token is being generated in confirmation_sent_at
232
248
  def generate_confirmation_token
233
- raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
234
- @raw_confirmation_token = raw
235
- self.confirmation_token = enc
236
- self.confirmation_sent_at = Time.now.utc
249
+ if self.confirmation_token && !confirmation_period_expired?
250
+ @raw_confirmation_token = self.confirmation_token
251
+ else
252
+ self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
253
+ self.confirmation_sent_at = Time.now.utc
254
+ end
237
255
  end
238
256
 
239
257
  def generate_confirmation_token!
240
258
  generate_confirmation_token && save(validate: false)
241
259
  end
242
260
 
243
- def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
244
- @reconfirmation_required = true
245
- self.unconfirmed_email = self.email
246
- self.email = self.email_was
247
- generate_confirmation_token
261
+ if Devise.activerecord51?
262
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
263
+ @reconfirmation_required = true
264
+ self.unconfirmed_email = self.email
265
+ self.email = self.email_in_database
266
+ self.confirmation_token = nil
267
+ generate_confirmation_token
268
+ end
269
+ else
270
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
271
+ @reconfirmation_required = true
272
+ self.unconfirmed_email = self.email
273
+ self.email = self.email_was
274
+ self.confirmation_token = nil
275
+ generate_confirmation_token
276
+ end
248
277
  end
249
278
 
250
- def postpone_email_change?
251
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
252
- @bypass_confirmation_postpone = false
253
- postpone
279
+ if Devise.activerecord51?
280
+ def postpone_email_change?
281
+ postpone = self.class.reconfirmable &&
282
+ will_save_change_to_email? &&
283
+ !@bypass_confirmation_postpone &&
284
+ self.email.present? &&
285
+ (!@skip_reconfirmation_in_callback || !self.email_in_database.nil?)
286
+ @bypass_confirmation_postpone = false
287
+ postpone
288
+ end
289
+ else
290
+ def postpone_email_change?
291
+ postpone = self.class.reconfirmable &&
292
+ email_changed? &&
293
+ !@bypass_confirmation_postpone &&
294
+ self.email.present? &&
295
+ (!@skip_reconfirmation_in_callback || !self.email_was.nil?)
296
+ @bypass_confirmation_postpone = false
297
+ postpone
298
+ end
254
299
  end
255
300
 
256
301
  def reconfirmation_required?
257
- self.class.reconfirmable && @reconfirmation_required && self.email.present?
302
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
258
303
  end
259
304
 
260
305
  def send_confirmation_notification?
261
306
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
262
307
  end
263
308
 
309
+ # With reconfirmable, notify the original email when the user first
310
+ # requests the email change, instead of when the change is confirmed.
311
+ def send_email_changed_notification?
312
+ if self.class.reconfirmable
313
+ self.class.send_email_changed_notification && reconfirmation_required?
314
+ else
315
+ super
316
+ end
317
+ end
318
+
264
319
  # A callback initiated after successfully confirming. This can be
265
320
  # used to insert your own logic that is only run after the user successfully
266
321
  # confirms.
@@ -279,7 +334,7 @@ module Devise
279
334
  # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
280
335
  # field. If no user is found, returns a new user with an email not found error.
281
336
  # Options must contain the user email
282
- def send_confirmation_instructions(attributes={})
337
+ def send_confirmation_instructions(attributes = {})
283
338
  confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
284
339
  unless confirmable.try(:persisted?)
285
340
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
@@ -293,17 +348,35 @@ module Devise
293
348
  # If the user is already confirmed, create an error for the user
294
349
  # Options must have the confirmation_token
295
350
  def confirm_by_token(confirmation_token)
296
- original_token = confirmation_token
297
- confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
351
+ # When the `confirmation_token` parameter is blank, if there are any users with a blank
352
+ # `confirmation_token` in the database, the first one would be confirmed here.
353
+ # The error is being manually added here to ensure no users are confirmed by mistake.
354
+ # This was done in the model for convenience, since validation errors are automatically
355
+ # displayed in the view.
356
+ if confirmation_token.blank?
357
+ confirmable = new
358
+ confirmable.errors.add(:confirmation_token, :blank)
359
+ return confirmable
360
+ end
361
+
362
+ confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
363
+
364
+ unless confirmable
365
+ confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
366
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
367
+ end
368
+
369
+ # TODO: replace above lines with
370
+ # confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
371
+ # after enough time has passed that Devise clients do not use digested tokens
298
372
 
299
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
300
373
  confirmable.confirm if confirmable.persisted?
301
- confirmable.confirmation_token = original_token
302
374
  confirmable
303
375
  end
304
376
 
305
377
  # Find a record for confirmation by unconfirmed email field
306
378
  def find_by_unconfirmed_email_with_errors(attributes = {})
379
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
307
380
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
308
381
  unconfirmed_attributes = attributes.symbolize_keys
309
382
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
@@ -1,25 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/database_authenticatable'
2
- require 'devise/encryptor'
3
4
 
4
5
  module Devise
5
- def self.bcrypt(klass, password)
6
- ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
7
- Devise::Encryptor.digest(klass, password)
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.
9
+ #
10
+ # This module defines a `password=` method. This method will hash the argument
11
+ # and store it in the `encrypted_password` column, bypassing any pre-existing
12
+ # `password` column if it exists.
13
13
  #
14
14
  # == Options
15
15
  #
16
- # DatabaseAuthenticable adds the following options to devise_for:
16
+ # DatabaseAuthenticatable adds the following options to devise_for:
17
17
  #
18
18
  # * +pepper+: a random string used to provide a more secure hash. Use
19
- # `rake secret` to generate new keys.
19
+ # `rails secret` to generate new keys.
20
20
  #
21
21
  # * +stretches+: the cost given to bcrypt.
22
22
  #
23
+ # * +send_email_changed_notification+: notify original email when it changes.
24
+ #
25
+ # * +send_password_change_notification+: notify email when password changes.
26
+ #
23
27
  # == Examples
24
28
  #
25
29
  # User.find(1).valid_password?('password123') # returns true/false
@@ -28,15 +32,36 @@ module Devise
28
32
  extend ActiveSupport::Concern
29
33
 
30
34
  included do
35
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
36
+ after_update :send_password_change_notification, if: :send_password_change_notification?
37
+
31
38
  attr_reader :password, :current_password
32
39
  attr_accessor :password_confirmation
33
40
  end
34
41
 
42
+ def initialize(*args, &block)
43
+ @skip_email_changed_notification = false
44
+ @skip_password_change_notification = false
45
+ super
46
+ end
47
+
48
+ # Skips sending the email changed notification after_update
49
+ def skip_email_changed_notification!
50
+ @skip_email_changed_notification = true
51
+ end
52
+
53
+ # Skips sending the password change notification after_update
54
+ def skip_password_change_notification!
55
+ @skip_password_change_notification = true
56
+ end
57
+
35
58
  def self.required_fields(klass)
36
59
  [:encrypted_password] + klass.authentication_keys
37
60
  end
38
61
 
39
- # Generates password encryption based on the given value.
62
+ # Generates a hashed password based on the given value.
63
+ # For legacy reasons, we use `encrypted_password` to store
64
+ # the hashed password.
40
65
  def password=(new_password)
41
66
  @password = new_password
42
67
  self.encrypted_password = password_digest(@password) if @password.present?
@@ -60,6 +85,15 @@ module Devise
60
85
  # their password). In case the password field is rejected, the confirmation
61
86
  # is also rejected as long as it is also blank.
62
87
  def update_with_password(params, *options)
88
+ if options.present?
89
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
90
+ [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
91
+ (`options`) is deprecated and it will be removed in the next major version.
92
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
93
+ from your code.
94
+ DEPRECATION
95
+ end
96
+
63
97
  current_password = params.delete(:current_password)
64
98
 
65
99
  if params[:password].blank?
@@ -68,11 +102,11 @@ module Devise
68
102
  end
69
103
 
70
104
  result = if valid_password?(current_password)
71
- update_attributes(params, *options)
105
+ update(params, *options)
72
106
  else
73
- self.assign_attributes(params, *options)
74
- self.valid?
75
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
107
+ assign_attributes(params, *options)
108
+ valid?
109
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
76
110
  false
77
111
  end
78
112
 
@@ -93,10 +127,19 @@ module Devise
93
127
  # end
94
128
  #
95
129
  def update_without_password(params, *options)
130
+ if options.present?
131
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
132
+ [Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
133
+ (`options`) is deprecated and it will be removed in the next major version.
134
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
135
+ from your code.
136
+ DEPRECATION
137
+ end
138
+
96
139
  params.delete(:password)
97
140
  params.delete(:password_confirmation)
98
141
 
99
- result = update_attributes(params, *options)
142
+ result = update(params, *options)
100
143
  clean_up_passwords
101
144
  result
102
145
  end
@@ -108,8 +151,8 @@ module Devise
108
151
  result = if valid_password?(current_password)
109
152
  destroy
110
153
  else
111
- self.valid?
112
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
154
+ valid?
155
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
113
156
  false
114
157
  end
115
158
 
@@ -134,19 +177,56 @@ module Devise
134
177
  encrypted_password[0,29] if encrypted_password
135
178
  end
136
179
 
180
+ if Devise.activerecord51?
181
+ # Send notification to user when email changes.
182
+ def send_email_changed_notification
183
+ send_devise_notification(:email_changed, to: email_before_last_save)
184
+ end
185
+ else
186
+ # Send notification to user when email changes.
187
+ def send_email_changed_notification
188
+ send_devise_notification(:email_changed, to: email_was)
189
+ end
190
+ end
191
+
192
+ # Send notification to user when password changes.
193
+ def send_password_change_notification
194
+ send_devise_notification(:password_change)
195
+ end
196
+
137
197
  protected
138
198
 
139
- # Digests the password using bcrypt. Custom encryption should override
199
+ # Hashes the password using bcrypt. Custom hash functions should override
140
200
  # this method to apply their own algorithm.
141
201
  #
142
- # See https://github.com/plataformatec/devise-encryptable for examples
143
- # of other encryption engines.
202
+ # See https://github.com/heartcombo/devise-encryptable for examples
203
+ # of other hashing engines.
144
204
  def password_digest(password)
145
205
  Devise::Encryptor.digest(self.class, password)
146
206
  end
147
207
 
208
+ if Devise.activerecord51?
209
+ def send_email_changed_notification?
210
+ self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
211
+ end
212
+ else
213
+ def send_email_changed_notification?
214
+ self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
215
+ end
216
+ end
217
+
218
+ if Devise.activerecord51?
219
+ def send_password_change_notification?
220
+ self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
221
+ end
222
+ else
223
+ def send_password_change_notification?
224
+ self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
225
+ end
226
+ end
227
+
148
228
  module ClassMethods
149
- Devise::Models.config(self, :pepper, :stretches)
229
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
150
230
 
151
231
  # We assume this method already gets the sanitized values from the
152
232
  # 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
  #
@@ -55,6 +57,14 @@ module Devise
55
57
  save(validate: false)
56
58
  end
57
59
 
60
+ # Resets failed attempts counter to 0.
61
+ def reset_failed_attempts!
62
+ if respond_to?(:failed_attempts) && !failed_attempts.to_i.zero?
63
+ self.failed_attempts = 0
64
+ save(validate: false)
65
+ end
66
+ end
67
+
58
68
  # Verifies whether a user is locked or not.
59
69
  def access_locked?
60
70
  !!locked_at && !lock_expired?
@@ -64,7 +74,7 @@ module Devise
64
74
  def send_unlock_instructions
65
75
  raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
66
76
  self.unlock_token = enc
67
- self.save(validate: false)
77
+ save(validate: false)
68
78
  send_devise_notification(:unlock_instructions, raw, {})
69
79
  raw
70
80
  end
@@ -99,8 +109,7 @@ module Devise
99
109
  if super && !access_locked?
100
110
  true
101
111
  else
102
- self.failed_attempts ||= 0
103
- self.failed_attempts += 1
112
+ increment_failed_attempts
104
113
  if attempts_exceeded?
105
114
  lock_access! unless access_locked?
106
115
  else
@@ -110,6 +119,11 @@ module Devise
110
119
  end
111
120
  end
112
121
 
122
+ def increment_failed_attempts
123
+ self.class.increment_counter(:failed_attempts, id)
124
+ reload
125
+ end
126
+
113
127
  def unauthenticated_message
114
128
  # If set to paranoid mode, do not show the locked message because it
115
129
  # leaks the existence of an account.
@@ -155,11 +169,14 @@ module Devise
155
169
  end
156
170
 
157
171
  module ClassMethods
172
+ # List of strategies that are enabled/supported if :both is used.
173
+ BOTH_STRATEGIES = [:time, :email]
174
+
158
175
  # Attempt to find a user by its unlock keys. If a record is found, send new
159
176
  # unlock instructions to it. If not user is found, returns a new user
160
177
  # with an email not found error.
161
178
  # Options must contain the user's unlock keys
162
- def send_unlock_instructions(attributes={})
179
+ def send_unlock_instructions(attributes = {})
163
180
  lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
164
181
  lockable.resend_unlock_instructions if lockable.persisted?
165
182
  lockable
@@ -181,7 +198,8 @@ module Devise
181
198
 
182
199
  # Is the unlock enabled for the given unlock strategy?
183
200
  def unlock_strategy_enabled?(strategy)
184
- [:both, strategy].include?(self.unlock_strategy)
201
+ self.unlock_strategy == strategy ||
202
+ (self.unlock_strategy == :both && BOTH_STRATEGIES.include?(strategy))
185
203
  end
186
204
 
187
205
  # Is the lock enabled for the given lock strategy?
@@ -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
 
@@ -16,10 +18,6 @@ module Devise
16
18
  # # resets the user password and save the record, true if valid passwords are given, otherwise false
17
19
  # User.find(1).reset_password('password123', 'password123')
18
20
  #
19
- # # only resets the user password, without saving the record
20
- # user = User.find(1)
21
- # user.reset_password('password123', 'password123')
22
- #
23
21
  # # creates a new token and send it with instructions about how to reset the password
24
22
  # User.find(1).send_reset_password_instructions
25
23
  #
@@ -31,30 +29,20 @@ module Devise
31
29
  end
32
30
 
33
31
  included do
34
- before_save do
35
- if email_changed? || encrypted_password_changed?
36
- clear_reset_password_token
37
- end
38
- end
32
+ before_update :clear_reset_password_token, if: :clear_reset_password_token?
39
33
  end
40
34
 
41
35
  # Update password saving the record and clearing token. Returns true if
42
36
  # the passwords are valid and the record was saved, false otherwise.
43
37
  def reset_password(new_password, new_password_confirmation)
44
- self.password = new_password
45
- self.password_confirmation = new_password_confirmation
46
-
47
- if respond_to?(:after_password_reset) && valid?
48
- ActiveSupport::Deprecation.warn "after_password_reset is deprecated"
49
- after_password_reset
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
50
45
  end
51
-
52
- save
53
- end
54
-
55
- def reset_password!(new_password, new_password_confirmation)
56
- ActiveSupport::Deprecation.warn "reset_password! is deprecated in favor of reset_password"
57
- reset_password(new_password, new_password_confirmation)
58
46
  end
59
47
 
60
48
  # Resets reset password token and send reset password instructions by email.
@@ -87,7 +75,7 @@ module Devise
87
75
  # reset_password_period_valid? # will always return false
88
76
  #
89
77
  def reset_password_period_valid?
90
- 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
91
79
  end
92
80
 
93
81
  protected
@@ -103,7 +91,7 @@ module Devise
103
91
 
104
92
  self.reset_password_token = enc
105
93
  self.reset_password_sent_at = Time.now.utc
106
- self.save(validate: false)
94
+ save(validate: false)
107
95
  raw
108
96
  end
109
97
 
@@ -111,6 +99,26 @@ module Devise
111
99
  send_devise_notification(:reset_password_instructions, token, {})
112
100
  end
113
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
120
+ end
121
+
114
122
  module ClassMethods
115
123
  # Attempt to find a user by password reset token. If a user is found, return it
116
124
  # If a user is not found, return nil
@@ -123,7 +131,7 @@ module Devise
123
131
  # password instructions to it. If user is not found, returns a new user
124
132
  # with an email not found error.
125
133
  # Attributes must contain the user's email
126
- def send_reset_password_instructions(attributes={})
134
+ def send_reset_password_instructions(attributes = {})
127
135
  recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
128
136
  recoverable.send_reset_password_instructions if recoverable.persisted?
129
137
  recoverable
@@ -134,7 +142,7 @@ module Devise
134
142
  # try saving the record. If not user is found, returns a new user
135
143
  # containing an error in reset_password_token attribute.
136
144
  # Attributes must contain reset_password_token, password and confirmation
137
- def reset_password_by_token(attributes={})
145
+ def reset_password_by_token(attributes = {})
138
146
  original_token = attributes[:reset_password_token]
139
147
  reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
140
148