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.
Files changed (242) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +351 -0
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +422 -130
  5. data/app/controllers/devise/confirmations_controller.rb +17 -6
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +12 -6
  7. data/app/controllers/devise/passwords_controller.rb +23 -8
  8. data/app/controllers/devise/registrations_controller.rb +70 -28
  9. data/app/controllers/devise/sessions_controller.rb +49 -17
  10. data/app/controllers/devise/unlocks_controller.rb +11 -4
  11. data/app/controllers/devise_controller.rb +74 -34
  12. data/app/helpers/devise_helper.rb +23 -18
  13. data/app/mailers/devise/mailer.rb +25 -10
  14. data/app/views/devise/confirmations/new.html.erb +9 -5
  15. data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
  16. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  17. data/app/views/devise/mailer/password_change.html.erb +3 -0
  18. data/app/views/devise/mailer/reset_password_instructions.html.erb +1 -1
  19. data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
  20. data/app/views/devise/passwords/edit.html.erb +16 -7
  21. data/app/views/devise/passwords/new.html.erb +9 -5
  22. data/app/views/devise/registrations/edit.html.erb +29 -15
  23. data/app/views/devise/registrations/new.html.erb +20 -9
  24. data/app/views/devise/sessions/new.html.erb +19 -10
  25. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  26. data/app/views/devise/shared/{_links.erb → _links.html.erb} +10 -10
  27. data/app/views/devise/unlocks/new.html.erb +9 -5
  28. data/config/locales/en.yml +26 -20
  29. data/lib/devise/controllers/helpers.rb +122 -125
  30. data/lib/devise/controllers/rememberable.rb +14 -14
  31. data/lib/devise/controllers/scoped_views.rb +3 -1
  32. data/lib/devise/controllers/sign_in_out.rb +121 -0
  33. data/lib/devise/controllers/store_location.rb +76 -0
  34. data/lib/devise/controllers/url_helpers.rb +10 -8
  35. data/lib/devise/delegator.rb +2 -0
  36. data/lib/devise/encryptor.rb +24 -0
  37. data/lib/devise/failure_app.rb +132 -42
  38. data/lib/devise/hooks/activatable.rb +7 -6
  39. data/lib/devise/hooks/csrf_cleaner.rb +9 -0
  40. data/lib/devise/hooks/forgetable.rb +3 -1
  41. data/lib/devise/hooks/lockable.rb +5 -3
  42. data/lib/devise/hooks/proxy.rb +23 -0
  43. data/lib/devise/hooks/rememberable.rb +7 -4
  44. data/lib/devise/hooks/timeoutable.rb +18 -8
  45. data/lib/devise/hooks/trackable.rb +3 -1
  46. data/lib/devise/mailers/helpers.rb +15 -18
  47. data/lib/devise/mapping.rb +9 -3
  48. data/lib/devise/models/authenticatable.rb +102 -80
  49. data/lib/devise/models/confirmable.rb +154 -72
  50. data/lib/devise/models/database_authenticatable.rb +125 -25
  51. data/lib/devise/models/lockable.rb +50 -29
  52. data/lib/devise/models/omniauthable.rb +3 -1
  53. data/lib/devise/models/recoverable.rb +72 -50
  54. data/lib/devise/models/registerable.rb +4 -0
  55. data/lib/devise/models/rememberable.rb +65 -32
  56. data/lib/devise/models/timeoutable.rb +4 -8
  57. data/lib/devise/models/trackable.rb +20 -4
  58. data/lib/devise/models/validatable.rb +16 -9
  59. data/lib/devise/models.rb +6 -13
  60. data/lib/devise/modules.rb +12 -11
  61. data/lib/devise/omniauth/config.rb +2 -0
  62. data/lib/devise/omniauth/url_helpers.rb +14 -5
  63. data/lib/devise/omniauth.rb +4 -5
  64. data/lib/devise/orm/active_record.rb +5 -1
  65. data/lib/devise/orm/mongoid.rb +6 -2
  66. data/lib/devise/parameter_filter.rb +4 -0
  67. data/lib/devise/parameter_sanitizer.rb +144 -34
  68. data/lib/devise/rails/deprecated_constant_accessor.rb +39 -0
  69. data/lib/devise/rails/routes.rb +191 -127
  70. data/lib/devise/rails/warden_compat.rb +2 -1
  71. data/lib/devise/rails.rb +13 -20
  72. data/lib/devise/secret_key_finder.rb +27 -0
  73. data/lib/devise/strategies/authenticatable.rb +21 -22
  74. data/lib/devise/strategies/base.rb +3 -1
  75. data/lib/devise/strategies/database_authenticatable.rb +15 -4
  76. data/lib/devise/strategies/rememberable.rb +15 -3
  77. data/lib/devise/test/controller_helpers.rb +167 -0
  78. data/lib/devise/test/integration_helpers.rb +63 -0
  79. data/lib/devise/test_helpers.rb +7 -123
  80. data/lib/devise/time_inflector.rb +4 -2
  81. data/lib/devise/token_generator.rb +32 -0
  82. data/lib/devise/version.rb +3 -1
  83. data/lib/devise.rb +124 -78
  84. data/lib/generators/active_record/devise_generator.rb +64 -15
  85. data/lib/generators/active_record/templates/migration.rb +9 -8
  86. data/lib/generators/active_record/templates/migration_existing.rb +9 -8
  87. data/lib/generators/devise/controllers_generator.rb +46 -0
  88. data/lib/generators/devise/devise_generator.rb +10 -6
  89. data/lib/generators/devise/install_generator.rb +19 -1
  90. data/lib/generators/devise/orm_helpers.rb +17 -9
  91. data/lib/generators/devise/views_generator.rb +51 -28
  92. data/lib/generators/mongoid/devise_generator.rb +24 -24
  93. data/lib/generators/templates/README +13 -12
  94. data/lib/generators/templates/controllers/README +14 -0
  95. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  96. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  97. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  98. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  99. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  100. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  101. data/lib/generators/templates/devise.rb +118 -53
  102. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  103. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  104. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  105. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  106. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  107. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +6 -2
  108. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +12 -4
  109. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +5 -2
  110. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +14 -6
  111. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +12 -4
  112. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +11 -6
  113. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +5 -2
  114. metadata +73 -294
  115. data/.gitignore +0 -10
  116. data/.travis.yml +0 -20
  117. data/.yardopts +0 -9
  118. data/CHANGELOG.rdoc +0 -941
  119. data/CONTRIBUTING.md +0 -14
  120. data/Gemfile +0 -31
  121. data/Gemfile.lock +0 -159
  122. data/Rakefile +0 -35
  123. data/app/views/devise/_links.erb +0 -3
  124. data/devise.gemspec +0 -26
  125. data/devise.png +0 -0
  126. data/gemfiles/Gemfile.rails-3.2.x +0 -31
  127. data/gemfiles/Gemfile.rails-3.2.x.lock +0 -156
  128. data/lib/devise/models/token_authenticatable.rb +0 -89
  129. data/lib/devise/strategies/token_authenticatable.rb +0 -91
  130. data/test/controllers/custom_strategy_test.rb +0 -62
  131. data/test/controllers/helpers_test.rb +0 -253
  132. data/test/controllers/internal_helpers_test.rb +0 -120
  133. data/test/controllers/passwords_controller_test.rb +0 -32
  134. data/test/controllers/sessions_controller_test.rb +0 -99
  135. data/test/controllers/url_helpers_test.rb +0 -59
  136. data/test/delegator_test.rb +0 -19
  137. data/test/devise_test.rb +0 -83
  138. data/test/failure_app_test.rb +0 -221
  139. data/test/generators/active_record_generator_test.rb +0 -73
  140. data/test/generators/devise_generator_test.rb +0 -39
  141. data/test/generators/install_generator_test.rb +0 -13
  142. data/test/generators/mongoid_generator_test.rb +0 -23
  143. data/test/generators/views_generator_test.rb +0 -67
  144. data/test/helpers/devise_helper_test.rb +0 -51
  145. data/test/integration/authenticatable_test.rb +0 -699
  146. data/test/integration/confirmable_test.rb +0 -299
  147. data/test/integration/database_authenticatable_test.rb +0 -84
  148. data/test/integration/http_authenticatable_test.rb +0 -115
  149. data/test/integration/lockable_test.rb +0 -242
  150. data/test/integration/omniauthable_test.rb +0 -133
  151. data/test/integration/recoverable_test.rb +0 -335
  152. data/test/integration/registerable_test.rb +0 -349
  153. data/test/integration/rememberable_test.rb +0 -165
  154. data/test/integration/timeoutable_test.rb +0 -150
  155. data/test/integration/token_authenticatable_test.rb +0 -205
  156. data/test/integration/trackable_test.rb +0 -92
  157. data/test/mailers/confirmation_instructions_test.rb +0 -111
  158. data/test/mailers/reset_password_instructions_test.rb +0 -92
  159. data/test/mailers/unlock_instructions_test.rb +0 -87
  160. data/test/mapping_test.rb +0 -127
  161. data/test/models/authenticatable_test.rb +0 -13
  162. data/test/models/confirmable_test.rb +0 -452
  163. data/test/models/database_authenticatable_test.rb +0 -226
  164. data/test/models/lockable_test.rb +0 -282
  165. data/test/models/omniauthable_test.rb +0 -7
  166. data/test/models/recoverable_test.rb +0 -222
  167. data/test/models/registerable_test.rb +0 -7
  168. data/test/models/rememberable_test.rb +0 -175
  169. data/test/models/serializable_test.rb +0 -49
  170. data/test/models/timeoutable_test.rb +0 -46
  171. data/test/models/token_authenticatable_test.rb +0 -55
  172. data/test/models/trackable_test.rb +0 -13
  173. data/test/models/validatable_test.rb +0 -127
  174. data/test/models_test.rb +0 -163
  175. data/test/omniauth/config_test.rb +0 -57
  176. data/test/omniauth/url_helpers_test.rb +0 -54
  177. data/test/orm/active_record.rb +0 -10
  178. data/test/orm/mongoid.rb +0 -13
  179. data/test/parameter_sanitizer_test.rb +0 -58
  180. data/test/rails_app/Rakefile +0 -6
  181. data/test/rails_app/app/active_record/admin.rb +0 -6
  182. data/test/rails_app/app/active_record/shim.rb +0 -2
  183. data/test/rails_app/app/active_record/user.rb +0 -6
  184. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  185. data/test/rails_app/app/controllers/admins_controller.rb +0 -11
  186. data/test/rails_app/app/controllers/application_controller.rb +0 -9
  187. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  188. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  189. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  190. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  191. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  192. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  193. data/test/rails_app/app/mailers/users/mailer.rb +0 -12
  194. data/test/rails_app/app/mongoid/admin.rb +0 -29
  195. data/test/rails_app/app/mongoid/shim.rb +0 -23
  196. data/test/rails_app/app/mongoid/user.rb +0 -42
  197. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  198. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  199. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  200. data/test/rails_app/app/views/home/index.html.erb +0 -1
  201. data/test/rails_app/app/views/home/join.html.erb +0 -1
  202. data/test/rails_app/app/views/home/private.html.erb +0 -1
  203. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  204. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  205. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  206. data/test/rails_app/app/views/users/index.html.erb +0 -1
  207. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  208. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  209. data/test/rails_app/bin/bundle +0 -3
  210. data/test/rails_app/bin/rails +0 -4
  211. data/test/rails_app/bin/rake +0 -4
  212. data/test/rails_app/config/application.rb +0 -40
  213. data/test/rails_app/config/boot.rb +0 -8
  214. data/test/rails_app/config/database.yml +0 -18
  215. data/test/rails_app/config/environment.rb +0 -5
  216. data/test/rails_app/config/environments/development.rb +0 -34
  217. data/test/rails_app/config/environments/production.rb +0 -84
  218. data/test/rails_app/config/environments/test.rb +0 -36
  219. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  220. data/test/rails_app/config/initializers/devise.rb +0 -178
  221. data/test/rails_app/config/initializers/inflections.rb +0 -2
  222. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  223. data/test/rails_app/config/initializers/session_store.rb +0 -1
  224. data/test/rails_app/config/routes.rb +0 -104
  225. data/test/rails_app/config.ru +0 -4
  226. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -74
  227. data/test/rails_app/db/schema.rb +0 -52
  228. data/test/rails_app/lib/shared_admin.rb +0 -14
  229. data/test/rails_app/lib/shared_user.rb +0 -25
  230. data/test/rails_app/public/404.html +0 -26
  231. data/test/rails_app/public/422.html +0 -26
  232. data/test/rails_app/public/500.html +0 -26
  233. data/test/rails_app/public/favicon.ico +0 -0
  234. data/test/routes_test.rb +0 -250
  235. data/test/support/assertions.rb +0 -40
  236. data/test/support/helpers.rb +0 -91
  237. data/test/support/integration.rb +0 -92
  238. data/test/support/locale/en.yml +0 -4
  239. data/test/support/webrat/integrations/rails.rb +0 -24
  240. data/test/test_helper.rb +0 -34
  241. data/test/test_helpers_test.rb +0 -151
  242. data/test/test_models.rb +0 -26
