devise 3.5.2 → 4.7.1

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 (255) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +259 -1086
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +254 -67
  5. data/app/controllers/devise/confirmations_controller.rb +3 -1
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +8 -6
  7. data/app/controllers/devise/passwords_controller.rb +8 -4
  8. data/app/controllers/devise/registrations_controller.rb +39 -18
  9. data/app/controllers/devise/sessions_controller.rb +9 -7
  10. data/app/controllers/devise/unlocks_controller.rb +4 -2
  11. data/app/controllers/devise_controller.rb +23 -10
  12. data/app/helpers/devise_helper.rb +12 -19
  13. data/app/mailers/devise/mailer.rb +10 -0
  14. data/app/views/devise/confirmations/new.html.erb +2 -2
  15. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  16. data/app/views/devise/mailer/password_change.html.erb +3 -0
  17. data/app/views/devise/passwords/edit.html.erb +3 -3
  18. data/app/views/devise/passwords/new.html.erb +2 -2
  19. data/app/views/devise/registrations/edit.html.erb +9 -5
  20. data/app/views/devise/registrations/new.html.erb +4 -4
  21. data/app/views/devise/sessions/new.html.erb +4 -4
  22. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  23. data/app/views/devise/shared/_links.html.erb +8 -8
  24. data/app/views/devise/unlocks/new.html.erb +2 -2
  25. data/config/locales/en.yml +6 -1
  26. data/lib/devise/controllers/helpers.rb +35 -26
  27. data/lib/devise/controllers/rememberable.rb +11 -2
  28. data/lib/devise/controllers/scoped_views.rb +2 -0
  29. data/lib/devise/controllers/sign_in_out.rb +34 -11
  30. data/lib/devise/controllers/store_location.rb +25 -7
  31. data/lib/devise/controllers/url_helpers.rb +2 -0
  32. data/lib/devise/delegator.rb +2 -0
  33. data/lib/devise/encryptor.rb +6 -4
  34. data/lib/devise/failure_app.rb +84 -32
  35. data/lib/devise/hooks/activatable.rb +2 -0
  36. data/lib/devise/hooks/csrf_cleaner.rb +2 -0
  37. data/lib/devise/hooks/forgetable.rb +2 -0
  38. data/lib/devise/hooks/lockable.rb +6 -1
  39. data/lib/devise/hooks/proxy.rb +3 -1
  40. data/lib/devise/hooks/rememberable.rb +2 -0
  41. data/lib/devise/hooks/timeoutable.rb +7 -3
  42. data/lib/devise/hooks/trackable.rb +2 -0
  43. data/lib/devise/mailers/helpers.rb +7 -4
  44. data/lib/devise/mapping.rb +2 -0
  45. data/lib/devise/models/authenticatable.rb +51 -26
  46. data/lib/devise/models/confirmable.rb +89 -27
  47. data/lib/devise/models/database_authenticatable.rb +97 -20
  48. data/lib/devise/models/lockable.rb +15 -5
  49. data/lib/devise/models/omniauthable.rb +2 -0
  50. data/lib/devise/models/recoverable.rb +32 -24
  51. data/lib/devise/models/registerable.rb +4 -0
  52. data/lib/devise/models/rememberable.rb +42 -26
  53. data/lib/devise/models/timeoutable.rb +2 -6
  54. data/lib/devise/models/trackable.rb +15 -1
  55. data/lib/devise/models/validatable.rb +10 -3
  56. data/lib/devise/models.rb +3 -1
  57. data/lib/devise/modules.rb +2 -0
  58. data/lib/devise/omniauth/config.rb +2 -0
  59. data/lib/devise/omniauth/url_helpers.rb +14 -5
  60. data/lib/devise/omniauth.rb +2 -0
  61. data/lib/devise/orm/active_record.rb +5 -1
  62. data/lib/devise/orm/mongoid.rb +6 -2
  63. data/lib/devise/parameter_filter.rb +4 -0
  64. data/lib/devise/parameter_sanitizer.rb +139 -65
  65. data/lib/devise/rails/routes.rb +59 -34
  66. data/lib/devise/rails/warden_compat.rb +3 -10
  67. data/lib/devise/rails.rb +7 -16
  68. data/lib/devise/secret_key_finder.rb +27 -0
  69. data/lib/devise/strategies/authenticatable.rb +4 -2
  70. data/lib/devise/strategies/base.rb +2 -0
  71. data/lib/devise/strategies/database_authenticatable.rb +11 -4
  72. data/lib/devise/strategies/rememberable.rb +5 -6
  73. data/lib/devise/test/controller_helpers.rb +165 -0
  74. data/lib/devise/test/integration_helpers.rb +63 -0
  75. data/lib/devise/test_helpers.rb +7 -124
  76. data/lib/devise/time_inflector.rb +2 -0
  77. data/lib/devise/token_generator.rb +3 -41
  78. data/lib/devise/version.rb +3 -1
  79. data/lib/devise.rb +72 -42
  80. data/lib/generators/active_record/devise_generator.rb +29 -10
  81. data/lib/generators/active_record/templates/migration.rb +4 -2
  82. data/lib/generators/active_record/templates/migration_existing.rb +4 -2
  83. data/lib/generators/devise/controllers_generator.rb +3 -1
  84. data/lib/generators/devise/devise_generator.rb +4 -2
  85. data/lib/generators/devise/install_generator.rb +17 -0
  86. data/lib/generators/devise/orm_helpers.rb +10 -21
  87. data/lib/generators/devise/views_generator.rb +21 -11
  88. data/lib/generators/mongoid/devise_generator.rb +7 -5
  89. data/lib/generators/templates/README +1 -8
  90. data/lib/generators/templates/controllers/confirmations_controller.rb +2 -0
  91. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +2 -0
  92. data/lib/generators/templates/controllers/passwords_controller.rb +2 -0
  93. data/lib/generators/templates/controllers/registrations_controller.rb +6 -4
  94. data/lib/generators/templates/controllers/sessions_controller.rb +4 -2
  95. data/lib/generators/templates/controllers/unlocks_controller.rb +2 -0
  96. data/lib/generators/templates/devise.rb +52 -19
  97. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  98. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  99. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  100. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  101. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  102. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +5 -1
  103. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +10 -2
  104. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +4 -1
  105. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +11 -3
  106. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +11 -3
  107. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +7 -2
  108. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +4 -1
  109. metadata +27 -313
  110. data/.gitignore +0 -10
  111. data/.travis.yml +0 -45
  112. data/.yardopts +0 -9
  113. data/CONTRIBUTING.md +0 -14
  114. data/Gemfile +0 -29
  115. data/Gemfile.lock +0 -183
  116. data/Rakefile +0 -36
  117. data/devise.gemspec +0 -27
  118. data/devise.png +0 -0
  119. data/gemfiles/Gemfile.rails-3.2-stable +0 -29
  120. data/gemfiles/Gemfile.rails-3.2-stable.lock +0 -169
  121. data/gemfiles/Gemfile.rails-4.0-stable +0 -29
  122. data/gemfiles/Gemfile.rails-4.0-stable.lock +0 -163
  123. data/gemfiles/Gemfile.rails-4.1-stable +0 -29
  124. data/gemfiles/Gemfile.rails-4.1-stable.lock +0 -169
  125. data/gemfiles/Gemfile.rails-4.2-stable +0 -29
  126. data/gemfiles/Gemfile.rails-4.2-stable.lock +0 -191
  127. data/script/cached-bundle +0 -49
  128. data/script/s3-put +0 -71
  129. data/test/controllers/custom_registrations_controller_test.rb +0 -40
  130. data/test/controllers/custom_strategy_test.rb +0 -62
  131. data/test/controllers/helpers_test.rb +0 -316
  132. data/test/controllers/inherited_controller_i18n_messages_test.rb +0 -51
  133. data/test/controllers/internal_helpers_test.rb +0 -129
  134. data/test/controllers/load_hooks_controller_test.rb +0 -19
  135. data/test/controllers/passwords_controller_test.rb +0 -31
  136. data/test/controllers/sessions_controller_test.rb +0 -103
  137. data/test/controllers/url_helpers_test.rb +0 -65
  138. data/test/delegator_test.rb +0 -19
  139. data/test/devise_test.rb +0 -107
  140. data/test/failure_app_test.rb +0 -298
  141. data/test/generators/active_record_generator_test.rb +0 -109
  142. data/test/generators/controllers_generator_test.rb +0 -48
  143. data/test/generators/devise_generator_test.rb +0 -39
  144. data/test/generators/install_generator_test.rb +0 -13
  145. data/test/generators/mongoid_generator_test.rb +0 -23
  146. data/test/generators/views_generator_test.rb +0 -96
  147. data/test/helpers/devise_helper_test.rb +0 -49
  148. data/test/integration/authenticatable_test.rb +0 -729
  149. data/test/integration/confirmable_test.rb +0 -324
  150. data/test/integration/database_authenticatable_test.rb +0 -95
  151. data/test/integration/http_authenticatable_test.rb +0 -105
  152. data/test/integration/lockable_test.rb +0 -239
  153. data/test/integration/omniauthable_test.rb +0 -133
  154. data/test/integration/recoverable_test.rb +0 -347
  155. data/test/integration/registerable_test.rb +0 -359
  156. data/test/integration/rememberable_test.rb +0 -176
  157. data/test/integration/timeoutable_test.rb +0 -172
  158. data/test/integration/trackable_test.rb +0 -92
  159. data/test/mailers/confirmation_instructions_test.rb +0 -115
  160. data/test/mailers/reset_password_instructions_test.rb +0 -96
  161. data/test/mailers/unlock_instructions_test.rb +0 -91
  162. data/test/mapping_test.rb +0 -134
  163. data/test/models/authenticatable_test.rb +0 -23
  164. data/test/models/confirmable_test.rb +0 -479
  165. data/test/models/database_authenticatable_test.rb +0 -249
  166. data/test/models/lockable_test.rb +0 -328
  167. data/test/models/omniauthable_test.rb +0 -7
  168. data/test/models/recoverable_test.rb +0 -228
  169. data/test/models/registerable_test.rb +0 -7
  170. data/test/models/rememberable_test.rb +0 -204
  171. data/test/models/serializable_test.rb +0 -49
  172. data/test/models/timeoutable_test.rb +0 -51
  173. data/test/models/trackable_test.rb +0 -41
  174. data/test/models/validatable_test.rb +0 -127
  175. data/test/models_test.rb +0 -144
  176. data/test/omniauth/config_test.rb +0 -57
  177. data/test/omniauth/url_helpers_test.rb +0 -54
  178. data/test/orm/active_record.rb +0 -10
  179. data/test/orm/mongoid.rb +0 -13
  180. data/test/parameter_sanitizer_test.rb +0 -81
  181. data/test/rails_app/Rakefile +0 -6
  182. data/test/rails_app/app/active_record/admin.rb +0 -6
  183. data/test/rails_app/app/active_record/shim.rb +0 -2
  184. data/test/rails_app/app/active_record/user.rb +0 -6
  185. data/test/rails_app/app/active_record/user_on_engine.rb +0 -7
  186. data/test/rails_app/app/active_record/user_on_main_app.rb +0 -7
  187. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  188. data/test/rails_app/app/controllers/admins_controller.rb +0 -6
  189. data/test/rails_app/app/controllers/application_controller.rb +0 -12
  190. data/test/rails_app/app/controllers/application_with_fake_engine.rb +0 -30
  191. data/test/rails_app/app/controllers/custom/registrations_controller.rb +0 -31
  192. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  193. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  194. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  195. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  196. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  197. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  198. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +0 -3
  199. data/test/rails_app/app/mailers/users/mailer.rb +0 -3
  200. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +0 -4
  201. data/test/rails_app/app/mongoid/admin.rb +0 -29
  202. data/test/rails_app/app/mongoid/shim.rb +0 -23
  203. data/test/rails_app/app/mongoid/user.rb +0 -39
  204. data/test/rails_app/app/mongoid/user_on_engine.rb +0 -39
  205. data/test/rails_app/app/mongoid/user_on_main_app.rb +0 -39
  206. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  207. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  208. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  209. data/test/rails_app/app/views/home/index.html.erb +0 -1
  210. data/test/rails_app/app/views/home/join.html.erb +0 -1
  211. data/test/rails_app/app/views/home/private.html.erb +0 -1
  212. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  213. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  214. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  215. data/test/rails_app/app/views/users/index.html.erb +0 -1
  216. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  217. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  218. data/test/rails_app/bin/bundle +0 -3
  219. data/test/rails_app/bin/rails +0 -4
  220. data/test/rails_app/bin/rake +0 -4
  221. data/test/rails_app/config/application.rb +0 -40
  222. data/test/rails_app/config/boot.rb +0 -14
  223. data/test/rails_app/config/database.yml +0 -18
  224. data/test/rails_app/config/environment.rb +0 -5
  225. data/test/rails_app/config/environments/development.rb +0 -30
  226. data/test/rails_app/config/environments/production.rb +0 -84
  227. data/test/rails_app/config/environments/test.rb +0 -41
  228. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  229. data/test/rails_app/config/initializers/devise.rb +0 -180
  230. data/test/rails_app/config/initializers/inflections.rb +0 -2
  231. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  232. data/test/rails_app/config/initializers/session_store.rb +0 -1
  233. data/test/rails_app/config/routes.rb +0 -120
  234. data/test/rails_app/config.ru +0 -4
  235. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  236. data/test/rails_app/db/schema.rb +0 -55
  237. data/test/rails_app/lib/shared_admin.rb +0 -17
  238. data/test/rails_app/lib/shared_user.rb +0 -29
  239. data/test/rails_app/lib/shared_user_without_omniauth.rb +0 -13
  240. data/test/rails_app/public/404.html +0 -26
  241. data/test/rails_app/public/422.html +0 -26
  242. data/test/rails_app/public/500.html +0 -26
  243. data/test/rails_app/public/favicon.ico +0 -0
  244. data/test/rails_test.rb +0 -9
  245. data/test/routes_test.rb +0 -264
  246. data/test/support/action_controller/record_identifier.rb +0 -10
  247. data/test/support/assertions.rb +0 -39
  248. data/test/support/helpers.rb +0 -73
  249. data/test/support/integration.rb +0 -92
  250. data/test/support/locale/en.yml +0 -8
  251. data/test/support/mongoid.yml +0 -6
  252. data/test/support/webrat/integrations/rails.rb +0 -24
  253. data/test/test_helper.rb +0 -34
  254. data/test/test_helpers_test.rb +0 -178
  255. data/test/test_models.rb +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Mailers
