devise 4.6.0 → 4.9.4

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 (58) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +122 -3
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +108 -61
  5. data/app/controllers/devise/confirmations_controller.rb +1 -0
  6. data/app/controllers/devise/passwords_controller.rb +2 -2
  7. data/app/controllers/devise/registrations_controller.rb +1 -1
  8. data/app/controllers/devise/sessions_controller.rb +2 -2
  9. data/app/controllers/devise/unlocks_controller.rb +1 -0
  10. data/app/controllers/devise_controller.rb +16 -2
  11. data/app/helpers/devise_helper.rb +19 -7
  12. data/app/mailers/devise/mailer.rb +5 -5
  13. data/app/views/devise/passwords/edit.html.erb +1 -1
  14. data/app/views/devise/registrations/edit.html.erb +1 -1
  15. data/app/views/devise/shared/_error_messages.html.erb +1 -1
  16. data/app/views/devise/shared/_links.html.erb +1 -1
  17. data/config/locales/en.yml +3 -3
  18. data/lib/devise/controllers/helpers.rb +9 -7
  19. data/lib/devise/controllers/responder.rb +35 -0
  20. data/lib/devise/controllers/sign_in_out.rb +7 -5
  21. data/lib/devise/controllers/url_helpers.rb +1 -1
  22. data/lib/devise/failure_app.rb +22 -16
  23. data/lib/devise/hooks/csrf_cleaner.rb +6 -1
  24. data/lib/devise/hooks/lockable.rb +2 -5
  25. data/lib/devise/hooks/timeoutable.rb +2 -2
  26. data/lib/devise/mapping.rb +1 -1
  27. data/lib/devise/models/authenticatable.rb +13 -9
  28. data/lib/devise/models/confirmable.rb +30 -39
  29. data/lib/devise/models/database_authenticatable.rb +18 -34
  30. data/lib/devise/models/lockable.rb +11 -3
  31. data/lib/devise/models/omniauthable.rb +2 -2
  32. data/lib/devise/models/recoverable.rb +8 -19
  33. data/lib/devise/models/rememberable.rb +2 -2
  34. data/lib/devise/models/timeoutable.rb +1 -1
  35. data/lib/devise/models/trackable.rb +1 -1
  36. data/lib/devise/models/validatable.rb +4 -9
  37. data/lib/devise/models.rb +1 -0
  38. data/lib/devise/omniauth.rb +2 -5
  39. data/lib/devise/orm.rb +71 -0
  40. data/lib/devise/rails/deprecated_constant_accessor.rb +39 -0
  41. data/lib/devise/rails/routes.rb +6 -6
  42. data/lib/devise/rails.rb +4 -0
  43. data/lib/devise/strategies/authenticatable.rb +1 -1
  44. data/lib/devise/test/controller_helpers.rb +4 -2
  45. data/lib/devise/test/integration_helpers.rb +1 -1
  46. data/lib/devise/test_helpers.rb +1 -1
  47. data/lib/devise/version.rb +1 -1
  48. data/lib/devise.rb +35 -12
  49. data/lib/generators/active_record/devise_generator.rb +17 -2
  50. data/lib/generators/devise/devise_generator.rb +1 -1
  51. data/lib/generators/devise/install_generator.rb +1 -5
  52. data/lib/generators/devise/views_generator.rb +1 -1
  53. data/lib/generators/templates/README +9 -1
  54. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +1 -1
  55. data/lib/generators/templates/devise.rb +25 -11
  56. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +4 -1
  57. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +1 -1
  58. metadata +21 -16
@@ -15,6 +15,7 @@ class DeviseController < Devise.parent_controller.constantize
15
15
  end
16
16
 
17
17
  prepend_before_action :assert_is_devise_resource!
18
+ self.responder = Devise.responder
18
19
  respond_to :html if mimes_for_respond_to.empty?
19
20
 
20
21
  # Override prefixes to consider the scoped view.
@@ -32,6 +33,19 @@ class DeviseController < Devise.parent_controller.constantize
32
33
  end
33
34
  end
34
35
 