@@ -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
@@ -5,44 +7,63 @@ module Devise
5
7
  # Confirmation instructions are sent to the user email after creating a
6
8
  # record and when manually requested by a new confirmation instruction request.
7
9
  #
10
+ # Confirmable tracks the following columns:
11
+ #
12
+ # * confirmation_token - A unique random token
13
+ # * confirmed_at - A timestamp when the user clicked the confirmation link
14
+ # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
15
+ # * unconfirmed_email - An email address copied from the email attr. After confirmation
16
+ # this value is copied to the email attr then cleared
17
+ #
8
18
  # == Options
9
19
  #
10
- # Confirmable adds the following options to devise_for:
20
+ # Confirmable adds the following options to +devise+:
11
21
  #
12
- # * +allow_unconfirmed_access_for+: the time you want to allow the user to access his account
22
+ # * +allow_unconfirmed_access_for+: the time you want to allow the user to access their account
13
23
  # before confirming it. After this period, the user access is denied. You can
14
24
  # use this to let your user access some features of your application without
15
25
  # confirming the account, but blocking it after a certain period (ie 7 days).
16
26
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
17
27
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
18
28
  # 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
29
+ # db field to be set up (t.reconfirmable in migrations). Until confirmed, new email is
20
30
  # stored in unconfirmed email column, and copied to email column on successful
