devise 3.3.0 → 3.5.10

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +29 -20
  3. data/CHANGELOG.md +219 -102
  4. data/CODE_OF_CONDUCT.md +22 -0
  5. data/CONTRIBUTING.md +2 -0
  6. data/Gemfile +3 -2
  7. data/Gemfile.lock +101 -80
  8. data/MIT-LICENSE +1 -1
  9. data/README.md +87 -43
  10. data/Rakefile +2 -1
  11. data/app/controllers/devise/confirmations_controller.rb +5 -1
  12. data/app/controllers/devise/omniauth_callbacks_controller.rb +4 -0
  13. data/app/controllers/devise/passwords_controller.rb +14 -4
  14. data/app/controllers/devise/registrations_controller.rb +10 -11
  15. data/app/controllers/devise/sessions_controller.rb +7 -2
  16. data/app/controllers/devise/unlocks_controller.rb +3 -0
  17. data/app/controllers/devise_controller.rb +44 -21
  18. data/app/mailers/devise/mailer.rb +4 -0
  19. data/app/views/devise/confirmations/new.html.erb +7 -3
  20. data/app/views/devise/mailer/password_change.html.erb +3 -0
  21. data/app/views/devise/passwords/edit.html.erb +14 -5
  22. data/app/views/devise/passwords/new.html.erb +7 -3
  23. data/app/views/devise/registrations/edit.html.erb +19 -9
  24. data/app/views/devise/registrations/new.html.erb +18 -7
  25. data/app/views/devise/sessions/new.html.erb +15 -6
  26. data/app/views/devise/shared/{_links.erb → _links.html.erb} +1 -1
  27. data/app/views/devise/unlocks/new.html.erb +7 -3
  28. data/config/locales/en.yml +4 -2
  29. data/devise.gemspec +2 -2
  30. data/gemfiles/Gemfile.rails-3.2-stable.lock +54 -48
  31. data/gemfiles/Gemfile.rails-4.0-stable +1 -0
  32. data/gemfiles/Gemfile.rails-4.0-stable.lock +63 -59
  33. data/gemfiles/{Gemfile.rails-head → Gemfile.rails-4.1-stable} +3 -5
  34. data/gemfiles/Gemfile.rails-4.1-stable.lock +171 -0
  35. data/gemfiles/Gemfile.rails-4.2-stable +30 -0
  36. data/gemfiles/Gemfile.rails-4.2-stable.lock +193 -0
  37. data/lib/devise/controllers/helpers.rb +12 -6
  38. data/lib/devise/controllers/rememberable.rb +9 -2
  39. data/lib/devise/controllers/sign_in_out.rb +2 -8
  40. data/lib/devise/controllers/store_location.rb +3 -1
  41. data/lib/devise/controllers/url_helpers.rb +7 -9
  42. data/lib/devise/encryptor.rb +22 -0
  43. data/lib/devise/failure_app.rb +56 -14
  44. data/lib/devise/hooks/timeoutable.rb +5 -7
  45. data/lib/devise/mapping.rb +2 -1
  46. data/lib/devise/models/authenticatable.rb +28 -28
  47. data/lib/devise/models/confirmable.rb +51 -17
  48. data/lib/devise/models/database_authenticatable.rb +17 -11
  49. data/lib/devise/models/lockable.rb +7 -3
  50. data/lib/devise/models/recoverable.rb +23 -15
  51. data/lib/devise/models/rememberable.rb +56 -22
  52. data/lib/devise/models/timeoutable.rb +0 -6
  53. data/lib/devise/models/trackable.rb +1 -2
  54. data/lib/devise/models/validatable.rb +3 -3
  55. data/lib/devise/models.rb +1 -1
  56. data/lib/devise/rails/routes.rb +33 -27
  57. data/lib/devise/rails.rb +1 -1
  58. data/lib/devise/strategies/authenticatable.rb +8 -6
  59. data/lib/devise/strategies/database_authenticatable.rb +2 -1
  60. data/lib/devise/strategies/rememberable.rb +13 -3
  61. data/lib/devise/test_helpers.rb +2 -2
  62. data/lib/devise/version.rb +1 -1
  63. data/lib/devise.rb +39 -37
  64. data/lib/generators/active_record/devise_generator.rb +2 -1
  65. data/lib/generators/active_record/templates/migration.rb +1 -1
  66. data/lib/generators/active_record/templates/migration_existing.rb +1 -1
  67. data/lib/generators/devise/controllers_generator.rb +44 -0
  68. data/lib/generators/devise/views_generator.rb +14 -3
  69. data/lib/generators/templates/controllers/README +14 -0
  70. data/lib/generators/templates/controllers/confirmations_controller.rb +28 -0
  71. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +28 -0
  72. data/lib/generators/templates/controllers/passwords_controller.rb +32 -0
  73. data/lib/generators/templates/controllers/registrations_controller.rb +60 -0
  74. data/lib/generators/templates/controllers/sessions_controller.rb +25 -0
  75. data/lib/generators/templates/controllers/unlocks_controller.rb +28 -0
  76. data/lib/generators/templates/devise.rb +19 -13
  77. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  78. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  79. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  80. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  81. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
  82. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +1 -1
  83. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +2 -2
  84. data/test/controllers/custom_registrations_controller_test.rb +6 -1
  85. data/test/controllers/helper_methods_test.rb +21 -0
  86. data/test/controllers/helpers_test.rb +5 -0
  87. data/test/controllers/inherited_controller_i18n_messages_test.rb +51 -0
  88. data/test/controllers/internal_helpers_test.rb +10 -4
  89. data/test/controllers/load_hooks_controller_test.rb +19 -0
  90. data/test/controllers/passwords_controller_test.rb +1 -1
  91. data/test/controllers/sessions_controller_test.rb +3 -3
  92. data/test/controllers/url_helpers_test.rb +6 -0
  93. data/test/devise_test.rb +3 -3
  94. data/test/failure_app_test.rb +47 -0
  95. data/test/generators/controllers_generator_test.rb +48 -0
  96. data/test/generators/views_generator_test.rb +8 -1
  97. data/test/helpers/devise_helper_test.rb +9 -12
  98. data/test/integration/authenticatable_test.rb +1 -1
  99. data/test/integration/database_authenticatable_test.rb +11 -0
  100. data/test/integration/http_authenticatable_test.rb +1 -1
  101. data/test/integration/omniauthable_test.rb +12 -10
  102. data/test/integration/recoverable_test.rb +13 -0
  103. data/test/integration/rememberable_test.rb +50 -3
  104. data/test/integration/timeoutable_test.rb +13 -18
  105. data/test/mailers/confirmation_instructions_test.rb +1 -1
  106. data/test/mapping_test.rb +7 -0
  107. data/test/models/authenticatable_test.rb +10 -0
  108. data/test/models/confirmable_test.rb +99 -42
  109. data/test/models/database_authenticatable_test.rb +20 -0
  110. data/test/models/lockable_test.rb +45 -17
  111. data/test/models/recoverable_test.rb +62 -7
  112. data/test/models/rememberable_test.rb +68 -97
  113. data/test/models/validatable_test.rb +5 -5
  114. data/test/models_test.rb +15 -6
  115. data/test/rails_app/app/active_record/user_without_email.rb +8 -0
  116. data/test/rails_app/app/controllers/admins_controller.rb +0 -5
  117. data/test/rails_app/app/controllers/custom/registrations_controller.rb +10 -0
  118. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +3 -0
  119. data/test/rails_app/app/mailers/users/mailer.rb +0 -9
  120. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +4 -0
  121. data/test/rails_app/app/mongoid/user_without_email.rb +33 -0
  122. data/test/rails_app/config/application.rb +1 -1
  123. data/test/rails_app/config/environments/production.rb +6 -2
  124. data/test/rails_app/config/environments/test.rb +7 -2
  125. data/test/rails_app/config/initializers/devise.rb +12 -15
  126. data/test/rails_app/config/routes.rb +6 -3
  127. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +2 -2
  128. data/test/rails_app/lib/shared_user.rb +1 -1
  129. data/test/rails_app/lib/shared_user_without_email.rb +26 -0
  130. data/test/rails_test.rb +9 -0
  131. data/test/support/helpers.rb +13 -6
  132. data/test/support/integration.rb +2 -2
  133. data/test/test_helper.rb +5 -0
  134. data/test/test_helpers_test.rb +22 -7
  135. data/test/test_models.rb +2 -2
  136. data/test/time_helpers.rb +137 -0
  137. metadata +58 -8
  138. data/gemfiles/Gemfile.rails-head.lock +0 -190