36
+ # Override internal methods to exclude `_prefixes` from action methods since
37
+ # we override it above.
38
+ #
39
+ # There was an intentional change in Rails 7.1 that will allow it to become
40
+ # an action method because it's a public method of a non-abstract controller,
41
+ # but we also can't make this abstract because it can affect potential actions
42
+ # defined in the parent controller, so instead we ensure `_prefixes` is going
43
+ # to be considered internal. (and thus, won't become an action method.)
44
+ # Ref: https://github.com/rails/rails/pull/48699
45
+ def self.internal_methods #:nodoc:
46
+ super << :_prefixes
47
+ end
48
+
35
49
  protected
36
50
 
37
51
  # Gets the actual resource stored in the instance variable
@@ -112,7 +126,7 @@ MESSAGE
112
126
  end
113
127
 
114
128
  if authenticated && resource = warden.user(resource_name)
115
- flash[:alert] = I18n.t("devise.failure.already_authenticated")
129
+ set_flash_message(:alert, 'already_authenticated', scope: 'devise.failure')
116
130
  redirect_to after_sign_in_path_for(resource)
117
131
  end
118
132
  end
@@ -184,7 +198,7 @@ MESSAGE
184
198
  options[:default] = Array(options[:default]).unshift(kind.to_sym)
185
199
  options[:resource_name] = resource_name
186
200
  options = devise_i18n_options(options)
187
- I18n.t("#{options[:resource_name]}.#{kind}", options)
201
+ I18n.t("#{options[:resource_name]}.#{kind}", **options)
188
202
  end
189
203
 
190
204
  # Controllers inheriting DeviseController are advised to override this
@@ -1,14 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeviseHelper
4
- # Retain this method for backwards compatibility, deprecated in favour of modifying the
5
- # devise/shared/error_messages partial
4
+ # Retain this method for backwards compatibility, deprecated in favor of modifying the
5
+ # devise/shared/error_messages partial.
6
6
  def devise_error_messages!
7
- ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
8
- [Devise] `DeviseHelper.devise_error_messages!`
9
- is deprecated and it will be removed in the next major version.
10
- To customize the errors styles please run `rails g devise:views` and modify the
11
- `devise/shared/error_messages` partial.
7
+ Devise.deprecator.warn <<-DEPRECATION.strip_heredoc
8
+ [Devise] `DeviseHelper#devise_error_messages!` is deprecated and will be
9
+ removed in the next major version.
10
+
11
+ Devise now uses a partial under "devise/shared/error_messages" to display
12
+ error messages by default, and make them easier to customize. Update your
13
+ views changing calls from:
14
+
15
+ <%= devise_error_messages! %>
16
+
17
+ to:
18
+
19
+ <%= render "devise/shared/error_messages", resource: resource %>
20
+
21
+ To start customizing how errors are displayed, you can copy the partial
22
+ from devise to your `app/views` folder. Alternatively, you can run
23
+ `rails g devise:views` which will copy all of them again to your app.
12
24
  DEPRECATION
13
25
 
14
26
  return "" if resource.errors.empty?
@@ -4,26 +4,26 @@ if defined?(ActionMailer)
4
4
  class Devise::Mailer < Devise.parent_mailer.constantize
5
5
  include Devise::Mailers::Helpers
6
6
 
7
- def confirmation_instructions(record, token, opts={})
7
+ def confirmation_instructions(record, token, opts = {})
8
8
  @token = token
9
9
  devise_mail(record, :confirmation_instructions, opts)
10
10
  end
11
11
 
12
- def reset_password_instructions(record, token, opts={})
12
+ def reset_password_instructions(record, token, opts = {})
13
13
  @token = token
14
14
  devise_mail(record, :reset_password_instructions, opts)
15
15
  end
16
16
 
17
- def unlock_instructions(record, token, opts={})
17
+ def unlock_instructions(record, token, opts = {})
18
18
  @token = token
19
19
  devise_mail(record, :unlock_instructions, opts)
20
20
  end
21
21
 
22
- def email_changed(record, opts={})
22
+ def email_changed(record, opts = {})
23
23
  devise_mail(record, :email_changed, opts)
24
24
  end
25
25
 
26
- def password_change(record, opts={})
26
+ def password_change(record, opts = {})
27
27
  devise_mail(record, :password_change, opts)
28
28
  end
29
29
  end
@@ -14,7 +14,7 @@
14
14
 
15
15
  <div class="field">
16
16
  <%= f.label :password_confirmation, "Confirm new password" %><br />
17
- <%= f.password_field :password_confirmation, autocomplete: "off" %>
17
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
18
18
  </div>
19
19
 