21
- # 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.
22
34
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
23
35
  # You can use this to force the user to confirm within a set period of time.
36
+ # Confirmable will not generate a new token if a repeat confirmation is requested
37
+ # during this time frame, unless the user's email changed too.
24
38
  #
25
39
  # == Examples
26
40
  #
27
- # User.find(1).confirm! # returns true unless it's already confirmed
41
+ # User.find(1).confirm # returns true unless it's already confirmed
28
42
  # User.find(1).confirmed? # true/false
29
43
  # User.find(1).send_confirmation_instructions # manually send instructions
30
44
  #
31
45
  module Confirmable
32
46
  extend ActiveSupport::Concern
33
- include ActionView::Helpers::DateHelper
34
47
 
35
48
  included do
36
- before_create :generate_confirmation_token, :if => :confirmation_required?
37
- after_create :send_on_create_confirmation_instructions, :if => :send_confirmation_notification?
38
- before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, :if => :postpone_email_change?
39
- after_update :send_reconfirmation_instructions, :if => :reconfirmation_required?
49
+ before_create :generate_confirmation_token, if: :confirmation_required?
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
58
+ before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
40
59
  end
41
60
 
42
61
  def initialize(*args, &block)
43
- @bypass_postpone = false
62
+ @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
44
64
  @reconfirmation_required = false