3
5
  module Helpers
@@ -5,15 +7,16 @@ module Devise
5
7
 
6
8
  included do
7
9
  include Devise::Controllers::ScopedViews
8
- attr_reader :scope_name, :resource
9
10
  end
10
11
 
11
12
  protected
12
13
 
14
+ attr_reader :scope_name, :resource
15
+
13
16
  # Configure default email options
14
- def devise_mail(record, action, opts={})
17
+ def devise_mail(record, action, opts = {}, &block)
15
18
  initialize_from_record(record)
16
- mail headers_for(action, opts)
19
+ mail headers_for(action, opts), &block
17
20
  end
18
21
 
19
22
  def initialize_from_record(record)
@@ -64,7 +67,7 @@ module Devise
64
67
  template_path
65
68
  end
66
69
 
67
- # Setup a subject doing an I18n lookup. At first, it attempts to set a subject
70
+ # Set up a subject doing an I18n lookup. At first, it attempts to set a subject
68
71
  # based on the current mapping:
69
72
  #
70
73
  # en:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  # Responsible for handling devise mappings and routes configuration. Each
3
5
  # resource configured by devise_for in routes is actually creating a mapping
@@ -1,4 +1,5 @@
1
- require 'active_model/version'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'devise/hooks/activatable'
3
4
  require 'devise/hooks/csrf_cleaner'