20
20
  <div class="actions">
@@ -38,6 +38,6 @@
38
38
 
39
39
  <h3>Cancel my account</h3>
40
40
 
41
- <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
41
+ <div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
42
42
 
43
43
  <%= link_to "Back", :back %>
@@ -1,5 +1,5 @@
1
1
  <% if resource.errors.any? %>
2
- <div id="error_explanation">
2
+ <div id="error_explanation" data-turbo-cache="false">
3
3
  <h2>
4
4
  <%= I18n.t("errors.messages.not_saved",
5
5
  count: resource.errors.count,
@@ -20,6 +20,6 @@
20
20
 
21
21
  <%- if devise_mapping.omniauthable? %>
22
22
  <%- resource_class.omniauth_providers.each do |provider| %>
23
- <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
23
+ <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %><br />
24
24
  <% end %>
25
25
  <% end %>
@@ -1,4 +1,4 @@
1
- # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
1
+ # Additional translations at https://github.com/heartcombo/devise/wiki/I18n
2
2
 
3
3
  en:
4
4
  devise:
@@ -42,9 +42,9 @@ en:
42
42
  signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
43
43
  signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
44
44
  signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
45
- update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
45
+ update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address."
46
46
  updated: "Your account has been updated successfully."
47
- updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again"
47
+ updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again."
48
48
  sessions:
49
49
  signed_in: "Signed in successfully."
50
50
  signed_out: "Signed out successfully."
@@ -36,16 +36,17 @@ module Devise
36
36
  # before_action ->{ authenticate_blogger! :admin } # Redirects to the admin login page
37
37
  # current_blogger :user # Preferably returns a User if one is signed in
38
38
  #
39
- def devise_group(group_name, opts={})
39
+ def devise_group(group_name, opts = {})
40
40
  mappings = "[#{ opts[:contains].map { |m| ":#{m}" }.join(',') }]"
41
41
 
42
42
  class_eval <<-METHODS, __FILE__, __LINE__ + 1
43
- def authenticate_#{group_name}!(favourite=nil, opts={})
43
+ def authenticate_#{group_name}!(favorite = nil, opts = {})
44
44
  unless #{group_name}_signed_in?
45
45
  mappings = #{mappings}
46
- mappings.unshift mappings.delete(favourite.to_sym) if favourite
46
+ mappings.unshift mappings.delete(favorite.to_sym) if favorite
47
47
  mappings.each do |mapping|
48
48
  opts[:scope] = mapping
49
+ opts[:locale] = I18n.locale
49
50
  warden.authenticate!(opts) if !devise_controller? || opts.delete(:force)
50
51
  end
51
52
  end
@@ -57,9 +58,9 @@ module Devise
57
58
  end
58
59
  end
59
60
 
60
- def current_#{group_name}(favourite=nil)
61
+ def current_#{group_name}(favorite = nil)
61
62
  mappings = #{mappings}
62
- mappings.unshift mappings.delete(favourite.to_sym) if favourite
63
+ mappings.unshift mappings.delete(favorite.to_sym) if favorite
63
64
  mappings.each do |mapping|
64
65
  current = warden.authenticate(scope: mapping)
65
66
  return current if current
@@ -113,8 +114,9 @@ module Devise
113
114
  mapping = mapping.name
114
115
 
115
116
  class_eval <<-METHODS, __FILE__, __LINE__ + 1
116
- def authenticate_#{mapping}!(opts={})
117
+ def authenticate_#{mapping}!(opts = {})
117
118
  opts[:scope] = :#{mapping}
119
+ opts[:locale] = I18n.locale
118
120
  warden.authenticate!(opts) if !devise_controller? || opts.delete(:force)
119
121
  end
120
122
 
@@ -252,7 +254,7 @@ module Devise
252
254
  # Overwrite Rails' handle unverified request to sign out all scopes,
253
255
  # clear run strategies and remove cached variables.
254
256
  def handle_unverified_request
255
- super # call the default behaviour which resets/nullifies/raises
257
+ super # call the default behavior which resets/nullifies/raises
256
258
  request.env["devise.skip_storage"] = true
257
259
  sign_out_all_scopes(false)
258
260
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Controllers
5
+ # Custom Responder to configure default statuses that only apply to Devise,
6
+ # and allow to integrate more easily with Hotwire/Turbo.
7
+ class Responder < ActionController::Responder
8
+ if respond_to?(:error_status=) && respond_to?(:redirect_status=)
9
+ self.error_status = :ok
10
+ self.redirect_status = :found
11
+ else
12
+ # TODO: remove this support for older Rails versions, which aren't supported by Turbo
13
+ # and/or responders. It won't allow configuring a custom response, but it allows Devise
14
+ # to use these methods and defaults across the implementation more easily.
15
+ def self.error_status
16
+ :ok
17
+ end
18
+
19
+ def self.redirect_status
20
+ :found
21
+ end
22
+
23
+ def self.error_status=(*)
24
+ warn "[DEVISE] Setting the error status on the Devise responder has no effect with this " \
25
+ "version of `responders`, please make sure you're using a newer version. Check the changelog for more info."
26
+ end
27
+
28
+ def self.redirect_status=(*)
29
+ warn "[DEVISE] Setting the redirect status on the Devise responder has no effect with this " \
30
+ "version of `responders`, please make sure you're using a newer version. Check the changelog for more info."
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -10,7 +10,7 @@ module Devise
10
10
  # cause exceptions to be thrown from this method; if you simply want to check
11
11
  # if a scope has already previously been authenticated without running
12
12
  # authentication hooks, you can directly call `warden.authenticated?(scope: scope)`
13
- def signed_in?(scope=nil)
13
+ def signed_in?(scope = nil)
14
14
  [scope || Devise.mappings.keys].flatten.any? do |_scope|
15
15
  warden.authenticate?(scope: _scope)
16
16
  end
@@ -21,7 +21,7 @@ module Devise
21
21
  # to the set_user method in warden.
22
22
  # If you are using a custom warden strategy and the timeoutable module, you have to
23
23
  # set `env["devise.skip_timeout"] = true` in the request to use this method, like we do
24
- # in the sessions controller: https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb#L7
24
+ # in the sessions controller: https://github.com/heartcombo/devise/blob/main/app/controllers/devise/sessions_controller.rb#L7
25
25
  #
26
26
  # Examples:
27
27
  #
@@ -38,7 +38,7 @@ module Devise
38
38
  expire_data_after_sign_in!
39
39
 
40
40
  if options[:bypass]
41
- ActiveSupport::Deprecation.warn(<<-DEPRECATION.strip_heredoc, caller)
41
+ Devise.deprecator.warn(<<-DEPRECATION.strip_heredoc, caller)
42
42
  [Devise] bypass option is deprecated and it will be removed in future version of Devise.
43
43
  Please use bypass_sign_in method instead.
44
44
  Example:
@@ -77,7 +77,7 @@ module Devise
77
77
  # sign_out :user # sign_out(scope)
78
78
  # sign_out @user # sign_out(resource)
79
79
  #
80
- def sign_out(resource_or_scope=nil)
80
+ def sign_out(resource_or_scope = nil)
81
81
  return sign_out_all_scopes unless resource_or_scope
82
82
  scope = Devise::Mapping.find_scope!(resource_or_scope)
83
83
  user = warden.user(scope: scope, run_callbacks: false) # If there is no user
@@ -92,7 +92,7 @@ module Devise
92
92
  # Sign out all active users or scopes. This helper is useful for signing out all roles
93
93
  # in one click. This signs out ALL scopes in warden. Returns true if there was at least one logout
94
94
  # and false if there was no user logged in on all scopes.
95
- def sign_out_all_scopes(lock=true)
95
+ def sign_out_all_scopes(lock = true)
96
96
  users = Devise.mappings.keys.map { |s| warden.user(scope: s, run_callbacks: false) }
97
97
 
98
98
  warden.logout
@@ -106,10 +106,12 @@ module Devise
106
106
  private
107
107
 
108
108
  def expire_data_after_sign_in!
109
+ # TODO: remove once Rails 5.2+ and forward are only supported.
109
110
  # session.keys will return an empty array if the session is not yet loaded.
110
111
  # This is a bug in both Rack and Rails.
111
112
  # A call to #empty? forces the session to be loaded.
112
113
  session.empty?
114
+
113
115
  session.keys.grep(/^devise\./).each { |k| session.delete(k) }
114
116
  end
115
117
 
@@ -34,7 +34,7 @@ module Devise
34
34
  end
35
35
  end
36
36
 
37
- def self.generate_helpers!(routes=nil)
37
+ def self.generate_helpers!(routes = nil)
38
38
  routes ||= begin
39
39
  mappings = Devise.mappings.values.map(&:used_helpers).flatten.uniq
40
40
  Devise::URL_HELPERS.slice(*mappings)
@@ -18,6 +18,11 @@ module Devise
18
18
 
19
19
  delegate :flash, to: :request
20
20
 
21
+ include AbstractController::Callbacks
22
+ around_action do |failure_app, action|
23
+ I18n.with_locale(failure_app.i18n_locale, &action)
24
+ end
25
+
21
26
  def self.call(env)
22
27
  @respond ||= action(:respond)
23
28
  @respond.call(env)
@@ -71,8 +76,11 @@ module Devise
71
76
  end
72
77
 
73
78
  flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
74
- # self.response = recall_app(warden_options[:recall]).call(env)
75
- self.response = recall_app(warden_options[:recall]).call(request.env)
79
+ self.response = recall_app(warden_options[:recall]).call(request.env).tap { |response|
80
+ response[0] = Rack::Utils.status_code(
81
+ response[0].in?(300..399) ? Devise.responder.redirect_status : Devise.responder.error_status
82
+ )
83
+ }
76
84
  end
77
85
 
78
86
  def redirect
@@ -104,15 +112,19 @@ module Devise
104
112
  options[:default] = [message]
105
113
  auth_keys = scope_class.authentication_keys
106
114
  keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) }