45
65
  @skip_confirmation_notification = false
66
+ @raw_confirmation_token = nil
46
67
  super
47
68
  end
48
69
 
@@ -55,27 +76,29 @@ module Devise
55
76
  # Confirm a user by setting it's confirmed_at to actual time. If the user
56
77
  # is already confirmed, add an error to email field. If the user is invalid
57
78
  # add errors
58
- def confirm!
79
+ def confirm(args = {})
59
80
  pending_any_confirmation do
60
81
  if confirmation_period_expired?
61
82
  self.errors.add(:email, :confirmation_period_expired,
62
- :period => Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
83
+ period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
63
84
  return false
64
85
  end
65
86
 
66
- self.confirmation_token = nil
67
87
  self.confirmed_at = Time.now.utc
68
88
 
69
- if self.class.reconfirmable && unconfirmed_email.present?
89
+ saved = if pending_reconfirmation?
70
90
  skip_reconfirmation!
71
91
  self.email = unconfirmed_email
72
92
  self.unconfirmed_email = nil
73
93
 
74
94
  # We need to validate in such cases to enforce e-mail uniqueness
75
- save(:validate => true)
95
+ save(validate: true)
76
96
  else
77
- save(:validate => false)
97
+ save(validate: args[:ensure_valid] == true)
78
98
  end
99
+
100
+ after_confirmation if saved
101
+ saved
79
102
  end
80
103
  end
81
104
 
@@ -90,10 +113,12 @@ module Devise
90
113
 
91
114
  # Send confirmation instructions by email
92
115
  def send_confirmation_instructions
93
- ensure_confirmation_token!
116
+ unless @raw_confirmation_token
117
+ generate_confirmation_token!
118
+ end
94
119
 
95
- opts = pending_reconfirmation? ? { :to => unconfirmed_email } : { }
96
- send_devise_notification(:confirmation_instructions, opts)
120
+ opts = pending_reconfirmation? ? { to: unconfirmed_email } : { }
121
+ send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
97
122
  end
98
123
 
99
124
  def send_reconfirmation_instructions
@@ -106,17 +131,11 @@ module Devise
106
131
 
107
132
  # Resend confirmation token.
108
133
  # Regenerates the token if the period is expired.
109
- def resend_confirmation_token
134
+ def resend_confirmation_instructions
110
135
  pending_any_confirmation do
111
- regenerate_confirmation_token! if confirmation_period_expired?
112
136
  send_confirmation_instructions
113
137
  end
114
138
  end
115
-
116
- # Generate a confirmation token unless already exists and save the record.
117
- def ensure_confirmation_token!
118
- generate_confirmation_token! if should_generate_confirmation_token?
119
- end
120
139
 
121
140
  # Overwrites active_for_authentication? for confirmation
122
141
  # by verifying whether a user is active to sign in or not. If the user
@@ -146,19 +165,22 @@ module Devise
146
165
  # If you don't want reconfirmation to be sent, neither a code
147
166
  # to be generated, call skip_reconfirmation!
148
167
  def skip_reconfirmation!
149
- @bypass_postpone = true
168
+ @bypass_confirmation_postpone = true
150
169
  end
151
170
 
152
171
  protected
153
- def should_generate_confirmation_token?
154
- confirmation_token.nil? || confirmation_period_expired?
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
155
177
  end
156
178
 
157
179
  # A callback method used to deliver confirmation
158
- # instructions on creation. This can be overriden
180
+ # instructions on creation. This can be overridden
159
181
  # in models to map to a nice sign up e-mail.
160
182
  def send_on_create_confirmation_instructions
161
- send_devise_notification(:confirmation_instructions)
183
+ send_confirmation_instructions
162
184
  end
163
185
 
164
186
  # Callback to overwrite if confirmation is required or not.
@@ -169,7 +191,7 @@ module Devise
169
191
  # Checks if the confirmation for the user is within the limit time.
170
192
  # We do this by calculating if the difference between today and the
171
193
  # confirmation sent date does not exceed the confirm in time configured.
172
- # 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.
173
195
  #
174
196
  # Example:
175
197
  #
@@ -189,7 +211,10 @@ module Devise
189
211
  # confirmation_period_valid? # will always return true
190
212
  #
191
213
  def confirmation_period_valid?
192
- 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
193
218
  end
194
219
 
195
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -205,7 +230,7 @@ module Devise
205
230
  # confirmation_period_expired? # will always return false
206
231
  #
207
232
  def confirmation_period_expired?
208
- 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)
209
234
  end