4
5
 
@@ -102,7 +103,7 @@ module Devise
102
103
  # and passing a new list of attributes you want to exempt. All attributes
103
104
  # given to :except will simply add names to exempt to Devise internal list.
104
105
  def serializable_hash(options = nil)
105
- options ||= {}
106
+ options = options.try(:dup) || {}
106
107
  options[:except] = Array(options[:except])
107
108
 
108
109
  if options[:force_except]
@@ -114,6 +115,15 @@ module Devise
114
115
  super(options)
115
116
  end
116
117
 
118
+ # Redefine inspect using serializable_hash, to ensure we don't accidentally
119
+ # leak passwords into exceptions.
120
+ def inspect
121
+ inspection = serializable_hash.collect do |k,v|
122
+ "#{k}: #{respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : v.inspect}"
123
+ end
124
+ "#<#{self.class} #{inspection.join(", ")}>"
125
+ end
126
+
117
127
  protected
118
128
 
119
129
  def devise_mailer
@@ -123,16 +133,18 @@ module Devise
123
133
  # This is an internal method called every time Devise needs
124
134
  # to send a notification/mail. This can be overridden if you
125
135
  # need to customize the e-mail delivery logic. For instance,
126
- # if you are using a queue to deliver e-mails (delayed job,
127
- # sidekiq, resque, etc), you must add the delivery to the queue
136
+ # if you are using a queue to deliver e-mails (active job, delayed
137
+ # job, sidekiq, resque, etc), you must add the delivery to the queue
128
138
  # just after the transaction was committed. To achieve this,