@@ -5,6 +5,14 @@ module Devise
5
5
  # Confirmation instructions are sent to the user email after creating a
6
6
  # record and when manually requested by a new confirmation instruction request.
7
7
  #
8
+ # Confirmable tracks the following columns:
9
+ #
10
+ # * confirmation_token - A unique random token
11
+ # * confirmed_at - A timestamp when the user clicked the confirmation link
12
+ # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
13
+ # * unconfirmed_email - An email address copied from the email attr. After confirmation
14
+ # this value is copied to the email attr then cleared
15
+ #
8
16
  # == Options
9
17
  #
10
18
  # Confirmable adds the following options to +devise+:
@@ -16,15 +24,17 @@ module Devise
16
24
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
17
25
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
18
26
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
19
- # db field to be setup (t.reconfirmable in migrations). Until confirmed new email is
27
+ # db field to be setup (t.reconfirmable in migrations). Until confirmed, new email is
20
28
  # stored in unconfirmed email column, and copied to email column on successful
21
29
  # confirmation.
22
30
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
23
31
  # You can use this to force the user to confirm within a set period of time.
32
+ # Confirmable will not generate a new token if a repeat confirmation is requested
33
+ # during this time frame, unless the user's email changed too.
24
34
  #
25
35
  # == Examples
26
36
  #
