devise 3.4.1 → 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.
- checksums.yaml +4 -4
- data/.travis.yml +28 -19
- data/CHANGELOG.md +193 -104
- data/CODE_OF_CONDUCT.md +22 -0
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +90 -95
- data/MIT-LICENSE +1 -1
- data/README.md +55 -34
- data/Rakefile +2 -1
- data/app/controllers/devise/confirmations_controller.rb +4 -0
- data/app/controllers/devise/omniauth_callbacks_controller.rb +4 -0
- data/app/controllers/devise/passwords_controller.rb +14 -4
- data/app/controllers/devise/registrations_controller.rb +10 -11
- data/app/controllers/devise/sessions_controller.rb +7 -2
- data/app/controllers/devise/unlocks_controller.rb +3 -0
- data/app/controllers/devise_controller.rb +34 -18
- data/app/mailers/devise/mailer.rb +4 -0
- data/app/views/devise/confirmations/new.html.erb +1 -1
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/passwords/edit.html.erb +3 -0
- data/app/views/devise/registrations/new.html.erb +1 -1
- data/app/views/devise/shared/_links.html.erb +1 -1
- data/config/locales/en.yml +2 -0
- data/devise.gemspec +0 -2
- data/gemfiles/Gemfile.rails-3.2-stable.lock +52 -49
- data/gemfiles/Gemfile.rails-4.0-stable +1 -0
- data/gemfiles/Gemfile.rails-4.0-stable.lock +61 -60
- data/gemfiles/Gemfile.rails-4.1-stable +1 -0
- data/gemfiles/Gemfile.rails-4.1-stable.lock +66 -65
- data/gemfiles/Gemfile.rails-4.2-stable +30 -0
- data/gemfiles/Gemfile.rails-4.2-stable.lock +193 -0
- data/lib/devise/controllers/helpers.rb +12 -6
- data/lib/devise/controllers/rememberable.rb +9 -2
- data/lib/devise/controllers/sign_in_out.rb +2 -8
- data/lib/devise/controllers/store_location.rb +3 -1
- data/lib/devise/controllers/url_helpers.rb +7 -9
- data/lib/devise/encryptor.rb +22 -0
- data/lib/devise/failure_app.rb +48 -13
- data/lib/devise/hooks/timeoutable.rb +5 -7
- data/lib/devise/mapping.rb +1 -0
- data/lib/devise/models/authenticatable.rb +20 -26
- data/lib/devise/models/confirmable.rb +51 -17
- data/lib/devise/models/database_authenticatable.rb +17 -11
- data/lib/devise/models/lockable.rb +5 -1
- data/lib/devise/models/recoverable.rb +23 -15
- data/lib/devise/models/rememberable.rb +56 -22
- data/lib/devise/models/timeoutable.rb +0 -6
- data/lib/devise/models/trackable.rb +1 -2
- data/lib/devise/models/validatable.rb +3 -3
- data/lib/devise/models.rb +1 -1
- data/lib/devise/rails/routes.rb +27 -18
- data/lib/devise/rails.rb +1 -1
- data/lib/devise/strategies/authenticatable.rb +7 -4
- data/lib/devise/strategies/database_authenticatable.rb +1 -1
- data/lib/devise/strategies/rememberable.rb +13 -6
- data/lib/devise/test_helpers.rb +2 -2
- data/lib/devise/version.rb +1 -1
- data/lib/devise.rb +37 -36
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/active_record/templates/migration_existing.rb +1 -1
- data/lib/generators/devise/views_generator.rb +14 -3
- data/lib/generators/templates/controllers/README +2 -2
- data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +1 -1
- data/lib/generators/templates/controllers/registrations_controller.rb +2 -2
- data/lib/generators/templates/controllers/sessions_controller.rb +1 -1
- data/lib/generators/templates/devise.rb +17 -11
- data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/password_change.markerb +3 -0
- data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
- data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
- data/lib/generators/templates/simple_form_for/registrations/new.html.erb +1 -1
- data/test/controllers/custom_registrations_controller_test.rb +6 -1
- data/test/controllers/helper_methods_test.rb +21 -0
- data/test/controllers/helpers_test.rb +5 -0
- data/test/controllers/inherited_controller_i18n_messages_test.rb +51 -0
- data/test/controllers/internal_helpers_test.rb +4 -4
- data/test/controllers/load_hooks_controller_test.rb +19 -0
- data/test/controllers/passwords_controller_test.rb +1 -1
- data/test/controllers/sessions_controller_test.rb +3 -3
- data/test/devise_test.rb +3 -3
- data/test/failure_app_test.rb +40 -0
- data/test/generators/views_generator_test.rb +7 -0
- data/test/integration/database_authenticatable_test.rb +11 -0
- data/test/integration/omniauthable_test.rb +12 -10
- data/test/integration/recoverable_test.rb +13 -0
- data/test/integration/rememberable_test.rb +50 -3
- data/test/integration/timeoutable_test.rb +13 -18
- data/test/mailers/confirmation_instructions_test.rb +1 -1
- data/test/mapping_test.rb +6 -0
- data/test/models/confirmable_test.rb +93 -37
- data/test/models/database_authenticatable_test.rb +20 -0
- data/test/models/lockable_test.rb +29 -7
- data/test/models/recoverable_test.rb +62 -7
- data/test/models/rememberable_test.rb +68 -97
- data/test/models/validatable_test.rb +5 -5
- data/test/models_test.rb +15 -6
- data/test/rails_app/app/active_record/user_without_email.rb +8 -0
- data/test/rails_app/app/controllers/admins_controller.rb +0 -5
- data/test/rails_app/app/controllers/custom/registrations_controller.rb +10 -0
- data/test/rails_app/app/mongoid/user_without_email.rb +33 -0
- data/test/rails_app/config/application.rb +1 -1
- data/test/rails_app/config/environments/production.rb +6 -2
- data/test/rails_app/config/environments/test.rb +7 -2
- data/test/rails_app/config/initializers/devise.rb +12 -15
- data/test/rails_app/config/routes.rb +6 -3
- data/test/rails_app/lib/shared_user.rb +1 -1
- data/test/rails_app/lib/shared_user_without_email.rb +26 -0
- data/test/rails_test.rb +9 -0
- data/test/support/helpers.rb +4 -0
- data/test/support/integration.rb +2 -2
- data/test/test_helpers_test.rb +22 -7
- data/test/test_models.rb +2 -2
- data/test/time_helpers.rb +137 -0
- metadata +26 -4
@@ -2,18 +2,25 @@ module Devise
|
|
2
2
|
module Controllers
|
3
3
|
# A module that may be optionally included in a controller in order
|
4
4
|
# to provide remember me behavior. Useful when signing in is done
|
5
|
-
# through a callback, like in
|
5
|
+
# through a callback, like in OmniAuth.
|
6
6
|
module Rememberable
|
7
7
|
# Return default cookie values retrieved from session options.
|
8
8
|
def self.cookie_values
|
9
9
|
Rails.configuration.session_options.slice(:path, :domain, :secure)
|
10
10
|
end
|
11
11
|
|
12
|
+
def remember_me_is_active?(resource)
|
13
|
+
return false unless resource.respond_to?(:remember_me)
|
14
|
+
scope = Devise::Mapping.find_scope!(resource)
|
15
|
+
_, token, generated_at = cookies.signed[remember_key(resource, scope)]
|
16
|
+
resource.remember_me?(token, generated_at)
|
17
|
+
end
|
18
|
+
|
12
19
|
# Remembers the given resource by setting up a cookie
|
13
20
|
def remember_me(resource)
|
14
21
|
return if env["devise.skip_storage"]
|
15
22
|
scope = Devise::Mapping.find_scope!(resource)
|
16
|
-
resource.remember_me!
|
23
|
+
resource.remember_me!
|
17
24
|
cookies.signed[remember_key(resource, scope)] = remember_cookie_values(resource)
|
18
25
|
end
|
19
26
|
|
@@ -6,7 +6,7 @@ module Devise
|
|
6
6
|
# Return true if the given scope is signed in session. If no scope given, return
|
7
7
|
# true if any scope is signed in. Does not run authentication hooks.
|
8
8
|
def signed_in?(scope=nil)
|
9
|
-
[
|
9
|
+
[scope || Devise.mappings.keys].flatten.any? do |_scope|
|
10
10
|
warden.authenticate?(scope: _scope)
|
11
11
|
end
|
12
12
|
end
|
@@ -90,13 +90,7 @@ module Devise
|
|
90
90
|
session.keys.grep(/^devise\./).each { |k| session.delete(k) }
|
91
91
|
end
|
92
92
|
|
93
|
-
|
94
|
-
# session.keys will return an empty array if the session is not yet loaded.
|
95
|
-
# This is a bug in both Rack and Rails.
|
96
|
-
# A call to #empty? forces the session to be loaded.
|
97
|
-
session.empty?
|
98
|
-
session.keys.grep(/^devise\./).each { |k| session.delete(k) }
|
99
|
-
end
|
93
|
+
alias :expire_data_after_sign_out! :expire_data_after_sign_in!
|
100
94
|
end
|
101
95
|
end
|
102
96
|
end
|
@@ -35,7 +35,9 @@ module Devise
|
|
35
35
|
session_key = stored_location_key_for(resource_or_scope)
|
36
36
|
uri = parse_uri(location)
|
37
37
|
if uri
|
38
|
-
|
38
|
+
path = [uri.path.sub(/\A\/+/, '/'), uri.query].compact.join('?')
|
39
|
+
path = [path, uri.fragment].compact.join('#')
|
40
|
+
session[session_key] = path
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
@@ -42,16 +42,14 @@ module Devise
|
|
42
42
|
[:path, :url].each do |path_or_url|
|
43
43
|
actions.each do |action|
|
44
44
|
action = action ? "#{action}_" : ""
|
45
|
-
method = "#{action}#{module_name}_#{path_or_url}"
|
45
|
+
method = :"#{action}#{module_name}_#{path_or_url}"
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
URL_HELPERS
|
47
|
+
define_method method do |resource_or_scope, *args|
|
48
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
49
|
+
router_name = Devise.mappings[scope].router_name
|
50
|
+
context = router_name ? send(router_name) : _devise_route_context
|
51
|
+
context.send("#{action}#{scope}_#{module_name}_#{path_or_url}", *args)
|
52
|
+
end
|
55
53
|
end
|
56
54
|
end
|
57
55
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bcrypt'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Encryptor
|
5
|
+
def self.digest(klass, password)
|
6
|
+
if klass.pepper.present?
|
7
|
+
password = "#{password}#{klass.pepper}"
|
8
|
+
end
|
9
|
+
::BCrypt::Password.create(password, cost: klass.stretches).to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.compare(klass, encrypted_password, password)
|
13
|
+
return false if encrypted_password.blank?
|
14
|
+
bcrypt = ::BCrypt::Password.new(encrypted_password)
|
15
|
+
if klass.pepper.present?
|
16
|
+
password = "#{password}#{klass.pepper}"
|
17
|
+
end
|
18
|
+
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
|
19
|
+
Devise.secure_compare(password, encrypted_password)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/devise/failure_app.rb
CHANGED
@@ -22,9 +22,12 @@ module Devise
|
|
22
22
|
@respond.call(env)
|
23
23
|
end
|
24
24
|
|
25
|
+
# Try retrieving the URL options from the parent controller (usually
|
26
|
+
# ApplicationController). Instance methods are not supported at the moment,
|
27
|
+
# so only the class-level attribute is used.
|
25
28
|
def self.default_url_options(*args)
|
26
|
-
if defined?(
|
27
|
-
|
29
|
+
if defined?(Devise.parent_controller.constantize)
|
30
|
+
Devise.parent_controller.constantize.try(:default_url_options) || {}
|
28
31
|
else
|
29
32
|
{}
|
30
33
|
end
|
@@ -48,18 +51,31 @@ module Devise
|
|
48
51
|
end
|
49
52
|
|
50
53
|
def recall
|
51
|
-
|
52
|
-
|
54
|
+
config = Rails.application.config
|
55
|
+
|
56
|
+
if config.try(:relative_url_root)
|
57
|
+
base_path = Pathname.new(config.relative_url_root)
|
58
|
+
full_path = Pathname.new(attempted_path)
|
59
|
+
|
60
|
+
env["SCRIPT_NAME"] = config.relative_url_root
|
61
|
+
env["PATH_INFO"] = '/' + full_path.relative_path_from(base_path).to_s
|
62
|
+
else
|
63
|
+
env["PATH_INFO"] = attempted_path
|
64
|
+
end
|
65
|
+
|
66
|
+
flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
|
53
67
|
self.response = recall_app(warden_options[:recall]).call(env)
|
54
68
|
end
|
55
69
|
|
56
70
|
def redirect
|
57
71
|
store_location!
|
58
|
-
if
|
59
|
-
flash
|
60
|
-
|
61
|
-
|
62
|
-
|
72
|
+
if is_flashing_format?
|
73
|
+
if flash[:timedout] && flash[:alert]
|
74
|
+
flash.keep(:timedout)
|
75
|
+
flash.keep(:alert)
|
76
|
+
else
|
77
|
+
flash[:alert] = i18n_message
|
78
|
+
end
|
63
79
|
end
|
64
80
|
redirect_to redirect_url
|
65
81
|
end
|
@@ -91,7 +107,7 @@ module Devise
|
|
91
107
|
|
92
108
|
def redirect_url
|
93
109
|
if warden_message == :timeout
|
94
|
-
flash[:timedout] = true
|
110
|
+
flash[:timedout] = true if is_flashing_format?
|
95
111
|
|
96
112
|
path = if request.get?
|
97
113
|
attempted_path
|
@@ -105,15 +121,28 @@ module Devise
|
|
105
121
|
end
|
106
122
|
end
|
107
123
|
|
124
|
+
def route(scope)
|
125
|
+
:"new_#{scope}_session_url"
|
126
|
+
end
|
127
|
+
|
108
128
|
def scope_url
|
109
129
|
opts = {}
|
110
|
-
route =
|
130
|
+
route = route(scope)
|
111
131
|
opts[:format] = request_format unless skip_format?
|
112
132
|
|
113
133
|
config = Rails.application.config
|
114
|
-
opts[:script_name] = (config.relative_url_root if config.respond_to?(:relative_url_root))
|
115
134
|
|
116
|
-
|
135
|
+
# Rails 4.2 goes into an infinite loop if opts[:script_name] is unset
|
136
|
+
if (Rails::VERSION::MAJOR >= 4) && (Rails::VERSION::MINOR >= 2)
|
137
|
+
opts[:script_name] = (config.relative_url_root if config.respond_to?(:relative_url_root))
|
138
|
+
else
|
139
|
+
if config.respond_to?(:relative_url_root) && config.relative_url_root.present?
|
140
|
+
opts[:script_name] = config.relative_url_root
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
router_name = Devise.mappings[scope].router_name || Devise.available_router_name
|
145
|
+
context = send(router_name)
|
117
146
|
|
118
147
|
if context.respond_to?(route)
|
119
148
|
context.send(route, opts)
|
@@ -205,6 +234,12 @@ module Devise
|
|
205
234
|
Devise.navigational_formats.include?(request_format)
|
206
235
|
end
|
207
236
|
|
237
|
+
# Check if flash messages should be emitted. Default is to do it on
|
238
|
+
# navigational formats
|
239
|
+
def is_flashing_format?
|
240
|
+
is_navigational_format?
|
241
|
+
end
|
242
|
+
|
208
243
|
def request_format
|
209
244
|
@request_format ||= request.format.try(:ref)
|
210
245
|
end
|
@@ -7,7 +7,8 @@ Warden::Manager.after_set_user do |record, warden, options|
|
|
7
7
|
scope = options[:scope]
|
8
8
|
env = warden.request.env
|
9
9
|
|
10
|
-
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) &&
|
10
|
+
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) &&
|
11
|
+
options[:store] != false && !env['devise.skip_timeoutable']
|
11
12
|
last_request_at = warden.session(scope)['last_request_at']
|
12
13
|
|
13
14
|
if last_request_at.is_a? Integer
|
@@ -18,13 +19,10 @@ Warden::Manager.after_set_user do |record, warden, options|
|
|
18
19
|
|
19
20
|
proxy = Devise::Hooks::Proxy.new(warden)
|
20
21
|
|
21
|
-
if record.timedout?(last_request_at) &&
|
22
|
+
if record.timedout?(last_request_at) &&
|
23
|
+
!env['devise.skip_timeout'] &&
|
24
|
+
!proxy.remember_me_is_active?(record)
|
22
25
|
Devise.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
|
23
|
-
|
24
|
-
if record.respond_to?(:expire_auth_token_on_timeout) && record.expire_auth_token_on_timeout
|
25
|
-
record.reset_authentication_token!
|
26
|
-
end
|
27
|
-
|
28
26
|
throw :warden, scope: scope, message: :timeout
|
29
27
|
end
|
30
28
|
|
data/lib/devise/mapping.rb
CHANGED
@@ -31,6 +31,7 @@ module Devise
|
|
31
31
|
# Receives an object and find a scope for it. If a scope cannot be found,
|
32
32
|
# raises an error. If a symbol is given, it's considered to be the scope.
|
33
33
|
def self.find_scope!(obj)
|
34
|
+
obj = obj.devise_scope if obj.respond_to?(:devise_scope)
|
34
35
|
case obj
|
35
36
|
when String, Symbol
|
36
37
|
return obj.to_sym
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_model/version'
|
1
2
|
require 'devise/hooks/activatable'
|
2
3
|
require 'devise/hooks/csrf_cleaner'
|
3
4
|
|
@@ -37,7 +38,7 @@ module Devise
|
|
37
38
|
# calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
|
38
39
|
# :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
|
39
40
|
#
|
40
|
-
# You overwrite this method yourself, but if you do, don't forget to call super:
|
41
|
+
# You can overwrite this method yourself, but if you do, don't forget to call super:
|
41
42
|
#
|
42
43
|
# def active_for_authentication?
|
43
44
|
# super && special_condition_is_valid?
|
@@ -95,29 +96,22 @@ module Devise
|
|
95
96
|
def authenticatable_salt
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
options[:except].concat Array(options[:force_except])
|
115
|
-
else
|
116
|
-
options[:except].concat BLACKLIST_FOR_SERIALIZATION
|
117
|
-
end
|
118
|
-
super(options)
|
119
|
-
end
|
120
|
-
RUBY
|
99
|
+
# Redefine serializable_hash in models for more secure defaults.
|
100
|
+
# By default, it removes from the serializable model all attributes that
|
101
|
+
# are *not* accessible. You can remove this default by using :force_except
|
102
|
+
# and passing a new list of attributes you want to exempt. All attributes
|
103
|
+
# given to :except will simply add names to exempt to Devise internal list.
|
104
|
+
def serializable_hash(options = nil)
|
105
|
+
options ||= {}
|
106
|
+
options[:except] = Array(options[:except])
|
107
|
+
|
108
|
+
if options[:force_except]
|
109
|
+
options[:except].concat Array(options[:force_except])
|
110
|
+
else
|
111
|
+
options[:except].concat BLACKLIST_FOR_SERIALIZATION
|
112
|
+
end
|
113
|
+
|
114
|
+
super(options)
|
121
115
|
end
|
122
116
|
|
123
117
|
protected
|
@@ -252,12 +246,12 @@ module Devise
|
|
252
246
|
to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
|
253
247
|
end
|
254
248
|
|
255
|
-
# Find
|
249
|
+
# Find or initialize a record setting an error if it can't be found.
|
256
250
|
def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
|
257
251
|
find_or_initialize_with_errors([attribute], { attribute => value }, error)
|
258
252
|
end
|
259
253
|
|
260
|
-
# Find
|
254
|
+
# Find or initialize a record with group of attributes based on a list of required attributes.
|
261
255
|
def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
|
262
256
|
attributes = attributes.slice(*required_attributes).with_indifferent_access
|
263
257
|
attributes.delete_if { |key, value| value.blank? }
|
@@ -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
|
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:
|
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
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
275
|
-
|
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
|
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
|
-
::
|
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
|
-
#
|
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
|
46
|
+
# Verifies whether a password (ie from sign in) is the user password.
|
46
47
|
def valid_password?(password)
|
47
|
-
|
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.
|
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
|
@@ -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
|
-
|
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?
|