129
139
  # you can override send_devise_notification to store the
130
- # deliveries until the after_commit callback is triggered:
140
+ # deliveries until the after_commit callback is triggered.
141
+ #
142
+ # The following example uses Active Job's `deliver_later` :
131
143
  #
132
144
  # class User
133
145
  # devise :database_authenticatable, :confirmable
134
146
  #
135
- # after_commit :send_pending_notifications
147
+ # after_commit :send_pending_devise_notifications
136
148
  #
137
149
  # protected
138
150
  #
@@ -141,26 +153,43 @@ module Devise
141
153
  # # delivery until the after_commit callback otherwise
142
154
  # # send now because after_commit will not be called.
143
155
  # if new_record? || changed?
144
- # pending_notifications << [notification, args]
156
+ # pending_devise_notifications << [notification, args]
145
157
  # else
146
- # devise_mailer.send(notification, self, *args).deliver
158
+ # render_and_send_devise_message(notification, *args)
147
159
  # end
148
160
  # end
149
161
  #
150
- # def send_pending_notifications
151
- # pending_notifications.each do |notification, args|
152
- # devise_mailer.send(notification, self, *args).deliver
162
+ # private
163
+ #
164
+ # def send_pending_devise_notifications
165
+ # pending_devise_notifications.each do |notification, args|
166
+ # render_and_send_devise_message(notification, *args)
153
167
  # end
154
168
  #
155
169
  # # Empty the pending notifications array because the
156
170
  # # after_commit hook can be called multiple times which
157
171
  # # could cause multiple emails to be sent.
158
- # pending_notifications.clear
172
+ # pending_devise_notifications.clear
173
+ # end
174
+ #
175
+ # def pending_devise_notifications
176
+ # @pending_devise_notifications ||= []
159
177
  # end
160
178
  #