27
- # User.find(1).confirm! # returns true unless it's already confirmed
37
+ # User.find(1).confirm # returns true unless it's already confirmed
28
38
  # User.find(1).confirmed? # true/false
29
39
  # User.find(1).send_confirmation_instructions # manually send instructions
30
40
  #
@@ -56,7 +66,7 @@ module Devise
56
66
  # Confirm a user by setting it's confirmed_at to actual time. If the user
57
67
  # is already confirmed, add an error to email field. If the user is invalid
58
68
  # add errors
59
- def confirm!
69
+ def confirm(args={})
60
70
  pending_any_confirmation do
61
71
  if confirmation_period_expired?
62
72
  self.errors.add(:email, :confirmation_period_expired,
@@ -64,7 +74,6 @@ module Devise
64
74
  return false
65
75
  end
66
76
 
67
- self.confirmation_token = nil
68
77
  self.confirmed_at = Time.now.utc
69
78
 
70
79
  saved = if self.class.reconfirmable && unconfirmed_email.present?
@@ -75,7 +84,7 @@ module Devise
75
84
  # We need to validate in such cases to enforce e-mail uniqueness
76
85
  save(validate: true)
77
86
  else
78
- save(validate: false)
87
+ save(validate: args[:ensure_valid] == true)
79
88
  end
80
89
 
81
90
  after_confirmation if saved
@@ -83,6 +92,11 @@ module Devise
83
92
  end
84
93
  end
85
94
 
95
+ def confirm!(args={})
96
+ ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
97
+ confirm(args)
98
+ end
99
+
86
100
  # Verifies whether a user is confirmed or not
87
101
  def confirmed?
88
102
  !!confirmed_at
@@ -156,6 +170,7 @@ module Devise
156
170
  # in models to map to a nice sign up e-mail.
157
171
  def send_on_create_confirmation_instructions
158
172
  send_confirmation_instructions
173
+ skip_reconfirmation!
159
174
  end
160
175
 
161
176
  # Callback to overwrite if confirmation is required or not.
@@ -202,7 +217,7 @@ module Devise
202
217
  # confirmation_period_expired? # will always return false
203
218
  #
204
219
  def confirmation_period_expired?
205
- self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
220
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
206
221
  end
207
222
 
208
223
  # Checks whether the record requires any confirmation.
@@ -216,12 +231,15 @@ module Devise
216
231
  end
217
232
 
218
233
  # Generates a new random token for confirmation, and stores
219
- # the time this token is being generated
234
+ # the time this token is being generated in confirmation_sent_at
220
235
  def generate_confirmation_token
221
- raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
222
- @raw_confirmation_token = raw
223
- self.confirmation_token = enc
224
- self.confirmation_sent_at = Time.now.utc
236
+ if self.confirmation_token && !confirmation_period_expired?
237
+ @raw_confirmation_token = self.confirmation_token
238
+ else
239
+ raw, _ = Devise.token_generator.generate(self.class, :confirmation_token)
240
+ self.confirmation_token = @raw_confirmation_token = raw
241
+ self.confirmation_sent_at = Time.now.utc
242
+ end
225
243
  end
226
244
 
227
245
  def generate_confirmation_token!
@@ -232,6 +250,7 @@ module Devise
232
250
  @reconfirmation_required = true
233
251
  self.unconfirmed_email = self.email
234
252
  self.email = self.email_was
253
+ self.confirmation_token = nil
235
254
  generate_confirmation_token
236
255
  end
237
256
 
@@ -242,13 +261,23 @@ module Devise
242
261
  end
243
262
 
244
263
  def reconfirmation_required?
245
- self.class.reconfirmable && @reconfirmation_required && self.email.present?
264
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
246
265
  end
247
266
 
248
267
  def send_confirmation_notification?
249
268
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
250
269
  end
251
270
 
271
+ # A callback initiated after successfully confirming. This can be
272
+ # used to insert your own logic that is only run after the user successfully
273
+ # confirms.
274
+ #
275
+ # Example:
276
+ #
277
+ # def after_confirmation
278
+ # self.update_attribute(:invite_code, nil)
279
+ # end
280
+ #
252
281
  def after_confirmation
253
282
  end
254
283
 
@@ -271,12 +300,17 @@ module Devise
271
300
  # If the user is already confirmed, create an error for the user
272
301
  # Options must have the confirmation_token
273
302
  def confirm_by_token(confirmation_token)
274
- original_token = confirmation_token
275
- confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
303
+ confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
304
+ unless confirmable
305
+ confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
306
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
307
+ end
308
+
309
+ # TODO: replace above lines with
310
+ # confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
311
+ # after enough time has passed that Devise clients do not use digested tokens
276
312
 
277
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
278
- confirmable.confirm! if confirmable.persisted?
279
- confirmable.confirmation_token = original_token
313
+ confirmable.confirm if confirmable.persisted?
280
314
  confirmable
281
315
  end
282
316
 
@@ -1,10 +1,9 @@
1
1
  require 'devise/strategies/database_authenticatable'
2
- require 'bcrypt'
3
2
 
4
3
  module Devise
5
- # Digests the password using bcrypt.
6
4
  def self.bcrypt(klass, password)
7
- ::BCrypt::Password.create("#{password}#{klass.pepper}", cost: klass.stretches).to_s
5
+ ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
6
+ Devise::Encryptor.digest(klass, password)
8
7
  end
9
8
 
10
9
  module Models
@@ -13,7 +12,7 @@ module Devise
13
12
  #
14
13
  # == Options
15
14
  #
16
- # DatabaseAuthenticable adds the following options to devise_for:
15
+ # DatabaseAuthenticatable adds the following options to devise_for:
17
16
  #
18
17
  # * +pepper+: a random string used to provide a more secure hash. Use
19
18
  # `rake secret` to generate new keys.
@@ -28,6 +27,8 @@ module Devise
28
27
  extend ActiveSupport::Concern
29
28
 
30
29
  included do
30
+ after_update :send_password_change_notification, if: :send_password_change_notification?
31
+
31
32
  attr_reader :password, :current_password
32
33
  attr_accessor :password_confirmation
33
34
  end
@@ -42,12 +43,9 @@ module Devise
42
43
  self.encrypted_password = password_digest(@password) if @password.present?
43
44
  end
44
45
 
45
- # Verifies whether an password (ie from sign in) is the user password.
46
+ # Verifies whether a password (ie from sign in) is the user password.
46
47
  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)