107
- options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector"))
115
+ options[:authentication_keys] = keys.join(I18n.t(:"support.array.words_connector"))
108
116
  options = i18n_options(options)
109
117
 
110
- I18n.t(:"#{scope}.#{message}", options)
118
+ I18n.t(:"#{scope}.#{message}", **options)
111
119
  else
112
120
  message.to_s
113
121
  end
114
122
  end
115
123
 
124
+ def i18n_locale
125
+ warden_options[:locale]
126
+ end
127
+
116
128
  def redirect_url
117
129
  if warden_message == :timeout
118
130
  flash[:timedout] = true if is_flashing_format?
@@ -152,8 +164,8 @@ module Devise
152
164
 
153
165
  # We need to add the rootpath to `script_name` manually for applications that use a Rails
154
166
  # version lower than 5.1. Otherwise, it is going to generate a wrong path for Engines
155
- # that use Devise. Remove it when the support of Rails 5.0 is droped.
156
- elsif root_path_defined?(context) && rails_5_and_down?
167
+ # that use Devise. Remove it when the support of Rails 5.0 is dropped.
168
+ elsif root_path_defined?(context) && !rails_51_and_up?
157
169
  rootpath = context.routes.url_helpers.root_path
158
170
  opts[:script_name] = rootpath.chomp('/') if rootpath.length > 1
