devise 3.0.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +351 -0
- data/MIT-LICENSE +2 -1
- data/README.md +422 -130
- data/app/controllers/devise/confirmations_controller.rb +17 -6
- data/app/controllers/devise/omniauth_callbacks_controller.rb +12 -6
- data/app/controllers/devise/passwords_controller.rb +23 -8
- data/app/controllers/devise/registrations_controller.rb +70 -28
- data/app/controllers/devise/sessions_controller.rb +49 -17
- data/app/controllers/devise/unlocks_controller.rb +11 -4
- data/app/controllers/devise_controller.rb +74 -34
- data/app/helpers/devise_helper.rb +23 -18
- data/app/mailers/devise/mailer.rb +25 -10
- data/app/views/devise/confirmations/new.html.erb +9 -5
- data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
- data/app/views/devise/mailer/email_changed.html.erb +7 -0
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +1 -1
- data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
- data/app/views/devise/passwords/edit.html.erb +16 -7
- data/app/views/devise/passwords/new.html.erb +9 -5
- data/app/views/devise/registrations/edit.html.erb +29 -15
- data/app/views/devise/registrations/new.html.erb +20 -9
- data/app/views/devise/sessions/new.html.erb +19 -10
- data/app/views/devise/shared/_error_messages.html.erb +15 -0
- data/app/views/devise/shared/{_links.erb → _links.html.erb} +10 -10
- data/app/views/devise/unlocks/new.html.erb +9 -5
- data/config/locales/en.yml +26 -20
- data/lib/devise/controllers/helpers.rb +122 -125
- data/lib/devise/controllers/rememberable.rb +14 -14
- data/lib/devise/controllers/scoped_views.rb +3 -1
- data/lib/devise/controllers/sign_in_out.rb +121 -0
- data/lib/devise/controllers/store_location.rb +76 -0
- data/lib/devise/controllers/url_helpers.rb +10 -8
- data/lib/devise/delegator.rb +2 -0
- data/lib/devise/encryptor.rb +24 -0
- data/lib/devise/failure_app.rb +132 -42
- data/lib/devise/hooks/activatable.rb +7 -6
- data/lib/devise/hooks/csrf_cleaner.rb +9 -0
- data/lib/devise/hooks/forgetable.rb +3 -1
- data/lib/devise/hooks/lockable.rb +5 -3
- data/lib/devise/hooks/proxy.rb +23 -0
- data/lib/devise/hooks/rememberable.rb +7 -4
- data/lib/devise/hooks/timeoutable.rb +18 -8
- data/lib/devise/hooks/trackable.rb +3 -1
- data/lib/devise/mailers/helpers.rb +15 -18
- data/lib/devise/mapping.rb +9 -3
- data/lib/devise/models/authenticatable.rb +102 -80
- data/lib/devise/models/confirmable.rb +154 -72
- data/lib/devise/models/database_authenticatable.rb +125 -25
- data/lib/devise/models/lockable.rb +50 -29
- data/lib/devise/models/omniauthable.rb +3 -1
- data/lib/devise/models/recoverable.rb +72 -50
- data/lib/devise/models/registerable.rb +4 -0
- data/lib/devise/models/rememberable.rb +65 -32
- data/lib/devise/models/timeoutable.rb +4 -8
- data/lib/devise/models/trackable.rb +20 -4
- data/lib/devise/models/validatable.rb +16 -9
- data/lib/devise/models.rb +6 -13
- data/lib/devise/modules.rb +12 -11
- data/lib/devise/omniauth/config.rb +2 -0
- data/lib/devise/omniauth/url_helpers.rb +14 -5
- data/lib/devise/omniauth.rb +4 -5
- data/lib/devise/orm/active_record.rb +5 -1
- data/lib/devise/orm/mongoid.rb +6 -2
- data/lib/devise/parameter_filter.rb +4 -0
- data/lib/devise/parameter_sanitizer.rb +144 -34
- data/lib/devise/rails/deprecated_constant_accessor.rb +39 -0
- data/lib/devise/rails/routes.rb +191 -127
- data/lib/devise/rails/warden_compat.rb +2 -1
- data/lib/devise/rails.rb +13 -20
- data/lib/devise/secret_key_finder.rb +27 -0
- data/lib/devise/strategies/authenticatable.rb +21 -22
- data/lib/devise/strategies/base.rb +3 -1
- data/lib/devise/strategies/database_authenticatable.rb +15 -4
- data/lib/devise/strategies/rememberable.rb +15 -3
- data/lib/devise/test/controller_helpers.rb +167 -0
- data/lib/devise/test/integration_helpers.rb +63 -0
- data/lib/devise/test_helpers.rb +7 -123
- data/lib/devise/time_inflector.rb +4 -2
- data/lib/devise/token_generator.rb +32 -0
- data/lib/devise/version.rb +3 -1
- data/lib/devise.rb +124 -78
- data/lib/generators/active_record/devise_generator.rb +64 -15
- data/lib/generators/active_record/templates/migration.rb +9 -8
- data/lib/generators/active_record/templates/migration_existing.rb +9 -8
- data/lib/generators/devise/controllers_generator.rb +46 -0
- data/lib/generators/devise/devise_generator.rb +10 -6
- data/lib/generators/devise/install_generator.rb +19 -1
- data/lib/generators/devise/orm_helpers.rb +17 -9
- data/lib/generators/devise/views_generator.rb +51 -28
- data/lib/generators/mongoid/devise_generator.rb +24 -24
- data/lib/generators/templates/README +13 -12
- data/lib/generators/templates/controllers/README +14 -0
- data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
- data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
- data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
- data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
- data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
- data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
- data/lib/generators/templates/devise.rb +118 -53
- data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/email_changed.markerb +7 -0
- 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/confirmations/new.html.erb +6 -2
- data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +12 -4
- data/lib/generators/templates/simple_form_for/passwords/new.html.erb +5 -2
- data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +14 -6
- data/lib/generators/templates/simple_form_for/registrations/new.html.erb +12 -4
- data/lib/generators/templates/simple_form_for/sessions/new.html.erb +11 -6
- data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +5 -2
- metadata +73 -294
- data/.gitignore +0 -10
- data/.travis.yml +0 -20
- data/.yardopts +0 -9
- data/CHANGELOG.rdoc +0 -941
- data/CONTRIBUTING.md +0 -14
- data/Gemfile +0 -31
- data/Gemfile.lock +0 -159
- data/Rakefile +0 -35
- data/app/views/devise/_links.erb +0 -3
- data/devise.gemspec +0 -26
- data/devise.png +0 -0
- data/gemfiles/Gemfile.rails-3.2.x +0 -31
- data/gemfiles/Gemfile.rails-3.2.x.lock +0 -156
- data/lib/devise/models/token_authenticatable.rb +0 -89
- data/lib/devise/strategies/token_authenticatable.rb +0 -91
- data/test/controllers/custom_strategy_test.rb +0 -62
- data/test/controllers/helpers_test.rb +0 -253
- data/test/controllers/internal_helpers_test.rb +0 -120
- data/test/controllers/passwords_controller_test.rb +0 -32
- data/test/controllers/sessions_controller_test.rb +0 -99
- data/test/controllers/url_helpers_test.rb +0 -59
- data/test/delegator_test.rb +0 -19
- data/test/devise_test.rb +0 -83
- data/test/failure_app_test.rb +0 -221
- data/test/generators/active_record_generator_test.rb +0 -73
- data/test/generators/devise_generator_test.rb +0 -39
- data/test/generators/install_generator_test.rb +0 -13
- data/test/generators/mongoid_generator_test.rb +0 -23
- data/test/generators/views_generator_test.rb +0 -67
- data/test/helpers/devise_helper_test.rb +0 -51
- data/test/integration/authenticatable_test.rb +0 -699
- data/test/integration/confirmable_test.rb +0 -299
- data/test/integration/database_authenticatable_test.rb +0 -84
- data/test/integration/http_authenticatable_test.rb +0 -115
- data/test/integration/lockable_test.rb +0 -242
- data/test/integration/omniauthable_test.rb +0 -133
- data/test/integration/recoverable_test.rb +0 -335
- data/test/integration/registerable_test.rb +0 -349
- data/test/integration/rememberable_test.rb +0 -165
- data/test/integration/timeoutable_test.rb +0 -150
- data/test/integration/token_authenticatable_test.rb +0 -205
- data/test/integration/trackable_test.rb +0 -92
- data/test/mailers/confirmation_instructions_test.rb +0 -111
- data/test/mailers/reset_password_instructions_test.rb +0 -92
- data/test/mailers/unlock_instructions_test.rb +0 -87
- data/test/mapping_test.rb +0 -127
- data/test/models/authenticatable_test.rb +0 -13
- data/test/models/confirmable_test.rb +0 -452
- data/test/models/database_authenticatable_test.rb +0 -226
- data/test/models/lockable_test.rb +0 -282
- data/test/models/omniauthable_test.rb +0 -7
- data/test/models/recoverable_test.rb +0 -222
- data/test/models/registerable_test.rb +0 -7
- data/test/models/rememberable_test.rb +0 -175
- data/test/models/serializable_test.rb +0 -49
- data/test/models/timeoutable_test.rb +0 -46
- data/test/models/token_authenticatable_test.rb +0 -55
- data/test/models/trackable_test.rb +0 -13
- data/test/models/validatable_test.rb +0 -127
- data/test/models_test.rb +0 -163
- data/test/omniauth/config_test.rb +0 -57
- data/test/omniauth/url_helpers_test.rb +0 -54
- data/test/orm/active_record.rb +0 -10
- data/test/orm/mongoid.rb +0 -13
- data/test/parameter_sanitizer_test.rb +0 -58
- data/test/rails_app/Rakefile +0 -6
- data/test/rails_app/app/active_record/admin.rb +0 -6
- data/test/rails_app/app/active_record/shim.rb +0 -2
- data/test/rails_app/app/active_record/user.rb +0 -6
- data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
- data/test/rails_app/app/controllers/admins_controller.rb +0 -11
- data/test/rails_app/app/controllers/application_controller.rb +0 -9
- data/test/rails_app/app/controllers/home_controller.rb +0 -25
- data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
- data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
- data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
- data/test/rails_app/app/controllers/users_controller.rb +0 -31
- data/test/rails_app/app/helpers/application_helper.rb +0 -3
- data/test/rails_app/app/mailers/users/mailer.rb +0 -12
- data/test/rails_app/app/mongoid/admin.rb +0 -29
- data/test/rails_app/app/mongoid/shim.rb +0 -23
- data/test/rails_app/app/mongoid/user.rb +0 -42
- data/test/rails_app/app/views/admins/index.html.erb +0 -1
- data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
- data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
- data/test/rails_app/app/views/home/index.html.erb +0 -1
- data/test/rails_app/app/views/home/join.html.erb +0 -1
- data/test/rails_app/app/views/home/private.html.erb +0 -1
- data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
- data/test/rails_app/app/views/layouts/application.html.erb +0 -24
- data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
- data/test/rails_app/app/views/users/index.html.erb +0 -1
- data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
- data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
- data/test/rails_app/bin/bundle +0 -3
- data/test/rails_app/bin/rails +0 -4
- data/test/rails_app/bin/rake +0 -4
- data/test/rails_app/config/application.rb +0 -40
- data/test/rails_app/config/boot.rb +0 -8
- data/test/rails_app/config/database.yml +0 -18
- data/test/rails_app/config/environment.rb +0 -5
- data/test/rails_app/config/environments/development.rb +0 -34
- data/test/rails_app/config/environments/production.rb +0 -84
- data/test/rails_app/config/environments/test.rb +0 -36
- data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
- data/test/rails_app/config/initializers/devise.rb +0 -178
- data/test/rails_app/config/initializers/inflections.rb +0 -2
- data/test/rails_app/config/initializers/secret_token.rb +0 -8
- data/test/rails_app/config/initializers/session_store.rb +0 -1
- data/test/rails_app/config/routes.rb +0 -104
- data/test/rails_app/config.ru +0 -4
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -74
- data/test/rails_app/db/schema.rb +0 -52
- data/test/rails_app/lib/shared_admin.rb +0 -14
- data/test/rails_app/lib/shared_user.rb +0 -25
- data/test/rails_app/public/404.html +0 -26
- data/test/rails_app/public/422.html +0 -26
- data/test/rails_app/public/500.html +0 -26
- data/test/rails_app/public/favicon.ico +0 -0
- data/test/routes_test.rb +0 -250
- data/test/support/assertions.rb +0 -40
- data/test/support/helpers.rb +0 -91
- data/test/support/integration.rb +0 -92
- data/test/support/locale/en.yml +0 -4
- data/test/support/webrat/integrations/rails.rb +0 -24
- data/test/test_helper.rb +0 -34
- data/test/test_helpers_test.rb +0 -151
- data/test/test_models.rb +0 -26
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "devise/hooks/lockable"
|
|
2
4
|
|
|
3
5
|
module Devise
|
|
@@ -7,7 +9,7 @@ module Devise
|
|
|
7
9
|
# blocked: email and time. The former will send an email to the user when
|
|
8
10
|
# the lock happens, containing a link to unlock its account. The second
|
|
9
11
|
# will unlock the user automatically after some configured time (ie 2.hours).
|
|
10
|
-
# It's also possible to
|
|
12
|
+
# It's also possible to set up lockable to use both email and time strategies.
|
|
11
13
|
#
|
|
12
14
|
# == Options
|
|
13
15
|
#
|
|
@@ -22,7 +24,7 @@ module Devise
|
|
|
22
24
|
module Lockable
|
|
23
25
|
extend ActiveSupport::Concern
|
|
24
26
|
|
|
25
|
-
delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :
|
|
27
|
+
delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, to: "self.class"
|
|
26
28
|
|
|
27
29
|
def self.required_fields(klass)
|
|
28
30
|
attributes = []
|
|
@@ -34,14 +36,16 @@ module Devise
|
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
# Lock a user setting its locked_at to actual time.
|
|
37
|
-
|
|
39
|
+
# * +opts+: Hash options if you don't want to send email
|
|
40
|
+
# when you lock access, you could pass the next hash
|
|
41
|
+
# `{ send_instructions: false } as option`.
|
|
42
|
+
def lock_access!(opts = { })
|
|
38
43
|
self.locked_at = Time.now.utc
|
|
39
44
|
|
|
40
|
-
if unlock_strategy_enabled?(:email)
|
|
41
|
-
generate_unlock_token!
|
|
45
|
+
if unlock_strategy_enabled?(:email) && opts.fetch(:send_instructions, true)
|
|
42
46
|
send_unlock_instructions
|
|
43
47
|
else
|
|
44
|
-
save(:
|
|
48
|
+
save(validate: false)
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
|
|
@@ -50,7 +54,15 @@ module Devise
|
|
|
50
54
|
self.locked_at = nil
|
|
51
55
|
self.failed_attempts = 0 if respond_to?(:failed_attempts=)
|
|
52
56
|
self.unlock_token = nil if respond_to?(:unlock_token=)
|
|
53
|
-
save(:
|
|
57
|
+
save(validate: false)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Resets failed attempts counter to 0.
|
|
61
|
+
def reset_failed_attempts!
|
|
62
|
+
if respond_to?(:failed_attempts) && !failed_attempts.to_i.zero?
|
|
63
|
+
self.failed_attempts = 0
|
|
64
|
+
save(validate: false)
|
|
65
|
+
end
|
|
54
66
|
end
|
|
55
67
|
|
|
56
68
|
# Verifies whether a user is locked or not.
|
|
@@ -60,11 +72,15 @@ module Devise
|
|
|
60
72
|
|
|
61
73
|
# Send unlock instructions by email
|
|
62
74
|
def send_unlock_instructions
|
|
63
|
-
|
|
75
|
+
raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
|
|
76
|
+
self.unlock_token = enc
|
|
77
|
+
save(validate: false)
|
|
78
|
+
send_devise_notification(:unlock_instructions, raw, {})
|
|
79
|
+
raw
|
|
64
80
|
end
|
|
65
81
|
|
|
66
82
|
# Resend the unlock instructions if the user is locked.
|
|
67
|
-
def
|
|
83
|
+
def resend_unlock_instructions
|
|
68
84
|
if_access_locked { send_unlock_instructions }
|
|
69
85
|
end
|
|
70
86
|
|
|
@@ -93,24 +109,30 @@ module Devise
|
|
|
93
109
|
if super && !access_locked?
|
|
94
110
|
true
|
|
95
111
|
else
|
|
96
|
-
|
|
97
|
-
self.failed_attempts += 1
|
|
112
|
+
increment_failed_attempts
|
|
98
113
|
if attempts_exceeded?
|
|
99
114
|
lock_access! unless access_locked?
|
|
100
115
|
else
|
|
101
|
-
save(:
|
|
116
|
+
save(validate: false)
|
|
102
117
|
end
|
|
103
118
|
false
|
|
104
119
|
end
|
|
105
120
|
end
|
|
106
121
|
|
|
122
|
+
def increment_failed_attempts
|
|
123
|
+
self.class.increment_counter(:failed_attempts, id)
|
|
124
|
+
reload
|
|
125
|
+
end
|
|
126
|
+
|
|
107
127
|
def unauthenticated_message
|
|
108
128
|
# If set to paranoid mode, do not show the locked message because it
|
|
109
129
|
# leaks the existence of an account.
|
|
110
130
|
if Devise.paranoid
|
|
111
131
|
super
|
|
112
|
-
elsif lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
|
|
132
|
+
elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
|
|
113
133
|
:locked
|
|
134
|
+
elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning
|
|
135
|
+
:last_attempt
|
|
114
136
|
else
|
|
115
137
|
super
|
|
116
138
|
end
|
|
@@ -119,16 +141,11 @@ module Devise
|
|
|
119
141
|
protected
|
|
120
142
|
|
|
121
143
|
def attempts_exceeded?
|
|
122
|
-
self.failed_attempts
|
|
144
|
+
self.failed_attempts >= self.class.maximum_attempts
|
|
123
145
|
end
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
self.unlock_token = self.class.unlock_token
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def generate_unlock_token!
|
|
131
|
-
generate_unlock_token && save(:validate => false)
|
|
147
|
+
def last_attempt?
|
|
148
|
+
self.failed_attempts == self.class.maximum_attempts - 1
|
|
132
149
|
end
|
|
133
150
|
|
|
134
151
|
# Tells if the lock is expired if :time unlock strategy is active
|
|
@@ -152,13 +169,16 @@ module Devise
|
|
|
152
169
|
end
|
|
153
170
|
|
|
154
171
|
module ClassMethods
|
|
172
|
+
# List of strategies that are enabled/supported if :both is used.
|
|
173
|
+
BOTH_STRATEGIES = [:time, :email]
|
|
174
|
+
|
|
155
175
|
# Attempt to find a user by its unlock keys. If a record is found, send new
|
|
156
176
|
# unlock instructions to it. If not user is found, returns a new user
|
|
157
177
|
# with an email not found error.
|
|
158
178
|
# Options must contain the user's unlock keys
|
|
159
|
-
def send_unlock_instructions(attributes={})
|
|
179
|
+
def send_unlock_instructions(attributes = {})
|
|
160
180
|
lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
|
|
161
|
-
lockable.
|
|
181
|
+
lockable.resend_unlock_instructions if lockable.persisted?
|
|
162
182
|
lockable
|
|
163
183
|
end
|
|
164
184
|
|
|
@@ -167,14 +187,19 @@ module Devise
|
|
|
167
187
|
# If the user is not locked, creates an error for the user
|
|
168
188
|
# Options must have the unlock_token
|
|
169
189
|
def unlock_access_by_token(unlock_token)
|
|
190
|
+
original_token = unlock_token
|
|
191
|
+
unlock_token = Devise.token_generator.digest(self, :unlock_token, unlock_token)
|
|
192
|
+
|
|
170
193
|
lockable = find_or_initialize_with_error_by(:unlock_token, unlock_token)
|
|
171
194
|
lockable.unlock_access! if lockable.persisted?
|
|
195
|
+
lockable.unlock_token = original_token
|
|
172
196
|
lockable
|
|
173
197
|
end
|
|
174
198
|
|
|
175
199
|
# Is the unlock enabled for the given unlock strategy?
|
|
176
200
|
def unlock_strategy_enabled?(strategy)
|
|
177
|
-
|
|
201
|
+
self.unlock_strategy == strategy ||
|
|
202
|
+
(self.unlock_strategy == :both && BOTH_STRATEGIES.include?(strategy))
|
|
178
203
|
end
|
|
179
204
|
|
|
180
205
|
# Is the lock enabled for the given lock strategy?
|
|
@@ -182,11 +207,7 @@ module Devise
|
|
|
182
207
|
self.lock_strategy == strategy
|
|
183
208
|
end
|
|
184
209
|
|
|
185
|
-
|
|
186
|
-
Devise.friendly_token
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
|
|
210
|
+
Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys, :last_attempt_warning)
|
|
190
211
|
end
|
|
191
212
|
end
|
|
192
213
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'devise/omniauth'
|
|
2
4
|
|
|
3
5
|
module Devise
|
|
@@ -10,7 +12,7 @@ module Devise
|
|
|
10
12
|
#
|
|
11
13
|
# * +omniauth_providers+: Which providers are available to this model. It expects an array:
|
|
12
14
|
#
|
|
13
|
-
# devise_for :database_authenticatable, :omniauthable, :
|
|
15
|
+
# devise_for :database_authenticatable, :omniauthable, omniauth_providers: [:twitter]
|
|
14
16
|
#
|
|
15
17
|
module Omniauthable
|
|
16
18
|
extend ActiveSupport::Concern
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Devise
|
|
2
4
|
module Models
|
|
3
5
|
|
|
@@ -8,15 +10,13 @@ module Devise
|
|
|
8
10
|
# Recoverable adds the following options to devise_for:
|
|
9
11
|
#
|
|
10
12
|
# * +reset_password_keys+: the keys you want to use when recovering the password for an account
|
|
13
|
+
# * +reset_password_within+: the time period within which the password must be reset or the token expires.
|
|
14
|
+
# * +sign_in_after_reset_password+: whether or not to sign in the user automatically after a password reset.
|
|
11
15
|
#
|
|
12
16
|
# == Examples
|
|
13
17
|
#
|
|
14
18
|
# # resets the user password and save the record, true if valid passwords are given, otherwise false
|
|
15
|
-
# User.find(1).reset_password
|
|
16
|
-
#
|
|
17
|
-
# # only resets the user password, without saving the record
|
|
18
|
-
# user = User.find(1)
|
|
19
|
-
# user.reset_password('password123', 'password123')
|
|
19
|
+
# User.find(1).reset_password('password123', 'password123')
|
|
20
20
|
#
|
|
21
21
|
# # creates a new token and send it with instructions about how to reset the password
|
|
22
22
|
# User.find(1).send_reset_password_instructions
|
|
@@ -28,31 +28,32 @@ module Devise
|
|
|
28
28
|
[:reset_password_sent_at, :reset_password_token]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
included do
|
|
32
|
+
before_update :clear_reset_password_token, if: :clear_reset_password_token?
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
# Update password saving the record and clearing token. Returns true if
|
|
32
36
|
# the passwords are valid and the record was saved, false otherwise.
|
|
33
|
-
def reset_password
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
def reset_password(new_password, new_password_confirmation)
|
|
38
|
+
if new_password.present?
|
|
39
|
+
self.password = new_password
|
|
40
|
+
self.password_confirmation = new_password_confirmation
|
|
41
|
+
save
|
|
42
|
+
else
|
|
43
|
+
errors.add(:password, :blank)
|
|
44
|
+
false
|
|
40
45
|
end
|
|
41
|
-
|
|
42
|
-
save
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
# Resets reset password token and send reset password instructions by email
|
|
48
|
+
# Resets reset password token and send reset password instructions by email.
|
|
49
|
+
# Returns the token sent in the e-mail.
|
|
46
50
|
def send_reset_password_instructions
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
token = set_reset_password_token
|
|
52
|
+
send_reset_password_instructions_notification(token)
|
|
53
|
+
|
|
54
|
+
token
|
|
49
55
|
end
|
|
50
|
-
|
|
51
|
-
# Generate reset password token unless already exists and save the record.
|
|
52
|
-
def ensure_reset_password_token!
|
|
53
|
-
generate_reset_password_token! if should_generate_reset_token?
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
+
|
|
56
57
|
# Checks if the reset password token sent is within the limit time.
|
|
57
58
|
# We do this by calculating if the difference between today and the
|
|
58
59
|
# sending date does not exceed the confirm in time configured.
|
|
@@ -74,71 +75,92 @@ module Devise
|
|
|
74
75
|
# reset_password_period_valid? # will always return false
|
|
75
76
|
#
|
|
76
77
|
def reset_password_period_valid?
|
|
77
|
-
reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
|
|
78
|
+
reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago.utc
|
|
78
79
|
end
|
|
79
80
|
|
|
80
81
|
protected
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
# Removes reset_password token
|
|
84
|
+
def clear_reset_password_token
|
|
85
|
+
self.reset_password_token = nil
|
|
86
|
+
self.reset_password_sent_at = nil
|
|
84
87
|
end
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
def set_reset_password_token
|
|
90
|
+
raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
|
|
91
|
+
|
|
92
|
+
self.reset_password_token = enc
|
|
89
93
|
self.reset_password_sent_at = Time.now.utc
|
|
90
|
-
|
|
94
|
+
save(validate: false)
|
|
95
|
+
raw
|
|
91
96
|
end
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def generate_reset_password_token!
|
|
96
|
-
generate_reset_password_token && save(:validate => false)
|
|
98
|
+
def send_reset_password_instructions_notification(token)
|
|
99
|
+
send_devise_notification(:reset_password_instructions, token, {})
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
if Devise.activerecord51?
|
|
103
|
+
def clear_reset_password_token?
|
|
104
|
+
encrypted_password_changed = respond_to?(:will_save_change_to_encrypted_password?) && will_save_change_to_encrypted_password?
|
|
105
|
+
authentication_keys_changed = self.class.authentication_keys.any? do |attribute|
|
|
106
|
+
respond_to?("will_save_change_to_#{attribute}?") && send("will_save_change_to_#{attribute}?")
|
|
107
|
+
end
|
|
104
108
|
|
|
105
|
-
|
|
109
|
+
authentication_keys_changed || encrypted_password_changed
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
def clear_reset_password_token?
|
|
113
|
+
encrypted_password_changed = respond_to?(:encrypted_password_changed?) && encrypted_password_changed?
|
|
114
|
+
authentication_keys_changed = self.class.authentication_keys.any? do |attribute|
|
|
115
|
+
respond_to?("#{attribute}_changed?") && send("#{attribute}_changed?")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
authentication_keys_changed || encrypted_password_changed
|
|
119
|
+
end
|
|
106
120
|
end
|
|
107
121
|
|
|
108
122
|
module ClassMethods
|
|
123
|
+
# Attempt to find a user by password reset token. If a user is found, return it
|
|
124
|
+
# If a user is not found, return nil
|
|
125
|
+
def with_reset_password_token(token)
|
|
126
|
+
reset_password_token = Devise.token_generator.digest(self, :reset_password_token, token)
|
|
127
|
+
to_adapter.find_first(reset_password_token: reset_password_token)
|
|
128
|
+
end
|
|
129
|
+
|
|
109
130
|
# Attempt to find a user by its email. If a record is found, send new
|
|
110
131
|
# password instructions to it. If user is not found, returns a new user
|
|
111
132
|
# with an email not found error.
|
|
112
133
|
# Attributes must contain the user's email
|
|
113
|
-
def send_reset_password_instructions(attributes={})
|
|
134
|
+
def send_reset_password_instructions(attributes = {})
|
|
114
135
|
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
|
|
115
136
|
recoverable.send_reset_password_instructions if recoverable.persisted?
|
|
116
137
|
recoverable
|
|
117
138
|
end
|
|
118
139
|
|
|
119
|
-
# Generate a token checking if one does not already exist in the database.
|
|
120
|
-
def reset_password_token
|
|
121
|
-
generate_token(:reset_password_token)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
140
|
# Attempt to find a user by its reset_password_token to reset its
|
|
125
141
|
# password. If a user is found and token is still valid, reset its password and automatically
|
|
126
142
|
# try saving the record. If not user is found, returns a new user
|
|
127
143
|
# containing an error in reset_password_token attribute.
|
|
128
144
|
# Attributes must contain reset_password_token, password and confirmation
|
|
129
|
-
def reset_password_by_token(attributes={})
|
|
130
|
-
|
|
145
|
+
def reset_password_by_token(attributes = {})
|
|
146
|
+
original_token = attributes[:reset_password_token]
|
|
147
|
+
reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
|
|
148
|
+
|
|
149
|
+
recoverable = find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
|
|
150
|
+
|
|
131
151
|
if recoverable.persisted?
|
|
132
152
|
if recoverable.reset_password_period_valid?
|
|
133
|
-
recoverable.reset_password
|
|
153
|
+
recoverable.reset_password(attributes[:password], attributes[:password_confirmation])
|
|
134
154
|
else
|
|
135
155
|
recoverable.errors.add(:reset_password_token, :expired)
|
|
136
156
|
end
|
|
137
157
|
end
|
|
158
|
+
|
|
159
|
+
recoverable.reset_password_token = original_token if recoverable.reset_password_token.present?
|
|
138
160
|
recoverable
|
|
139
161
|
end
|
|
140
162
|
|
|
141
|
-
Devise::Models.config(self, :reset_password_keys, :reset_password_within)
|
|
163
|
+
Devise::Models.config(self, :reset_password_keys, :reset_password_within, :sign_in_after_reset_password)
|
|
142
164
|
end
|
|
143
165
|
end
|
|
144
166
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Devise
|
|
2
4
|
module Models
|
|
3
5
|
# Registerable is responsible for everything related to registering a new
|
|
@@ -19,6 +21,8 @@ module Devise
|
|
|
19
21
|
def new_with_session(params, session)
|
|
20
22
|
new(params)
|
|
21
23
|
end
|
|
24
|
+
|
|
25
|
+
Devise::Models.config(self, :sign_in_after_change_password)
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'devise/strategies/rememberable'
|
|
2
4
|
require 'devise/hooks/rememberable'
|
|
3
5
|
require 'devise/hooks/forgetable'
|
|
4
6
|
|
|
5
7
|
module Devise
|
|
6
8
|
module Models
|
|
7
|
-
# Rememberable manages generating and clearing token for
|
|
9
|
+
# Rememberable manages generating and clearing token for remembering the user
|
|
8
10
|
# from a saved cookie. Rememberable also has utility methods for dealing
|
|
9
11
|
# with serializing the user into the cookie and back from the cookie, trying
|
|
10
12
|
# to lookup the record based on the saved information.
|
|
@@ -17,7 +19,7 @@ module Devise
|
|
|
17
19
|
#
|
|
18
20
|
# * +remember_for+: the time you want the user will be remembered without
|
|
19
21
|
# asking for credentials. After this time the user will be blocked and
|
|
20
|
-
# will have to enter
|
|
22
|
+
# will have to enter their credentials again. This configuration is also
|
|
21
23
|
# used to calculate the expires time for the cookie created to remember
|
|
22
24
|
# the user. By default remember_for is 2.weeks.
|
|
23
25
|
#
|
|
@@ -39,46 +41,42 @@ module Devise
|
|
|
39
41
|
module Rememberable
|
|
40
42
|
extend ActiveSupport::Concern
|
|
41
43
|
|
|
42
|
-
attr_accessor :remember_me
|
|
44
|
+
attr_accessor :remember_me
|
|
43
45
|
|
|
44
46
|
def self.required_fields(klass)
|
|
45
47
|
[:remember_created_at]
|
|
46
48
|
end
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
self.remember_created_at = Time.now.utc if generate_remember_timestamp?(extend_period)
|
|
53
|
-
save(:validate => false) if self.changed?
|
|
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
|
+
save(validate: false) if self.changed?
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# If the record is persisted, remove the remember token (but only if
|
|
57
57
|
# it exists), and save the record without validations.
|
|
58
58
|
def forget_me!
|
|
59
59
|
return unless persisted?
|
|
60
|
-
self.remember_token = nil if respond_to?(:remember_token
|
|
61
|
-
self.remember_created_at = nil
|
|
62
|
-
save(:
|
|
60
|
+
self.remember_token = nil if respond_to?(:remember_token)
|
|
61
|
+
self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out
|
|
62
|
+
save(validate: false)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
remember_created_at.nil? || (remember_expires_at <= Time.now.utc)
|
|
65
|
+
def remember_expires_at
|
|
66
|
+
self.class.remember_for.from_now
|
|
68
67
|
end
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
remember_created_at + self.class.remember_for
|
|
69
|
+
def extend_remember_period
|
|
70
|
+
self.class.extend_remember_period
|
|
73
71
|
end
|
|
74
72
|
|
|
75
73
|
def rememberable_value
|
|
76
74
|
if respond_to?(:remember_token)
|
|
77
75
|
remember_token
|
|
78
|
-
elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt)
|
|
76
|
+
elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt.presence)
|
|
79
77
|
salt
|
|
80
78
|
else
|
|
81
|
-
raise "
|
|
79
|
+
raise "authenticatable_salt returned nil for the #{self.class.name} model. " \
|
|
82
80
|
"In order to use rememberable, you must ensure a password is always set " \
|
|
83
81
|
"or have a remember_token column in your model or implement your own " \
|
|
84
82
|
"rememberable_value in the model with custom logic."
|
|
@@ -89,36 +87,71 @@ module Devise
|
|
|
89
87
|
self.class.rememberable_options
|
|
90
88
|
end
|
|
91
89
|
|
|
92
|
-
|
|
90
|
+
# A callback initiated after successfully being remembered. This can be
|
|
91
|
+
# used to insert your own logic that is only run after the user is
|
|
92
|
+
# remembered.
|
|
93
|
+
#
|
|
94
|
+
# Example:
|
|
95
|
+
#
|
|
96
|
+
# def after_remembered
|
|
97
|
+
# self.update_attribute(:invite_code, nil)
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
def after_remembered
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def remember_me?(token, generated_at)
|
|
104
|
+
# TODO: Normalize the JSON type coercion along with the Timeoutable hook
|
|
105
|
+
# in a single place https://github.com/heartcombo/devise/blob/ffe9d6d406e79108cf32a2c6a1d0b3828849c40b/lib/devise/hooks/timeoutable.rb#L14-L18
|
|
106
|
+
if generated_at.is_a?(String)
|
|
107
|
+
generated_at = time_from_json(generated_at)
|
|
108
|
+
end
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
# The token is only valid if:
|
|
111
|
+
# 1. we have a date
|
|
112
|
+
# 2. the current time does not pass the expiry period
|
|
113
|
+
# 3. the record has a remember_created_at date
|
|
114
|
+
# 4. the token date is bigger than the remember_created_at
|
|
115
|
+
# 5. the token matches
|
|
116
|
+
generated_at.is_a?(Time) &&
|
|
117
|
+
(self.class.remember_for.ago < generated_at) &&
|
|
118
|
+
(generated_at > (remember_created_at || Time.now).utc) &&
|
|
119
|
+
Devise.secure_compare(rememberable_value, token)
|
|
96
120
|
end
|
|
97
121
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def
|
|
101
|
-
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def time_from_json(value)
|
|
125
|
+
if value =~ /\A\d+\.\d+\Z/
|
|
126
|
+
Time.at(value.to_f)
|
|
127
|
+
else
|
|
128
|
+
Time.parse(value) rescue nil
|
|
129
|
+
end
|
|
102
130
|
end
|
|
103
131
|
|
|
104
132
|
module ClassMethods
|
|
105
133
|
# Create the cookie key using the record id and remember_token
|
|
106
134
|
def serialize_into_cookie(record)
|
|
107
|
-
[record.to_key, record.rememberable_value]
|
|
135
|
+
[record.to_key, record.rememberable_value, Time.now.utc.to_f.to_s]
|
|
108
136
|
end
|
|
109
137
|
|
|
110
138
|
# Recreate the user based on the stored cookie
|
|
111
|
-
def serialize_from_cookie(
|
|
139
|
+
def serialize_from_cookie(*args)
|
|
140
|
+
id, token, generated_at = *args
|
|
141
|
+
|
|
112
142
|
record = to_adapter.get(id)
|
|
113
|
-
record if record && record.
|
|
143
|
+
record if record && record.remember_me?(token, generated_at)
|
|
114
144
|
end
|
|
115
145
|
|
|
116
146
|
# Generate a token checking if one does not already exist in the database.
|
|
117
147
|
def remember_token #:nodoc:
|
|
118
|
-
|
|
148
|
+
loop do
|
|
149
|
+
token = Devise.friendly_token
|
|
150
|
+
break token unless to_adapter.find_first({ remember_token: token })
|
|
151
|
+
end
|
|
119
152
|
end
|
|
120
153
|
|
|
121
|
-
Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options)
|
|
154
|
+
Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options, :expire_all_remember_me_on_sign_out)
|
|
122
155
|
end
|
|
123
156
|
end
|
|
124
157
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'devise/hooks/timeoutable'
|
|
2
4
|
|
|
3
5
|
module Devise
|
|
4
6
|
module Models
|
|
5
|
-
# Timeoutable takes care of
|
|
7
|
+
# Timeoutable takes care of verifying whether a user session has already
|
|
6
8
|
# expired or not. When a session expires after the configured time, the user
|
|
7
|
-
# will be asked for credentials again, it means,
|
|
9
|
+
# will be asked for credentials again, it means, they will be redirected
|
|
8
10
|
# to the sign in page.
|
|
9
11
|
#
|
|
10
12
|
# == Options
|
|
@@ -26,7 +28,6 @@ module Devise
|
|
|
26
28
|
|
|
27
29
|
# Checks whether the user session has expired based on configured time.
|
|
28
30
|
def timedout?(last_access)
|
|
29
|
-
return false if remember_exists_and_not_expired?
|
|
30
31
|
!timeout_in.nil? && last_access && last_access <= timeout_in.ago
|
|
31
32
|
end
|
|
32
33
|
|
|
@@ -36,11 +37,6 @@ module Devise
|
|
|
36
37
|
|
|
37
38
|
private
|
|
38
39
|
|
|
39
|
-
def remember_exists_and_not_expired?
|
|
40
|
-
return false unless respond_to?(:remember_created_at)
|
|
41
|
-
remember_created_at && !remember_expired?
|
|
42
|
-
end
|
|
43
|
-
|
|
44
40
|
module ClassMethods
|
|
45
41
|
Devise::Models.config(self, :timeout_in)
|
|
46
42
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'devise/hooks/trackable'
|
|
2
4
|
|
|
3
5
|
module Devise
|
|
@@ -15,21 +17,35 @@ module Devise
|
|
|
15
17
|
[:current_sign_in_at, :current_sign_in_ip, :last_sign_in_at, :last_sign_in_ip, :sign_in_count]
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
def update_tracked_fields
|
|
20
|
+
def update_tracked_fields(request)
|
|
19
21
|
old_current, new_current = self.current_sign_in_at, Time.now.utc
|
|
20
22
|
self.last_sign_in_at = old_current || new_current
|
|
21
23
|
self.current_sign_in_at = new_current
|
|
22
24
|
|
|
23
|
-
old_current, new_current = self.current_sign_in_ip, request
|
|
25
|
+
old_current, new_current = self.current_sign_in_ip, extract_ip_from(request)
|
|
24
26
|
self.last_sign_in_ip = old_current || new_current
|
|
25
27
|
self.current_sign_in_ip = new_current
|
|
26
28
|
|
|
27
29
|
self.sign_in_count ||= 0
|
|
28
30
|
self.sign_in_count += 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update_tracked_fields!(request)
|
|
34
|
+
# We have to check if the user is already persisted before running
|
|
35
|
+
# `save` here because invalid users can be saved if we don't.
|
|
36
|
+
# See https://github.com/heartcombo/devise/issues/4673 for more details.
|
|
37
|
+
return if new_record?
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
update_tracked_fields(request)
|
|
40
|
+
save(validate: false)
|
|
32
41
|
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def extract_ip_from(request)
|
|
46
|
+
request.remote_ip
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
end
|
|
34
50
|
end
|
|
35
51
|
end
|