48
+ Devise::Encryptor.compare(self.class, encrypted_password, password)
51
49
  end
52
50
 
53
51
  # Set password and password confirmation to nil
@@ -137,6 +135,10 @@ module Devise
137
135
  encrypted_password[0,29] if encrypted_password
138
136
  end
139
137
 
138
+ def send_password_change_notification
139
+ send_devise_notification(:password_change)
140
+ end
141
+
140
142
  protected
141
143
 
142
144
  # Digests the password using bcrypt. Custom encryption should override
@@ -145,11 +147,15 @@ module Devise
145
147
  # See https://github.com/plataformatec/devise-encryptable for examples
146
148
  # of other encryption engines.
147
149
  def password_digest(password)
148
- Devise.bcrypt(self.class, password)
150
+ Devise::Encryptor.digest(self.class, password)
151
+ end
152
+
153
+ def send_password_change_notification?
154
+ self.class.send_password_change_notification && encrypted_password_changed?
149
155
  end
150
156
 
151
157
  module ClassMethods
152
- Devise::Models.config(self, :pepper, :stretches)
158
+ Devise::Models.config(self, :pepper, :stretches, :send_password_change_notification)
153
159
 
154
160
  # We assume this method already gets the sanitized values from the
155
161
  # DatabaseAuthenticatable strategy. If you are using this method on