210
235
 
211
236
  # Checks whether the record requires any confirmation.
@@ -218,50 +243,90 @@ module Devise
218
243
  end
219
244
  end
220
245
 
221
- # Generates a new random token for confirmation, and stores the time
222
- # this token is being generated
246
+ # Generates a new random token for confirmation, and stores
247
+ # the time this token is being generated in confirmation_sent_at
223
248
  def generate_confirmation_token
224
- self.confirmation_token = self.class.confirmation_token
225
- self.confirmation_sent_at = Time.now.utc
249
+ if self.confirmation_token && !confirmation_period_expired?
250
+ @raw_confirmation_token = self.confirmation_token
251
+ else
252
+ self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
253
+ self.confirmation_sent_at = Time.now.utc
254
+ end
226
255
  end
227
256
 
228
257
  def generate_confirmation_token!
229
- generate_confirmation_token && save(:validate => false)
258
+ generate_confirmation_token && save(validate: false)
230
259
  end
231
260
 
232
- # Regenerates a new token.
233
- def regenerate_confirmation_token
234
- generate_confirmation_token
235
- end
236
-
237
- def regenerate_confirmation_token!
238
- regenerate_confirmation_token && save(:validate => false)
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
239
277
  end
240
278
 
241
- def after_password_reset
242
- super
243
- confirm! unless confirmed?
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
244
299
  end