159
171
  end
@@ -168,7 +180,7 @@ module Devise
168
180
  end
169
181
 
170
182
  def skip_format?
171
- %w(html */*).include? request_format.to_s
183
+ %w(html */* turbo_stream).include? request_format.to_s
172
184
  end
173
185
 
174
186
  # Choose whether we should respond in an HTTP authentication fashion,
@@ -275,17 +287,11 @@ module Devise
275
287
  private
276
288
 
277
289
  def root_path_defined?(context)
278
- defined?(context.routes) && context.routes.url_helpers.root_path.present?
279
- end
280
-
281
- def rails_5_and_down?
282
- return false if rails_5_up?
283
-
284
- Rails::VERSION::MAJOR >= 4
290
+ defined?(context.routes) && context.routes.url_helpers.respond_to?(:root_path)
285
291
  end
286
292
 
287
- def rails_5_up?
288
- Rails::VERSION::MAJOR >= 5 && Rails::VERSION::MINOR > 0
293
+ def rails_51_and_up?
294
+ Rails.gem_version >= Gem::Version.new("5.1")
289
295
  end
290
296
  end
291
297
  end
@@ -4,6 +4,11 @@ Warden::Manager.after_authentication do |record, warden, options|
4
4
  clean_up_for_winning_strategy = !warden.winning_strategy.respond_to?(:clean_up_csrf?) ||
5
5
  warden.winning_strategy.clean_up_csrf?
6
6
  if Devise.clean_up_csrf_token_on_authentication && clean_up_for_winning_strategy