@@ -117,7 +117,7 @@ module Devise
117
117
  super
118
118
  elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
119
119
  :locked
120
- elsif lock_strategy_enabled?(:failed_attempts) && last_attempt?
120
+ elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning
121
121
  :last_attempt
122
122
  else
123
123
  super
@@ -155,6 +155,9 @@ module Devise
155
155
  end
156
156
 
157
157
  module ClassMethods
158
+ # List of strategies that are enabled/supported if :both is used.
159
+ BOTH_STRATEGIES = [:time, :email]
160
+
158
161
  # Attempt to find a user by its unlock keys. If a record is found, send new
159
162
  # unlock instructions to it. If not user is found, returns a new user
160
163
  # with an email not found error.
@@ -181,7 +184,8 @@ module Devise
181
184
 
182
185
  # Is the unlock enabled for the given unlock strategy?
183
186
  def unlock_strategy_enabled?(strategy)
184
- [:both, strategy].include?(self.unlock_strategy)
187
+ self.unlock_strategy == strategy ||
188
+ (self.unlock_strategy == :both && BOTH_STRATEGIES.include?(strategy))
185
189
  end
186
190
 
187
191
  # Is the lock enabled for the given lock strategy?
@@ -189,7 +193,7 @@ module Devise
189
193
  self.lock_strategy == strategy
190
194
  end
191
195
 
192
- Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
196
+ Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys, :last_attempt_warning)
193
197
  end
194
198
  end
195
199
  end
@@ -8,15 +8,13 @@ module Devise
8
8
  # Recoverable adds the following options to devise_for:
9
9
  #
10
10
  # * +reset_password_keys+: the keys you want to use when recovering the password for an account
11
+ # * +reset_password_within+: the time period within which the password must be reset or the token expires.
12
+ # * +sign_in_after_reset_password+: whether or not to sign in the user automatically after a password reset.
11
13
  #
12
14
  # == Examples
13
15
  #
14
16
  # # 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')
17
+ # User.find(1).reset_password('password123', 'password123')
20
18
  #
21
19
  # # creates a new token and send it with instructions about how to reset the password
22
20
  # User.find(1).send_reset_password_instructions
@@ -28,20 +26,33 @@ module Devise
28
26
  [:reset_password_sent_at, :reset_password_token]
29
27
  end
30
28
 
29
+ included do
30
+ before_update do
31
+ if (respond_to?(:email_changed?) && email_changed?) || encrypted_password_changed?
32
+ clear_reset_password_token
33
+ end
34
+ end
35
+ end
36
+
31
37
  # Update password saving the record and clearing token. Returns true if
32
38
  # the passwords are valid and the record was saved, false otherwise.
33
- def reset_password!(new_password, new_password_confirmation)
39
+ def reset_password(new_password, new_password_confirmation)
34
40
  self.password = new_password
35
41
  self.password_confirmation = new_password_confirmation
36
42
 
37
- if valid?
38
- clear_reset_password_token
43
+ if respond_to?(:after_password_reset) && valid?
44
+ ActiveSupport::Deprecation.warn "after_password_reset is deprecated"
39
45
  after_password_reset
40
46
  end
41
47
 
42
48
  save
43
49
  end
44
50
 
51
+ def reset_password!(new_password, new_password_confirmation)
52
+ ActiveSupport::Deprecation.warn "reset_password! is deprecated in favor of reset_password"
53
+ reset_password(new_password, new_password_confirmation)
54
+ end
55
+
45
56
  # Resets reset password token and send reset password instructions by email.
46
57
  # Returns the token sent in the e-mail.
47
58
  def send_reset_password_instructions
@@ -72,7 +83,7 @@ module Devise
72
83
  # reset_password_period_valid? # will always return false