245
300
 
246
- def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
247
- @reconfirmation_required = true
248
- self.unconfirmed_email = self.email
249
- self.email = self.email_was
250
- regenerate_confirmation_token
301
+ def reconfirmation_required?
302
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
251
303
  end
252
304
 
253
- def postpone_email_change?
254
- postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone && !self.email.blank?
255
- @bypass_postpone = false
256
- postpone
305
+ def send_confirmation_notification?
306
+ confirmation_required? && !@skip_confirmation_notification && self.email.present?
257
307
  end
258
308
 
259
- def reconfirmation_required?
260
- self.class.reconfirmable && @reconfirmation_required && !self.email.blank?
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
261
317
  end
262
318
 
263
- def send_confirmation_notification?
264
- confirmation_required? && !@skip_confirmation_notification && !self.email.blank?
319
+ # A callback initiated after successfully confirming. This can be
320
+ # used to insert your own logic that is only run after the user successfully
321
+ # confirms.
322
+ #
323
+ # Example:
324
+ #
325
+ # def after_confirmation
326
+ # self.update_attribute(:invite_code, nil)
327
+ # end
328
+ #
329
+ def after_confirmation
265
330
  end
266
331
 
267
332
  module ClassMethods
@@ -269,12 +334,12 @@ module Devise
269
334
  # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
270
335
  # field. If no user is found, returns a new user with an email not found error.
271
336
  # Options must contain the user email
272
- def send_confirmation_instructions(attributes={})
337
+ def send_confirmation_instructions(attributes = {})
273
338
  confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
274
339
  unless confirmable.try(:persisted?)
275
340
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
276
341
  end
277
- confirmable.resend_confirmation_token if confirmable.persisted?
342
+ confirmable.resend_confirmation_instructions if confirmable.persisted?
278
343
  confirmable
279
344
  end
280
345
 
@@ -283,18 +348,35 @@ module Devise
283
348
  # If the user is already confirmed, create an error for the user
284
349
  # Options must have the confirmation_token
285
350
  def confirm_by_token(confirmation_token)
286
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
287
- confirmable.confirm! if confirmable.persisted?
288
- confirmable
289
- end
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
+
362
+ confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
290
363
 
291
- # Generate a token checking if one does not already exist in the database.
292
- def confirmation_token
293
- generate_token(:confirmation_token)
364
+ unless confirmable
365
+ confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
366
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
367
+ end
368
+
369
+ # TODO: replace above lines with
370
+ # confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
371
+ # after enough time has passed that Devise clients do not use digested tokens
372
+
373
+ confirmable.confirm if confirmable.persisted?
374
+ confirmable
294
375
  end