161
- # def pending_notifications
162
- # @pending_notifications ||= []
179
+ # def render_and_send_devise_message(notification, *args)
180
+ # message = devise_mailer.send(notification, self, *args)
181
+ #
182
+ # # Deliver later with Active Job's `deliver_later`
183
+ # if message.respond_to?(:deliver_later)
184
+ # message.deliver_later
185
+ # # Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
186
+ # elsif message.respond_to?(:deliver_now)
187
+ # message.deliver_now
188
+ # else
189
+ # message.deliver
190
+ # end
163
191
  # end
192
+ #
164
193
  # end
165
194
  #
166
195
  def send_devise_notification(notification, *args)
@@ -235,7 +264,7 @@ module Devise
235
264
  # end
236
265
  #
237
266
  # Finally, notice that Devise also queries for users in other scenarios
238
- # besides authentication, for example when retrieving an user to send
267
+ # besides authentication, for example when retrieving a user to send
239
268
  # an e-mail for password reset. In such cases, find_for_authentication
240
269
  # is not called.
241
270
  def find_for_authentication(tainted_conditions)
@@ -253,24 +282,20 @@ module Devise
253
282
 
254
283
  # Find or initialize a record with group of attributes based on a list of required attributes.
255
284
  def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
256
- attributes = attributes.slice(*required_attributes).with_indifferent_access
257
- attributes.delete_if { |key, value| value.blank? }
285
+ attributes.try(:permit!)
286
+ attributes = attributes.to_h.with_indifferent_access
287
+ .slice(*required_attributes)
288
+ .delete_if { |key, value| value.blank? }
258
289
 
259
290
  if attributes.size == required_attributes.size
260
- record = find_first_by_auth_conditions(attributes)
291
+ record = find_first_by_auth_conditions(attributes) and return record
261
292
  end
262
293
 
263
- unless record
264
- record = new
265
-
294
+ new(devise_parameter_filter.filter(attributes)).tap do |record|
266
295
  required_attributes.each do |key|
267
- value = attributes[key]
268
- record.send("#{key}=", value)
269
- record.errors.add(key, value.present? ? error : :blank)
296
+ record.errors.add(key, attributes[key].blank? ? :blank : error)
270
297
  end
271
298
  end
272
-
273
- record
274
299
  end
275
300
 
276
301
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  # Confirmable is responsible to verify if an account is already confirmed to
@@ -24,9 +26,11 @@ module Devise
24
26
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
25
27
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
26
28
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
27
- # db field to be setup (t.reconfirmable in migrations). Until confirmed new email is
29
+ # db field to be set up (t.reconfirmable in migrations). Until confirmed, new email is
28
30
  # stored in unconfirmed email column, and copied to email column on successful
29
- # confirmation.
31
+ # confirmation. Also, when used in conjunction with `send_email_changed_notification`,
32
+ # the notification is sent to the original email when the change is requested,
33
+ # not when the unconfirmed email is confirmed.
30
34
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
31
35
  # You can use this to force the user to confirm within a set period of time.
32
36
  # Confirmable will not generate a new token if a repeat confirmation is requested
@@ -40,17 +44,23 @@ module Devise
40
44
  #
41
45
  module Confirmable
42
46
  extend ActiveSupport::Concern
43
- include ActionView::Helpers::DateHelper
44
47
 
45
48
  included do
46
49
  before_create :generate_confirmation_token, if: :confirmation_required?
47
- after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
50
+ after_create :skip_reconfirmation_in_callback!, if: :send_confirmation_notification?
51
+ if defined?(ActiveRecord) && self < ActiveRecord::Base # ActiveRecord
52
+ after_commit :send_on_create_confirmation_instructions, on: :create, if: :send_confirmation_notification?
53
+ after_commit :send_reconfirmation_instructions, on: :update, if: :reconfirmation_required?
54
+ else # Mongoid
55
+ after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
56
+ after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
57
+ end
48
58
  before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
49
- after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
50
59
  end
51
60
 
52
61
  def initialize(*args, &block)
53
62
  @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
54
64
  @reconfirmation_required = false
55
65
  @skip_confirmation_notification = false
56
66
  @raw_confirmation_token = nil
@@ -76,7 +86,7 @@ module Devise
76
86
 
77
87
  self.confirmed_at = Time.now.utc
78
88
 
79
- saved = if self.class.reconfirmable && unconfirmed_email.present?
89
+ saved = if pending_reconfirmation?
80
90
  skip_reconfirmation!
81
91
  self.email = unconfirmed_email
82
92
  self.unconfirmed_email = nil
@@ -92,11 +102,6 @@ module Devise
92
102
  end
93
103
  end
94
104
 
95
- def confirm!(args={})
96
- ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
97
- confirm(args)
98
- end
99
-
100
105
  # Verifies whether a user is confirmed or not
101
106
  def confirmed?
102
107
  !!confirmed_at