73
84
  #
74
85
  def reset_password_period_valid?
75
- reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
86
+ reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago.utc
76
87
  end
77
88
 
78
89
  protected
@@ -83,9 +94,6 @@ module Devise
83
94
  self.reset_password_sent_at = nil
84
95
  end
85
96
 
86
- def after_password_reset
87
- end
88
-
89
97
  def set_reset_password_token
90
98
  raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
91
99
 
@@ -130,17 +138,17 @@ module Devise
130
138
 
131
139
  if recoverable.persisted?
132
140
  if recoverable.reset_password_period_valid?
133
- recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
141
+ recoverable.reset_password(attributes[:password], attributes[:password_confirmation])
134
142
  else
135
143
  recoverable.errors.add(:reset_password_token, :expired)
136
144
  end
137
145
  end
138
146
 
139
- recoverable.reset_password_token = original_token
147
+ recoverable.reset_password_token = original_token if recoverable.reset_password_token.present?
140
148
  recoverable
141
149
  end
142
150
 
143
- Devise::Models.config(self, :reset_password_keys, :reset_password_within)
151
+ Devise::Models.config(self, :reset_password_keys, :reset_password_within, :sign_in_after_reset_password)
144
152
  end
145
153
  end
146
154
  end
@@ -39,17 +39,17 @@ module Devise
39
39
  module Rememberable
40
40
  extend ActiveSupport::Concern
41
41
 
42
- attr_accessor :remember_me, :extend_remember_period
42
+ attr_accessor :remember_me
43
43
 
44
44
  def self.required_fields(klass)
45
45
  [:remember_created_at]
46
46
  end
47
47
 
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)
48
+ # TODO: We were used to receive a extend period argument but we no longer do.
49
+ # Remove this for Devise 4.0.
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,25 +57,28 @@ 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=)
60
+ self.remember_token = nil if respond_to?(:remember_token)
61
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
65
  # Remember token should be expired if expiration time not overpass now.
66
66
  def remember_expired?
67
- remember_created_at.nil? || (remember_expires_at <= Time.now.utc)
67
+ remember_created_at.nil?
68
68
  end
69
69
 
70
- # Remember token expires at created time + remember_for configuration
71
70
  def remember_expires_at
72
- remember_created_at + self.class.remember_for
71
+ self.class.remember_for.from_now
72
+ end
73
+
74
+ def extend_remember_period
75
+ self.class.extend_remember_period
73
76
  end
74
77
 
75
78
  def rememberable_value
76
79
  if respond_to?(:remember_token)
77
80
  remember_token
78
- elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt)
81
+ elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt.presence)
79
82
  salt
80
83
  else
81
84
  raise "authenticable_salt returned nil for the #{self.class.name} model. " \
@@ -89,29 +92,60 @@ module Devise
89
92
  self.class.rememberable_options
90
93
  end
91
94
 
92
- protected
95
+ # A callback initiated after successfully being remembered. This can be
96
+ # used to insert your own logic that is only run after the user is
97
+ # remembered.
98
+ #
99
+ # Example:
100
+ #
101
+ # def after_remembered
102
+ # self.update_attribute(:invite_code, nil)
103
+ # end
104
+ #
105
+ def after_remembered
106
+ end
107
+
108
+ def remember_me?(token, generated_at)
109
+ # TODO: Normalize the JSON type coercion along with the Timeoutable hook
110
+ # in a single place https://github.com/plataformatec/devise/blob/ffe9d6d406e79108cf32a2c6a1d0b3828849c40b/lib/devise/hooks/timeoutable.rb#L14-L18
111
+ if generated_at.is_a?(String)
112
+ generated_at = time_from_json(generated_at)
113
+ end
93
114
 
94
- def generate_remember_token? #:nodoc:
95
- respond_to?(:remember_token) && remember_expired?
115
+ # The token is only valid if:
116
+ # 1. we have a date
117
+ # 2. the current time does not pass the expiry period
118
+ # 3. the record has a remember_created_at date
119
+ # 4. the token date is bigger than the remember_created_at
120
+ # 5. the token matches
121
+ generated_at.is_a?(Time) &&
122
+ (self.class.remember_for.ago < generated_at) &&
123
+ (generated_at > (remember_created_at || Time.now).utc) &&
124
+ Devise.secure_compare(rememberable_value, token)
96
125
  end