7
- warden.request.session.try(:delete, :_csrf_token)
7
+ if warden.request.respond_to?(:reset_csrf_token)
8
+ # Rails 7.1+
9
+ warden.request.reset_csrf_token
10
+ else
11
+ warden.request.session.try(:delete, :_csrf_token)
12
+ end
8
13
  end
9
14
  end
@@ -3,10 +3,7 @@
3
3
  # After each sign in, if resource responds to failed_attempts, sets it to 0
4
4
  # This is only triggered when the user is explicitly set (with set_user)
5
5
  Warden::Manager.after_set_user except: :fetch do |record, warden, options|
6
- if record.respond_to?(:failed_attempts) && warden.authenticated?(options[:scope])
7
- unless record.failed_attempts.to_i.zero?
8
- record.failed_attempts = 0
9
- record.save(validate: false)
10
- end
6
+ if record.respond_to?(:reset_failed_attempts!) && warden.authenticated?(options[:scope])
7
+ record.reset_failed_attempts!
11
8
  end
12
9
  end
@@ -21,8 +21,8 @@ Warden::Manager.after_set_user do |record, warden, options|
21
21
 
22
22
  proxy = Devise::Hooks::Proxy.new(warden)
23
23
 
24
- if record.timedout?(last_request_at) &&
25
- !env['devise.skip_timeout'] &&
24
+ if !env['devise.skip_timeout'] &&
25
+ record.timedout?(last_request_at) &&
26
26
  !proxy.remember_me_is_active?(record)
27
27
  Devise.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
28
28
  throw :warden, scope: scope, message: :timeout
@@ -46,7 +46,7 @@ module Devise
46
46
  raise "Could not find a valid mapping for #{obj.inspect}"
47
47
  end
48
48
 
49
- def self.find_by_path!(path, path_type=:fullpath)
49
+ def self.find_by_path!(path, path_type = :fullpath)
50
50
  Devise.mappings.each_value { |m| return m if path.include?(m.send(path_type)) }
51
51
  raise "Could not find a valid mapping for path #{path.inspect}"
52
52
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_model/version'
4
3
  require 'devise/hooks/activatable'
5
4
  require 'devise/hooks/csrf_cleaner'
5
+ require 'devise/rails/deprecated_constant_accessor'
6
6
 
7
7
  module Devise
8
8
  module Models
@@ -10,7 +10,7 @@ module Devise
10
10
  #
11
11
  # == Options
12
12
  #
13
- # Authenticatable adds the following options to devise_for:
13
+ # Authenticatable adds the following options to +devise+:
14
14
  #
15
15
  # * +authentication_keys+: parameters used for authentication. By default [:email].
16
16
  #
@@ -56,11 +56,14 @@ module Devise
56
56
  module Authenticatable
57
57
  extend ActiveSupport::Concern
58
58
 
59
- BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
59
+ UNSAFE_ATTRIBUTES_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
60
60
  :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
61
61
  :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
62
62
  :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
63
63
 
64
+ include Devise::DeprecatedConstantAccessor
65
+ deprecate_constant "BLACKLIST_FOR_SERIALIZATION", "Devise::Models::Authenticatable::UNSAFE_ATTRIBUTES_FOR_SERIALIZATION", deprecator: Devise.deprecator
66
+
64
67
  included do
65
68
  class_attribute :devise_modules, instance_writer: false
66
69
  self.devise_modules ||= []
@@ -105,12 +108,12 @@ module Devise
105
108
  # given to :except will simply add names to exempt to Devise internal list.
106
109
  def serializable_hash(options = nil)
107
110
  options = options.try(:dup) || {}
108
- options[:except] = Array(options[:except])
111
+ options[:except] = Array(options[:except]).dup
109
112
 
110
113
  if options[:force_except]
111
114
  options[:except].concat Array(options[:force_except])
112
115
  else
113
- options[:except].concat BLACKLIST_FOR_SERIALIZATION
116
+ options[:except].concat UNSAFE_ATTRIBUTES_FOR_SERIALIZATION
114
117
  end
115
118
 
116
119
  super(options)
@@ -153,7 +156,8 @@ module Devise
153
156
  # # If the record is new or changed then delay the
154
157
  # # delivery until the after_commit callback otherwise
155
158
  # # send now because after_commit will not be called.