@@ -165,6 +170,12 @@ module Devise
165
170
 
166
171
  protected
167
172
 
173
+ # To not require reconfirmation after creating with #save called in a
174
+ # callback call skip_create_confirmation!
175
+ def skip_reconfirmation_in_callback!
176
+ @skip_reconfirmation_in_callback = true
177
+ end
178
+
168
179
  # A callback method used to deliver confirmation
169
180
  # instructions on creation. This can be overridden
170
181
  # in models to map to a nice sign up e-mail.
@@ -180,7 +191,7 @@ module Devise
180
191
  # Checks if the confirmation for the user is within the limit time.
181
192
  # We do this by calculating if the difference between today and the
182
193
  # confirmation sent date does not exceed the confirm in time configured.
183
- # Confirm_within is a model configuration, must always be an integer value.
194
+ # allow_unconfirmed_access_for is a model configuration, must always be an integer value.
184
195
  #
185
196
  # Example:
186
197
  #
@@ -200,7 +211,10 @@ module Devise
200
211
  # confirmation_period_valid? # will always return true
201
212
  #
202
213
  def confirmation_period_valid?
203
- self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
214
+ return true if self.class.allow_unconfirmed_access_for.nil?
215
+ return false if self.class.allow_unconfirmed_access_for == 0.days
216
+
217
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
204
218
  end
205
219
 
206
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -216,7 +230,7 @@ module Devise
216
230
  # confirmation_period_expired? # will always return false
217
231
  #
218
232
  def confirmation_period_expired?
219
- self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
233
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now.utc > self.confirmation_sent_at.utc + self.class.confirm_within)
220
234
  end
221
235
 
222
236
  # Checks whether the record requires any confirmation.
@@ -235,8 +249,7 @@ module Devise
235
249
  if self.confirmation_token && !confirmation_period_expired?
236
250
  @raw_confirmation_token = self.confirmation_token
237
251
  else
238
- raw, _ = Devise.token_generator.generate(self.class, :confirmation_token)
239
- self.confirmation_token = @raw_confirmation_token = raw
252
+ self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
240
253
  self.confirmation_sent_at = Time.now.utc
241
254
  end
242
255
  end
@@ -245,28 +258,64 @@ module Devise
245
258
  generate_confirmation_token && save(validate: false)
246
259
  end
247
260
 
248
- def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
249
- @reconfirmation_required = true
250
- self.unconfirmed_email = self.email
251
- self.email = self.email_was
252
- self.confirmation_token = nil
253
- generate_confirmation_token
261
+ if Devise.activerecord51?
262
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
263
+ @reconfirmation_required = true
264
+ self.unconfirmed_email = self.email
265
+ self.email = self.email_in_database
266
+ self.confirmation_token = nil
267
+ generate_confirmation_token
268
+ end
269
+ else
270
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
271
+ @reconfirmation_required = true
272
+ self.unconfirmed_email = self.email
273
+ self.email = self.email_was
274
+ self.confirmation_token = nil
275
+ generate_confirmation_token
276
+ end
254
277
  end
255
278
 
256
- def postpone_email_change?
257
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
258
- @bypass_confirmation_postpone = false
259
- postpone
279
+ if Devise.activerecord51?
280
+ def postpone_email_change?
281
+ postpone = self.class.reconfirmable &&
282
+ will_save_change_to_email? &&
283
+ !@bypass_confirmation_postpone &&
284
+ self.email.present? &&
285
+ (!@skip_reconfirmation_in_callback || !self.email_in_database.nil?)
286
+ @bypass_confirmation_postpone = false
287
+ postpone
288
+ end
289
+ else
290
+ def postpone_email_change?
291
+ postpone = self.class.reconfirmable &&
292
+ email_changed? &&
293
+ !@bypass_confirmation_postpone &&
294
+ self.email.present? &&
295
+ (!@skip_reconfirmation_in_callback || !self.email_was.nil?)
296
+ @bypass_confirmation_postpone = false
297
+ postpone
298
+ end
260
299
  end
261
300
 
262
301
  def reconfirmation_required?
263
- self.class.reconfirmable && @reconfirmation_required && self.email.present?
302
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
264
303
  end
265
304
 
266
305
  def send_confirmation_notification?
267
306
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
268
307
  end
269
308
 
309
+ # With reconfirmable, notify the original email when the user first
310
+ # requests the email change, instead of when the change is confirmed.
311
+ def send_email_changed_notification?
312
+ if self.class.reconfirmable
313
+ self.class.send_email_changed_notification && reconfirmation_required?
314
+ else
315
+ super
316
+ end
317
+ end
318
+
270
319
  # A callback initiated after successfully confirming. This can be
271
320
  # used to insert your own logic that is only run after the user successfully
272
321
  # confirms.
@@ -299,7 +348,19 @@ module Devise
299
348
  # If the user is already confirmed, create an error for the user
300
349
  # Options must have the confirmation_token