97
126
 
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?
127
+ private
128
+
129
+ def time_from_json(value)
130
+ if value =~ /\A\d+\.\d+\Z/
131
+ Time.at(value.to_f)
132
+ else
133
+ Time.parse(value) rescue nil
134
+ end
102
135
  end
103
136
 
104
137
  module ClassMethods
105
138
  # Create the cookie key using the record id and remember_token
106
139
  def serialize_into_cookie(record)
107
- [record.to_key, record.rememberable_value]
140
+ [record.to_key, record.rememberable_value, Time.now.utc.to_f.to_s]
108
141
  end
109
142
 
110
143
  # Recreate the user based on the stored cookie
111
- def serialize_from_cookie(id, remember_token)
144
+ def serialize_from_cookie(*args)
145
+ id, token, generated_at = *args
146
+
112
147
  record = to_adapter.get(id)
113
- record if record && !record.remember_expired? &&
114
- Devise.secure_compare(record.rememberable_value, remember_token)
148
+ record if record && record.remember_me?(token, generated_at)
115
149
  end
116
150
 
117
151
  # Generate a token checking if one does not already exist in the database.
@@ -26,7 +26,6 @@ module Devise
26
26
 
27
27
  # Checks whether the user session has expired based on configured time.
28
28
  def timedout?(last_access)
29
- return false if remember_exists_and_not_expired?
30
29
  !timeout_in.nil? && last_access && last_access <= timeout_in.ago
31
30
  end
32
31
 
@@ -36,11 +35,6 @@ module Devise
36
35
 
37
36
  private
38
37
 
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
38
  module ClassMethods
45
39
  Devise::Models.config(self, :timeout_in)
46
40
  end
@@ -30,8 +30,7 @@ module Devise
30
30
 
31
31
  def update_tracked_fields!(request)
32
32
  update_tracked_fields(request)
33
- save(validate: false) or raise "Devise trackable could not save #{inspect}." \
34
- "Please make sure a model using trackable can be saved at sign in."
33
+ save(validate: false)
35
34
  end
36
35
  end
37
36
  end
@@ -10,12 +10,12 @@ module Devise
10
10
  # Validatable adds the following options to devise_for:
11
11
  #
12
12
  # * +email_regexp+: the regular expression used to validate e-mails;
13
- # * +password_length+: a range expressing password length. Defaults to 8..128.
13
+ # * +password_length+: a range expressing password length. Defaults to 8..72.
14
14
  #
15
15
  module Validatable
16
16
  # All validations used by this module.
17
- VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of,
18
- :validates_confirmation_of, :validates_length_of ].freeze
17
+ VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
18
+ :validates_confirmation_of, :validates_length_of].freeze
19
19
 
20
20
  def self.required_fields(klass)
21
21
  []
data/lib/devise/models.rb CHANGED
@@ -12,7 +12,7 @@ module Devise
12
12
 
13
13
  # Creates configuration values for Devise and for the given module.
14
14
  #
15
- # Devise::Models.config(Devise::Authenticatable, :stretches, 10)
15
+ # Devise::Models.config(Devise::DatabaseAuthenticatable, :stretches)
16
16
  #
17
17
  # The line above creates:
18
18
  #
@@ -94,10 +94,24 @@ module ActionDispatch::Routing
94
94
  #
95
95
  # devise_for :users, path: 'accounts'
96
96
  #
97
- # * singular: setup the singular name for the given resource. This is used as the instance variable
98
- # name in controller, as the name in routes and the scope given to warden.
97
+ # * singular: setup the singular name for the given resource. This is used as the helper methods
98
+ # names in controller ("authenticate_#{singular}!", "#{singular}_signed_in?", "current_#{singular}"
99
+ # and "#{singular}_session"), as the scope name in routes and as the scope given to warden.
99
100
  #
100
- # devise_for :users, singular: :user
101
+ # devise_for :admins, singular: :manager
102
+ #
103
+ # devise_scope :manager do
104
+ # ...
105
+ # end
106
+ #
107
+ # class ManagerController < ApplicationController
108
+ # before_filter authenticate_manager!
109
+ #
110
+ # def show
111
+ # @manager = current_manager
112
+ # ...
113
+ # end
114
+ # end
101
115
  #