156
- # if new_record? || changed?
159
+ # # For Rails < 6 use `changed?` instead of `saved_changes?`.
160
+ # if new_record? || saved_changes?
157
161
  # pending_devise_notifications << [notification, args]
158
162
  # else
159
163
  # render_and_send_devise_message(notification, *args)
@@ -272,17 +276,17 @@ module Devise
272
276
  find_first_by_auth_conditions(tainted_conditions)
273
277
  end
274
278
 
275
- def find_first_by_auth_conditions(tainted_conditions, opts={})
279
+ def find_first_by_auth_conditions(tainted_conditions, opts = {})
276
280
  to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
277
281
  end
278
282
 
279
283
  # Find or initialize a record setting an error if it can't be found.
280
- def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
284
+ def find_or_initialize_with_error_by(attribute, value, error = :invalid) #:nodoc:
281
285
  find_or_initialize_with_errors([attribute], { attribute => value }, error)
282
286
  end
283
287
 
284
288
  # Find or initialize a record with group of attributes based on a list of required attributes.
285
- def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
289
+ def find_or_initialize_with_errors(required_attributes, attributes, error = :invalid) #:nodoc:
286
290
  attributes.try(:permit!)
287
291
  attributes = attributes.to_h.with_indifferent_access
288
292
  .slice(*required_attributes)
@@ -48,7 +48,7 @@ module Devise
48
48
  included do
49
49
  before_create :generate_confirmation_token, if: :confirmation_required?
50
50
  after_create :skip_reconfirmation_in_callback!, if: :send_confirmation_notification?
51
- if defined?(ActiveRecord) && self < ActiveRecord::Base # ActiveRecord
51
+ if Devise::Orm.active_record?(self) # ActiveRecord
52
52
  after_commit :send_on_create_confirmation_instructions, on: :create, if: :send_confirmation_notification?
53
53
  after_commit :send_reconfirmation_instructions, on: :update, if: :reconfirmation_required?
54
54
  else # Mongoid
@@ -76,7 +76,7 @@ module Devise
76
76
  # Confirm a user by setting it's confirmed_at to actual time. If the user
77
77
  # is already confirmed, add an error to email field. If the user is invalid
78
78
  # add errors
79
- def confirm(args={})
79
+ def confirm(args = {})
80
80
  pending_any_confirmation do
81
81
  if confirmation_period_expired?
82
82
  self.errors.add(:email, :confirmation_period_expired,
@@ -258,44 +258,23 @@ module Devise
258
258
  generate_confirmation_token && save(validate: false)
259
259
  end
260
260
 
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
261
+
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.devise_email_in_database
266
+ self.confirmation_token = nil
267
+ generate_confirmation_token
277
268
  end
278
269
 
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
270
+ def postpone_email_change?
271
+ postpone = self.class.reconfirmable &&
272
+ devise_will_save_change_to_email? &&
273
+ !@bypass_confirmation_postpone &&
274
+ self.email.present? &&
275
+ (!@skip_reconfirmation_in_callback || !self.devise_email_in_database.nil?)
276
+ @bypass_confirmation_postpone = false
277
+ postpone
299
278
  end
300
279
 
301
280
  def reconfirmation_required?
@@ -334,7 +313,7 @@ module Devise
334
313
  # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
335
314
  # field. If no user is found, returns a new user with an email not found error.
336
315
  # Options must contain the user email
337
- def send_confirmation_instructions(attributes={})
316
+ def send_confirmation_instructions(attributes = {})
338
317
  confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
339
318
  unless confirmable.try(:persisted?)
340
319
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
@@ -348,7 +327,19 @@ module Devise
348
327
  # If the user is already confirmed, create an error for the user
349
328
  # Options must have the confirmation_token
350
329
  def confirm_by_token(confirmation_token)
330
+ # When the `confirmation_token` parameter is blank, if there are any users with a blank
331
+ # `confirmation_token` in the database, the first one would be confirmed here.
332
+ # The error is being manually added here to ensure no users are confirmed by mistake.
333
+ # This was done in the model for convenience, since validation errors are automatically
334
+ # displayed in the view.
335
+ if confirmation_token.blank?
336
+ confirmable = new
337
+ confirmable.errors.add(:confirmation_token, :blank)
338
+ return confirmable
339
+ end
340
+
351
341
  confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
342
+
352
343
  unless confirmable
353
344
  confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
354
345
  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)