295
376
 
296
377
  # Find a record for confirmation by unconfirmed email field
297
378
  def find_by_unconfirmed_email_with_errors(attributes = {})
379
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
298
380
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
299
381
  unconfirmed_attributes = attributes.symbolize_keys
300
382
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
@@ -1,20 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/database_authenticatable'
2
- require 'bcrypt'
3
4
 
4
5
  module Devise
5
6
  module Models
6
- # Authenticatable Module, responsible for encrypting password and validating
7
- # 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.
9
+ #
10
+ # This module defines a `password=` method. This method will hash the argument
11
+ # and store it in the `encrypted_password` column, bypassing any pre-existing
12
+ # `password` column if it exists.
8
13
  #
9
14
  # == Options
10
15
  #
11
- # DatabaseAuthenticable adds the following options to devise_for:
16
+ # DatabaseAuthenticatable adds the following options to devise_for:
12
17
  #
13
18
  # * +pepper+: a random string used to provide a more secure hash. Use
14
- # `rake secret` to generate new keys.
19
+ # `rails secret` to generate new keys.
15
20
  #
16
21
  # * +stretches+: the cost given to bcrypt.
17
22
  #
23
+ # * +send_email_changed_notification+: notify original email when it changes.
24
+ #
25
+ # * +send_password_change_notification+: notify email when password changes.
26
+ #
18
27
  # == Examples
19
28
  #
20
29
  # User.find(1).valid_password?('password123') # returns true/false
@@ -23,26 +32,44 @@ module Devise
23
32
  extend ActiveSupport::Concern
24
33
 
25
34
  included do
35
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
36
+ after_update :send_password_change_notification, if: :send_password_change_notification?
37
+
26
38
  attr_reader :password, :current_password
27
39
  attr_accessor :password_confirmation
28
40
  end
29
41
 
42
+ def initialize(*args, &block)
43
+ @skip_email_changed_notification = false
44
+ @skip_password_change_notification = false
45
+ super
46
+ end
47
+
48
+ # Skips sending the email changed notification after_update
49
+ def skip_email_changed_notification!
50
+ @skip_email_changed_notification = true
51
+ end
52
+
53
+ # Skips sending the password change notification after_update
54
+ def skip_password_change_notification!
55
+ @skip_password_change_notification = true
56
+ end
57
+
30
58
  def self.required_fields(klass)
31
59
  [:encrypted_password] + klass.authentication_keys
32
60
  end
33
61
 
34
- # Generates password encryption based on the given value.
62
+ # Generates a hashed password based on the given value.
63
+ # For legacy reasons, we use `encrypted_password` to store
64
+ # the hashed password.
35
65
  def password=(new_password)
36
66
  @password = new_password
37
67
  self.encrypted_password = password_digest(@password) if @password.present?
38
68
  end
39
69
 
40
- # Verifies whether an password (ie from sign in) is the user password.
70
+ # Verifies whether a password (ie from sign in) is the user password.
41
71
  def valid_password?(password)
42
- return false if encrypted_password.blank?
43
- bcrypt = ::BCrypt::Password.new(encrypted_password)
44
- password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
45
- Devise.secure_compare(password, encrypted_password)
72
+ Devise::Encryptor.compare(self.class, encrypted_password, password)
46
73
  end
47
74
 
48
75
  # Set password and password confirmation to nil
@@ -50,10 +77,23 @@ module Devise
50
77
  self.password = self.password_confirmation = nil
51
78
  end
52
79
 
53
- # Update record attributes when :current_password matches, otherwise returns
54
- # error on :current_password. It also automatically rejects :password and
55
- # :password_confirmation if they are blank.
80
+ # Update record attributes when :current_password matches, otherwise
81
+ # returns error on :current_password.
82
+ #
83
+ # This method also rejects the password field if it is blank (allowing
84
+ # users to change relevant information like the e-mail without changing
85
+ # their password). In case the password field is rejected, the confirmation
86
+ # is also rejected as long as it is also blank.
56
87
  def update_with_password(params, *options)