102
116
  # * path_names: configure different path names to overwrite defaults :sign_in, :sign_out, :sign_up,
103
117
  # :password, :confirmation, :unlock.
@@ -119,7 +133,7 @@ module ActionDispatch::Routing
119
133
  # * sign_out_via: the HTTP method(s) accepted for the :sign_out action (default: :get),
120
134
  # if you wish to restrict this to accept only :post or :delete requests you should do:
121
135
  #
122
- # devise_for :users, sign_out_via: [ :post, :delete ]
136
+ # devise_for :users, sign_out_via: [:post, :delete]
123
137
  #
124
138
  # You need to make sure that your sign_out controls trigger a request with a matching HTTP method.
125
139
  #
@@ -402,21 +416,16 @@ module ActionDispatch::Routing
402
416
  def devise_omniauth_callback(mapping, controllers) #:nodoc:
403
417
  if mapping.fullpath =~ /:[a-zA-Z_]/
404
418
  raise <<-ERROR
405
- Devise does not support scoping omniauth callbacks under a dynamic segment
419
+ Devise does not support scoping OmniAuth callbacks under a dynamic segment
406
420
  and you have set #{mapping.fullpath.inspect}. You can work around by passing
407
- `skip: :omniauth_callbacks` and manually defining the routes. Here is an example:
408
-
409
- match "/users/auth/:provider",
410
- constraints: { provider: /google|facebook/ },
411
- to: "devise/omniauth_callbacks#passthru",
412
- as: :omniauth_authorize,
413
- via: [:get, :post]
414
-
415
- match "/users/auth/:action/callback",
416
- constraints: { action: /google|facebook/ },
417
- to: "devise/omniauth_callbacks",
418
- as: :omniauth_callback,
419
- via: [:get, :post]
421
+ `skip: :omniauth_callbacks` to the `devise_for` call and extract omniauth
422
+ options to another `devise_for` call outside the scope. Here is an example:
423
+
424
+ devise_for :users, only: :omniauth_callbacks, controllers: {omniauth_callbacks: 'users/omniauth_callbacks'}
425
+
426
+ scope '/(:locale)', locale: /ru|en/ do
427
+ devise_for :users, skip: :omniauth_callbacks
428
+ end
420
429
  ERROR
421
430
  end
422
431
 
@@ -435,26 +444,23 @@ ERROR
435
444
 
436
445
  match "#{path_prefix}/:action/callback",
437
446
  constraints: { action: providers },
438
- to: controllers[:omniauth_callbacks],
447
+ to: "#{controllers[:omniauth_callbacks]}#:action",
439
448
  as: :omniauth_callback,
440
449
  via: [:get, :post]
441
450
  ensure
442
451
  @scope[:path] = path
443
452
  end
444
453
 
445
- DEVISE_SCOPE_KEYS = [:as, :path, :module, :constraints, :defaults, :options]
446
-
447
454
  def with_devise_exclusive_scope(new_path, new_as, options) #:nodoc:
448
- old = {}
449
- DEVISE_SCOPE_KEYS.each { |k| old[k] = @scope[k] }
455
+ current_scope = @scope.dup
450
456
 
451
- new = { as: new_as, path: new_path, module: nil }
452
- new.merge!(options.slice(:constraints, :defaults, :options))
457
+ exclusive = { as: new_as, path: new_path, module: nil }
458
+ exclusive.merge!(options.slice(:constraints, :defaults, :options))
453
459
 
454
- @scope.merge!(new)
460
+ exclusive.each_pair { |key, value| @scope[key] = value }
455
461
  yield
456
462
  ensure
457
- @scope.merge!(old)
463
+ @scope = current_scope
458
464
  end
459
465
 
460
466
  def constraints_for(method_to_apply, scope=nil, block=nil)
data/lib/devise/rails.rb CHANGED
@@ -17,7 +17,7 @@ module Devise
17
17
  Devise.include_helpers(Devise::Controllers)
18
18
  end
19
19
 
20
- initializer "devise.omniauth" do |app|
20
+ initializer "devise.omniauth", after: :load_config_initializers, before: :build_middleware_stack do |app|
21
21
  Devise.omniauth_configs.each do |provider, config|
22
22
  app.middleware.use config.strategy_class, *config.args do |strategy|
23
23
  config.strategy = strategy