301
350
  def confirm_by_token(confirmation_token)
351
+ # When the `confirmation_token` parameter is blank, if there are any users with a blank
352
+ # `confirmation_token` in the database, the first one would be confirmed here.
353
+ # The error is being manually added here to ensure no users are confirmed by mistake.
354
+ # This was done in the model for convenience, since validation errors are automatically
355
+ # displayed in the view.
356
+ if confirmation_token.blank?
357
+ confirmable = new
358
+ confirmable.errors.add(:confirmation_token, :blank)
359
+ return confirmable
360
+ end
361
+
302
362
  confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
363
+
303
364
  unless confirmable
304
365
  confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
305
366
  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
@@ -315,6 +376,7 @@ module Devise
315
376
 
316
377
  # Find a record for confirmation by unconfirmed email field
317
378
  def find_by_unconfirmed_email_with_errors(attributes = {})
379
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
318
380
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
319
381
  unconfirmed_attributes = attributes.symbolize_keys
320
382
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
@@ -1,24 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/database_authenticatable'
2
4
 
3
5
  module Devise
4
- def self.bcrypt(klass, password)
5
- ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
6
- Devise::Encryptor.digest(klass, password)
7
- end
8
-
9
6
  module Models
10
- # Authenticatable Module, responsible for encrypting password and validating
11
- # authenticity of a user while signing in.
7
+ # Authenticatable Module, responsible for hashing the password and
8
+ # validating the authenticity of a user while signing in.
12
9
  #
13
10
  # == Options
14
11
  #
15
- # DatabaseAuthenticable adds the following options to devise_for:
12
+ # DatabaseAuthenticatable adds the following options to devise_for:
16
13
  #
17
14
  # * +pepper+: a random string used to provide a more secure hash. Use
18
- # `rake secret` to generate new keys.
15
+ # `rails secret` to generate new keys.
19
16
  #
20
17
  # * +stretches+: the cost given to bcrypt.
21
18
  #
19
+ # * +send_email_changed_notification+: notify original email when it changes.
20
+ #
21
+ # * +send_password_change_notification+: notify email when password changes.
22
+ #
22
23
  # == Examples
23
24
  #
24
25
  # User.find(1).valid_password?('password123') # returns true/false
@@ -27,15 +28,36 @@ module Devise
27
28
  extend ActiveSupport::Concern
28
29
 
29
30
  included do
31
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
32
+ after_update :send_password_change_notification, if: :send_password_change_notification?
33
+
30
34
  attr_reader :password, :current_password
31
35
  attr_accessor :password_confirmation
32
36
  end
33
37
 
38
+ def initialize(*args, &block)
39
+ @skip_email_changed_notification = false
40
+ @skip_password_change_notification = false
41
+ super
42
+ end
43
+
44
+ # Skips sending the email changed notification after_update
45
+ def skip_email_changed_notification!
46
+ @skip_email_changed_notification = true
47
+ end
48
+
49
+ # Skips sending the password change notification after_update
50
+ def skip_password_change_notification!
51
+ @skip_password_change_notification = true
52
+ end
53
+
34
54
  def self.required_fields(klass)
35
55
  [:encrypted_password] + klass.authentication_keys
36
56
  end
37
57
 
38
- # Generates password encryption based on the given value.
58
+ # Generates a hashed password based on the given value.
59
+ # For legacy reasons, we use `encrypted_password` to store
60
+ # the hashed password.
39
61
  def password=(new_password)
40
62
  @password = new_password
41
63
  self.encrypted_password = password_digest(@password) if @password.present?
@@ -59,6 +81,15 @@ module Devise
59
81
  # their password). In case the password field is rejected, the confirmation
60
82
  # is also rejected as long as it is also blank.
61
83
  def update_with_password(params, *options)