88
+ if options.present?
89
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
90
+ [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
91
+ (`options`) is deprecated and it will be removed in the next major version.
92
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
93
+ from your code.
94
+ DEPRECATION
95
+ end
96
+
57
97
  current_password = params.delete(:current_password)
58
98
 
59
99
  if params[:password].blank?
@@ -62,11 +102,11 @@ module Devise
62
102
  end
63
103
 
64
104
  result = if valid_password?(current_password)
65
- update_attributes(params, *options)
105
+ update(params, *options)
66
106
  else
67
- self.assign_attributes(params, *options)
68
- self.valid?
69
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
107
+ assign_attributes(params, *options)
108
+ valid?
109
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
70
110
  false
71
111
  end
72
112
 
@@ -87,29 +127,48 @@ module Devise
87
127
  # end
88
128
  #
89
129
  def update_without_password(params, *options)
130
+ if options.present?
131
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
132
+ [Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
133
+ (`options`) is deprecated and it will be removed in the next major version.
134
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
135
+ from your code.
136
+ DEPRECATION
137
+ end
138
+
90
139
  params.delete(:password)
91
140
  params.delete(:password_confirmation)
92
141
 
93
- result = update_attributes(params, *options)
142
+ result = update(params, *options)
94
143
  clean_up_passwords
95
144
  result
96
145
  end
97
146
 
98
147
  # Destroy record when :current_password matches, otherwise returns
99
- # error on :current_password. It also automatically rejects
148
+ # error on :current_password. It also automatically rejects
100
149
  # :current_password if it is blank.
101
150
  def destroy_with_password(current_password)
102
151
  result = if valid_password?(current_password)
103
152
  destroy
104
153
  else
105
- self.valid?
106
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
154
+ valid?
155
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
107
156
  false
108
157
  end
109
158
 
110
159
  result
111
160
  end
112
161
 
162
+ # A callback initiated after successfully authenticating. This can be
163
+ # used to insert your own logic that is only run after the user successfully
164
+ # authenticates.
165
+ #
166
+ # Example:
167
+ #
168
+ # def after_database_authentication
169
+ # self.update_attribute(:invite_code, nil)
170
+ # end
171
+ #
113
172
  def after_database_authentication
114
173
  end
115
174
 
@@ -118,15 +177,56 @@ module Devise
118
177
  encrypted_password[0,29] if encrypted_password
119
178
  end
120
179
 
180
+ if Devise.activerecord51?
181
+ # Send notification to user when email changes.
182
+ def send_email_changed_notification
183
+ send_devise_notification(:email_changed, to: email_before_last_save)
184
+ end
185
+ else
186
+ # Send notification to user when email changes.
187
+ def send_email_changed_notification
188
+ send_devise_notification(:email_changed, to: email_was)
189
+ end
190
+ end
191
+
192
+ # Send notification to user when password changes.
193
+ def send_password_change_notification
194
+ send_devise_notification(:password_change)
195
+ end
196
+
121
197
  protected
122
198
 
123
- # Digests the password using bcrypt.
199
+ # Hashes the password using bcrypt. Custom hash functions should override
200
+ # this method to apply their own algorithm.
201
+ #
202
+ # See https://github.com/heartcombo/devise-encryptable for examples
203
+ # of other hashing engines.
124
204
  def password_digest(password)
125
- ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
205
+ Devise::Encryptor.digest(self.class, password)
206
+ end
207
+
208
+ if Devise.activerecord51?
209
+ def send_email_changed_notification?
210
+ self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
211
+ end
212
+ else
213
+ def send_email_changed_notification?
214
+ self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
215
+ end
216
+ end
217
+
218
+ if Devise.activerecord51?
219
+ def send_password_change_notification?
220
+ self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
221
+ end
222
+ else
223
+ def send_password_change_notification?
224
+ self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
225
+ end
126
226
  end
127
227
 
128
228
  module ClassMethods
129
- Devise::Models.config(self, :pepper, :stretches)
229
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
130
230
 
131
231
  # We assume this method already gets the sanitized values from the
132
232
  # DatabaseAuthenticatable strategy. If you are using this method on