84
+ if options.present?
85
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
86
+ [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
87
+ (`options`) is deprecated and it will be removed in the next major version.
88
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
89
+ from your code.
90
+ DEPRECATION
91
+ end
92
+
62
93
  current_password = params.delete(:current_password)
63
94
 
64
95
  if params[:password].blank?
@@ -67,11 +98,11 @@ module Devise
67
98
  end
68
99
 
69
100
  result = if valid_password?(current_password)
70
- update_attributes(params, *options)
101
+ update(params, *options)
71
102
  else
72
- self.assign_attributes(params, *options)
73
- self.valid?
74
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
103
+ assign_attributes(params, *options)
104
+ valid?
105
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
75
106
  false
76
107
  end
77
108
 
@@ -92,10 +123,19 @@ module Devise
92
123
  # end
93
124
  #
94
125
  def update_without_password(params, *options)
126
+ if options.present?
127
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
128
+ [Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
129
+ (`options`) is deprecated and it will be removed in the next major version.
130
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
131
+ from your code.
132
+ DEPRECATION
133
+ end
134
+
95
135
  params.delete(:password)
96
136
  params.delete(:password_confirmation)
97
137
 
98
- result = update_attributes(params, *options)
138
+ result = update(params, *options)
99
139
  clean_up_passwords
100
140
  result
101
141
  end
@@ -107,8 +147,8 @@ module Devise
107
147
  result = if valid_password?(current_password)
108
148
  destroy
109
149
  else
110
- self.valid?
111
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
150
+ valid?
151
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
112
152
  false
113
153
  end
114
154
 
@@ -133,19 +173,56 @@ module Devise
133
173
  encrypted_password[0,29] if encrypted_password
134
174
  end
135
175
 
176
+ if Devise.activerecord51?
177
+ # Send notification to user when email changes.
178
+ def send_email_changed_notification
179
+ send_devise_notification(:email_changed, to: email_before_last_save)
180
+ end
181
+ else
182
+ # Send notification to user when email changes.
183
+ def send_email_changed_notification
184
+ send_devise_notification(:email_changed, to: email_was)
185
+ end
186
+ end
187
+
188
+ # Send notification to user when password changes.
189
+ def send_password_change_notification
190
+ send_devise_notification(:password_change)
191
+ end
192
+
136
193
  protected
137
194
 
138
- # Digests the password using bcrypt. Custom encryption should override
195
+ # Hashes the password using bcrypt. Custom hash functions should override
139
196
  # this method to apply their own algorithm.
140
197
  #
141
198
  # See https://github.com/plataformatec/devise-encryptable for examples
142
- # of other encryption engines.
199
+ # of other hashing engines.
143
200
  def password_digest(password)
144
201
  Devise::Encryptor.digest(self.class, password)
145
202
  end
146
203
 
204
+ if Devise.activerecord51?
205
+ def send_email_changed_notification?
206
+ self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
207
+ end
208
+ else
209
+ def send_email_changed_notification?
210
+ self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
211
+ end
212
+ end
213
+
214
+ if Devise.activerecord51?
215
+ def send_password_change_notification?
216
+ self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
217
+ end
218
+ else
219
+ def send_password_change_notification?
220
+ self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
221
+ end
222
+ end
223
+
147
224
  module ClassMethods
148
- Devise::Models.config(self, :pepper, :stretches)
225
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
149
226
 
150
227
  # We assume this method already gets the sanitized values from the
151
228
  # DatabaseAuthenticatable strategy. If you are using this method on
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "devise/hooks/lockable"
2
4
 
3
5
  module Devise
@@ -7,7 +9,7 @@ module Devise
7
9
  # blocked: email and time. The former will send an email to the user when
8
10
  # the lock happens, containing a link to unlock its account. The second
9
11
  # will unlock the user automatically after some configured time (ie 2.hours).
10
- # It's also possible to setup lockable to use both email and time strategies.
12
+ # It's also possible to set up lockable to use both email and time strategies.
11
13
  #
12
14
  # == Options
13
15
  #
@@ -64,7 +66,7 @@ module Devise
64
66
  def send_unlock_instructions
65
67
  raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
66
68
  self.unlock_token = enc
67
- self.save(validate: false)
69
+ save(validate: false)
68
70
  send_devise_notification(:unlock_instructions, raw, {})
69
71
  raw
70
72
  end
@@ -99,8 +101,7 @@ module Devise
99
101
  if super && !access_locked?
100
102
  true
101
103
  else
102
- self.failed_attempts ||= 0
103
- self.failed_attempts += 1
104
+ increment_failed_attempts
104
105
  if attempts_exceeded?
105
106
  lock_access! unless access_locked?
106
107
  else
@@ -109,6 +110,11 @@ module Devise
109
110
  false
110
111
  end
111
112
  end
113
+
114
+ def increment_failed_attempts
115
+ self.class.increment_counter(:failed_attempts, id)
116
+ reload
117
+ end
112
118
 
113
119
  def unauthenticated_message
114
120
  # If set to paranoid mode, do not show the locked message because it
@@ -155,6 +161,9 @@ module Devise
155
161
  end
156
162
 
157
163
  module ClassMethods
164
+ # List of strategies that are enabled/supported if :both is used.
165
+ BOTH_STRATEGIES = [:time, :email]
166
+
158
167
  # Attempt to find a user by its unlock keys. If a record is found, send new
159
168
  # unlock instructions to it. If not user is found, returns a new user
160
169
  # with an email not found error.
@@ -181,7 +190,8 @@ module Devise
181
190
 
182
191
  # Is the unlock enabled for the given unlock strategy?
183
192
  def unlock_strategy_enabled?(strategy)
184
- [:both, strategy].include?(self.unlock_strategy)
193
+ self.unlock_strategy == strategy ||
194
+ (self.unlock_strategy == :both && BOTH_STRATEGIES.include?(strategy))
185
195
  end
186
196
 
187
197
  # Is the lock enabled for the given lock strategy?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/omniauth'
2
4
 
3
5
  module Devise