devise 2.1.4 → 2.2.0.rc

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

Files changed (84) hide show
  1. data/.travis.yml +26 -0
  2. data/CHANGELOG.rdoc +29 -10
  3. data/CONTRIBUTING.md +14 -0
  4. data/Gemfile +2 -8
  5. data/Gemfile.lock +68 -77
  6. data/README.md +15 -9
  7. data/app/controllers/devise/omniauth_callbacks_controller.rb +1 -1
  8. data/app/controllers/devise/passwords_controller.rb +9 -0
  9. data/app/controllers/devise/registrations_controller.rb +17 -5
  10. data/app/controllers/devise/sessions_controller.rb +3 -3
  11. data/app/controllers/devise_controller.rb +3 -11
  12. data/app/views/devise/confirmations/new.html.erb +1 -1
  13. data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
  14. data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
  15. data/app/views/devise/passwords/edit.html.erb +1 -1
  16. data/app/views/devise/passwords/new.html.erb +1 -1
  17. data/app/views/devise/registrations/edit.html.erb +5 -1
  18. data/app/views/devise/registrations/new.html.erb +1 -1
  19. data/app/views/devise/sessions/new.html.erb +1 -1
  20. data/app/views/devise/unlocks/new.html.erb +1 -1
  21. data/config/locales/en.yml +2 -0
  22. data/gemfiles/Gemfile.rails-3.1.x +1 -3
  23. data/gemfiles/Gemfile.rails-3.1.x.lock +9 -12
  24. data/lib/devise.rb +12 -7
  25. data/lib/devise/controllers/helpers.rb +16 -3
  26. data/lib/devise/mailers/helpers.rb +14 -8
  27. data/lib/devise/models/authenticatable.rb +14 -9
  28. data/lib/devise/models/confirmable.rb +36 -3
  29. data/lib/devise/models/database_authenticatable.rb +2 -1
  30. data/lib/devise/models/lockable.rb +6 -2
  31. data/lib/devise/models/recoverable.rb +2 -2
  32. data/lib/devise/models/token_authenticatable.rb +12 -0
  33. data/lib/devise/models/trackable.rb +1 -1
  34. data/lib/devise/models/validatable.rb +1 -1
  35. data/lib/devise/param_filter.rb +2 -1
  36. data/lib/devise/rails/routes.rb +11 -11
  37. data/lib/devise/strategies/database_authenticatable.rb +1 -1
  38. data/lib/devise/test_helpers.rb +3 -2
  39. data/lib/devise/time_inflector.rb +14 -0
  40. data/lib/devise/version.rb +1 -1
  41. data/lib/generators/active_record/devise_generator.rb +5 -1
  42. data/lib/generators/mongoid/devise_generator.rb +0 -3
  43. data/lib/generators/templates/README +5 -1
  44. data/lib/generators/templates/devise.rb +12 -4
  45. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  46. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  47. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +2 -1
  48. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
  49. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +1 -1
  50. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +5 -0
  51. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +2 -1
  52. data/test/controllers/internal_helpers_test.rb +6 -0
  53. data/test/controllers/sessions_controller_test.rb +43 -1
  54. data/test/devise_test.rb +12 -1
  55. data/test/generators/active_record_generator_test.rb +7 -1
  56. data/test/integration/authenticatable_test.rb +4 -4
  57. data/test/integration/confirmable_test.rb +44 -0
  58. data/test/integration/database_authenticatable_test.rb +17 -15
  59. data/test/integration/http_authenticatable_test.rb +3 -3
  60. data/test/integration/lockable_test.rb +18 -0
  61. data/test/integration/omniauthable_test.rb +2 -2
  62. data/test/integration/recoverable_test.rb +43 -6
  63. data/test/integration/registerable_test.rb +34 -11
  64. data/test/mailers/confirmation_instructions_test.rb +7 -0
  65. data/test/models/authenticatable_test.rb +1 -7
  66. data/test/models/confirmable_test.rb +35 -1
  67. data/test/models/database_authenticatable_test.rb +49 -24
  68. data/test/models/lockable_test.rb +13 -3
  69. data/test/models/serializable_test.rb +2 -1
  70. data/test/models_test.rb +1 -22
  71. data/test/orm/mongoid.rb +2 -3
  72. data/test/rails_app/app/controllers/application_controller.rb +1 -0
  73. data/test/rails_app/app/mailers/users/mailer.rb +6 -1
  74. data/test/rails_app/app/mongoid/admin.rb +4 -2
  75. data/test/rails_app/app/mongoid/shim.rb +3 -3
  76. data/test/rails_app/app/mongoid/user.rb +2 -2
  77. data/test/rails_app/config/initializers/devise.rb +2 -2
  78. data/test/rails_app/lib/shared_user.rb +1 -1
  79. data/test/support/helpers.rb +2 -2
  80. data/test/support/integration.rb +4 -3
  81. data/test/test_helpers_test.rb +19 -2
  82. data/test/test_models.rb +27 -0
  83. metadata +31 -29
  84. data/test/indifferent_hash.rb +0 -33
@@ -5,14 +5,14 @@ class Devise::SessionsController < DeviseController
5
5
 
6
6
  # GET /resource/sign_in
7
7
  def new
8
- resource = build_resource(nil, :unsafe => true)
8
+ self.resource = build_resource(nil, :unsafe => true)
9
9
  clean_up_passwords(resource)
10
10
  respond_with(resource, serialize_options(resource))
11
11
  end
12
12
 
13
13
  # POST /resource/sign_in
14
14
  def create
15
- resource = warden.authenticate!(auth_options)
15
+ self.resource = warden.authenticate!(auth_options)
16
16
  set_flash_message(:notice, :signed_in) if is_navigational_format?
17
17
  sign_in(resource_name, resource)
18
18
  respond_with resource, :location => after_sign_in_path_for(resource)
@@ -22,7 +22,7 @@ class Devise::SessionsController < DeviseController
22
22
  def destroy
23
23
  redirect_path = after_sign_out_path_for(resource_name)
24
24
  signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
25
- set_flash_message :notice, :signed_out if signed_out
25
+ set_flash_message :notice, :signed_out if signed_out && is_navigational_format?
26
26
 
27
27
  # We actually need to hardcode this as Rails default responder doesn't
28
28
  # support returning empty response on GET request
@@ -10,7 +10,7 @@ class DeviseController < Devise.parent_controller.constantize
10
10
  helper_method *helpers
11
11
 
12
12
  prepend_before_filter :assert_is_devise_resource!
13
- respond_to *Mime::SET.map(&:to_sym) if mimes_for_respond_to.empty?
13
+ respond_to :html if mimes_for_respond_to.empty?
14
14
 
15
15
  # Gets the actual resource stored in the instance variable
16
16
  def resource
@@ -72,7 +72,7 @@ This may happen for two reasons:
72
72
 
73
73
  2) You are testing a Devise controller bypassing the router.
74
74
  If so, you can explicitly tell Devise which mapping to use:
75
-
75
+
76
76
  @request.env["devise.mapping"] = Devise.mappings[:user]
77
77
 
78
78
  MESSAGE
@@ -168,7 +168,7 @@ MESSAGE
168
168
  options[:default] = Array(options[:default]).unshift(kind.to_sym)
169
169
  options[:resource_name] = resource_name
170
170
  options = devise_i18n_options(options) if respond_to?(:devise_i18n_options, true)
171
- message = I18n.t("#{resource_name}.#{kind}", options)
171
+ message = I18n.t("#{options[:resource_name]}.#{kind}", options)
172
172
  flash[key] = message if message.present?
173
173
  end
174
174
 
@@ -181,12 +181,4 @@ MESSAGE
181
181
  format.any(*navigational_formats, &block)
182
182
  end
183
183
  end
184
-
185
- def request_format
186
- @request_format ||= request.format.try(:ref)
187
- end
188
-
189
- def is_navigational_format?
190
- Devise.navigational_formats.include?(request.format.try(:ref))
191
- end
192
184
  end
@@ -4,7 +4,7 @@
4
4
  <%= devise_error_messages! %>
5
5
 
6
6
  <div><%= f.label :email %><br />
7
- <%= f.email_field :email %></div>
7
+ <%= f.email_field :email, :autofocus => true %></div>
8
8
 
9
9
  <div><%= f.submit "Resend confirmation instructions" %></div>
10
10
  <% end %>
@@ -1,4 +1,4 @@
1
- <p>Welcome <%= @resource.email %>!</p>
1
+ <p>Welcome <%= @email %>!</p>
2
2
 
3
3
  <p>You can confirm your account email through the link below:</p>
4
4
 
@@ -1,6 +1,6 @@
1
1
  <p>Hello <%= @resource.email %>!</p>
2
2
 
3
- <p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p>
3
+ <p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
4
4
 
5
5
  <p>Click the link below to unlock your account:</p>
6
6
 
@@ -5,7 +5,7 @@
5
5
  <%= f.hidden_field :reset_password_token %>
6
6
 
7
7
  <div><%= f.label :password, "New password" %><br />
8
- <%= f.password_field :password %></div>
8
+ <%= f.password_field :password, :autofocus => true %></div>
9
9
 
10
10
  <div><%= f.label :password_confirmation, "Confirm new password" %><br />
11
11
  <%= f.password_field :password_confirmation %></div>
@@ -4,7 +4,7 @@
4
4
  <%= devise_error_messages! %>
5
5
 
6
6
  <div><%= f.label :email %><br />
7
- <%= f.email_field :email %></div>
7
+ <%= f.email_field :email, :autofocus => true %></div>
8
8
 
9
9
  <div><%= f.submit "Send me reset password instructions" %></div>
10
10
  <% end %>
@@ -4,7 +4,11 @@
4
4
  <%= devise_error_messages! %>
5
5
 
6
6
  <div><%= f.label :email %><br />
7
- <%= f.email_field :email %></div>
7
+ <%= f.email_field :email, :autofocus => true %></div>
8
+
9
+ <% if resource.class.reconfirmable && resource.unconfirmed_email.present? %>
10
+ <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
11
+ <% end %>
8
12
 
9
13
  <div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
10
14
  <%= f.password_field :password, :autocomplete => "off" %></div>
@@ -4,7 +4,7 @@
4
4
  <%= devise_error_messages! %>
5
5
 
6
6
  <div><%= f.label :email %><br />
7
- <%= f.email_field :email %></div>
7
+ <%= f.email_field :email, :autofocus => true %></div>
8
8
 
9
9
  <div><%= f.label :password %><br />
10
10
  <%= f.password_field :password %></div>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
4
4
  <div><%= f.label :email %><br />
5
- <%= f.email_field :email %></div>
5
+ <%= f.email_field :email, :autofocus => true %></div>
6
6
 
7
7
  <div><%= f.label :password %><br />
8
8
  <%= f.password_field :password %></div>
@@ -4,7 +4,7 @@
4
4
  <%= devise_error_messages! %>
5
5
 
6
6
  <div><%= f.label :email %><br />
7
- <%= f.email_field :email %></div>
7
+ <%= f.email_field :email, :autofocus => true %></div>
8
8
 
9
9
  <div><%= f.submit "Resend unlock instructions" %></div>
10
10
  <% end %>
@@ -10,6 +10,7 @@ en:
10
10
  not_saved:
11
11
  one: "1 error prohibited this %{resource} from being saved:"
12
12
  other: "%{count} errors prohibited this %{resource} from being saved:"
13
+ confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
13
14
 
14
15
  devise:
15
16
  failure:
@@ -17,6 +18,7 @@ en:
17
18
  unauthenticated: 'You need to sign in or sign up before continuing.'
18
19
  unconfirmed: 'You have to confirm your account before continuing.'
19
20
  locked: 'Your account is locked.'
21
+ not_found_in_database: 'Invalid email or password.'
20
22
  invalid: 'Invalid email or password.'
21
23
  invalid_token: 'Invalid authentication token.'
22
24
  timeout: 'Your session expired, please sign in again to continue.'
@@ -28,8 +28,6 @@ platforms :ruby do
28
28
  gem "sqlite3"
29
29
 
30
30
  group :mongoid do
31
- gem "mongo", "~> 1.3.0"
32
- gem "mongoid", "~> 2.0"
33
- gem "bson_ext", "~> 1.3.0"
31
+ gem "mongoid", "~> 3.0"
34
32
  end
35
33
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- devise (2.1.4)
4
+ devise (2.1.2)
5
5
  bcrypt-ruby (~> 3.0)
6
6
  orm_adapter (~> 0.1)
7
7
  railties (~> 3.1)
@@ -40,9 +40,7 @@ GEM
40
40
  multi_json (~> 1.0)
41
41
  addressable (2.2.7)
42
42
  arel (2.2.3)
43
- bcrypt-ruby (3.1.1)
44
- bson (1.5.2)
45
- bson_ext (1.3.1)
43
+ bcrypt-ruby (3.0.1)
46
44
  builder (3.0.0)
47
45
  columnize (0.3.6)
48
46
  erubis (2.7.0)
@@ -64,12 +62,12 @@ GEM
64
62
  mime-types (1.18)
65
63
  mocha (0.10.4)
66
64
  metaclass (~> 0.0.1)
67
- mongo (1.3.1)
68
- bson (>= 1.3.1)
69
- mongoid (2.4.4)
65
+ mongoid (3.0.12)
70
66
  activemodel (~> 3.1)
71
- mongo (~> 1.3)
67
+ moped (~> 1.1)
68
+ origin (~> 1.0)
72
69
  tzinfo (~> 0.3.22)
70
+ moped (1.2.9)
73
71
  multi_json (1.3.4)
74
72
  multipart-post (1.1.5)
75
73
  nokogiri (1.5.0)
@@ -87,6 +85,7 @@ GEM
87
85
  omniauth-openid (1.0.1)
88
86
  omniauth (~> 1.0)
89
87
  rack-openid (~> 1.3.1)
88
+ origin (1.0.10)
90
89
  orm_adapter (0.4.0)
91
90
  polyglot (0.3.3)
92
91
  rack (1.3.6)
@@ -137,7 +136,7 @@ GEM
137
136
  polyglot
138
137
  polyglot (>= 0.3.1)
139
138
  tzinfo (0.3.33)
140
- warden (1.2.3)
139
+ warden (1.2.1)
141
140
  rack (>= 1.0)
142
141
  webrat (0.7.2)
143
142
  nokogiri (>= 1.2.0)
@@ -150,12 +149,10 @@ PLATFORMS
150
149
  DEPENDENCIES
151
150
  activerecord-jdbc-adapter
152
151
  activerecord-jdbcsqlite3-adapter
153
- bson_ext (~> 1.3.0)
154
152
  devise!
155
153
  jruby-openssl
156
154
  mocha
157
- mongo (~> 1.3.0)
158
- mongoid (~> 2.0)
155
+ mongoid (~> 3.0)
159
156
  omniauth (~> 1.0.0)
160
157
  omniauth-facebook
161
158
  omniauth-oauth2 (~> 1.0.0)
@@ -6,11 +6,12 @@ require 'set'
6
6
  require 'securerandom'
7
7
 
8
8
  module Devise
9
- autoload :Delegator, 'devise/delegator'
10
- autoload :FailureApp, 'devise/failure_app'
11
- autoload :OmniAuth, 'devise/omniauth'
12
- autoload :ParamFilter, 'devise/param_filter'
13
- autoload :TestHelpers, 'devise/test_helpers'
9
+ autoload :Delegator, 'devise/delegator'
10
+ autoload :FailureApp, 'devise/failure_app'
11
+ autoload :OmniAuth, 'devise/omniauth'
12
+ autoload :ParamFilter, 'devise/param_filter'
13
+ autoload :TestHelpers, 'devise/test_helpers'
14
+ autoload :TimeInflector, 'devise/time_inflector'
14
15
 
15
16
  module Controllers
16
17
  autoload :Helpers, 'devise/controllers/helpers'
@@ -86,7 +87,7 @@ module Devise
86
87
  # an one (and only one) @ exists in the given string. This is mainly
87
88
  # to give user feedback and not to assert the e-mail validity.
88
89
  mattr_accessor :email_regexp
89
- @@email_regexp = /\A[^@]+@([^@\.]+\.)+[^@\.]+\z/
90
+ @@email_regexp = /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
90
91
 
91
92
  # Range validation for password length
92
93
  mattr_accessor :password_length
@@ -104,6 +105,10 @@ module Devise
104
105
  mattr_accessor :allow_unconfirmed_access_for
105
106
  @@allow_unconfirmed_access_for = 0.days
106
107
 
108
+ # Time interval the confirmation token is valid. nil = unlimited
109
+ mattr_accessor :confirm_within
110
+ @@confirm_within = nil
111
+
107
112
  # Defines which key will be used when confirming an account.
108
113
  mattr_accessor :confirmation_keys
109
114
  @@confirmation_keys = [ :email ]
@@ -199,7 +204,7 @@ module Devise
199
204
  # to provide custom routes.
200
205
  mattr_accessor :router_name
201
206
  @@router_name = nil
202
-
207
+
203
208
  # Set the omniauth path prefix so it can be overriden when
204
209
  # Devise is used in a mountable engine
205
210
  mattr_accessor :omniauth_path_prefix
@@ -162,8 +162,8 @@ module Devise
162
162
  users.any?
163
163
  end
164
164
 
165
- # Returns and delete the url stored in the session for the given scope. Useful
166
- # for giving redirect backs after sign up:
165
+ # Returns and delete (if it's navigational format) the url stored in the session for
166
+ # the given scope. Useful for giving redirect backs after sign up:
167
167
  #
168
168
  # Example:
169
169
  #
@@ -171,7 +171,12 @@ module Devise
171
171
  #
172
172
  def stored_location_for(resource_or_scope)
173
173
  scope = Devise::Mapping.find_scope!(resource_or_scope)
174
- session.delete("#{scope}_return_to")
174
+
175
+ if is_navigational_format?
176
+ session.delete("#{scope}_return_to")
177
+ else
178
+ session["#{scope}_return_to"]
179
+ end
175
180
  end
176
181
 
177
182
  # The scope root url to be used when he's signed in. By default, it first
@@ -262,6 +267,14 @@ module Devise
262
267
  super # call the default behaviour which resets the session
263
268
  end
264
269
 
270
+ def request_format
271
+ @request_format ||= request.format.try(:ref)
272
+ end
273
+
274
+ def is_navigational_format?
275
+ Devise.navigational_formats.include?(request_format)
276
+ end
277
+
265
278
  private
266
279
 
267
280
  def expire_devise_cached_variables!
@@ -28,8 +28,9 @@ module Devise
28
28
  def headers_for(action)
29
29
  headers = {
30
30
  :subject => translate(devise_mapping, action),
31
- :from => mailer_sender(devise_mapping),
32
31
  :to => resource.email,
32
+ :from => mailer_sender(devise_mapping),
33
+ :reply_to => mailer_reply_to(devise_mapping),
33
34
  :template_path => template_paths
34
35
  }
35
36
 
@@ -37,16 +38,21 @@ module Devise
37
38
  headers.merge!(resource.headers_for(action))
38
39
  end
39
40
 
40
- unless headers.key?(:reply_to)
41
- headers[:reply_to] = headers[:from]
42
- end
43
-
41
+ @email = headers[:to]
44
42
  headers
45
43
  end
46
44
 
47
- def mailer_sender(mapping)
48
- if default_params[:from].present?
49
- default_params[:from]
45
+ def mailer_reply_to(mapping)
46
+ mailer_sender(mapping, :reply_to)
47
+ end
48
+
49
+ def mailer_from(mapping)
50
+ mailer_sender(mapping, :from)
51
+ end
52
+
53
+ def mailer_sender(mapping, sender = :from)
54
+ if default_params[sender].present?
55
+ default_params[sender]
50
56
  elsif Devise.mailer_sender.is_a?(Proc)
51
57
  Devise.mailer_sender.call(mapping.name)
52
58
  else
@@ -164,11 +164,15 @@ module Devise
164
164
  end
165
165
 
166
166
  def downcase_keys
167
- self.class.case_insensitive_keys.each { |k| self[k].try(:downcase!) }
167
+ self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase!) }
168
168
  end
169
169
 
170
170
  def strip_whitespace
171
- self.class.strip_whitespace_keys.each { |k| self[k].try(:strip!) }
171
+ self.class.strip_whitespace_keys.each { |k| apply_to_attribute_or_variable(k, :strip!) }
172
+ end
173
+
174
+ def apply_to_attribute_or_variable(attr, method)
175
+ (self[attr] || send(attr)).try(method)
172
176
  end
173
177
 
174
178
  module ClassMethods
@@ -199,26 +203,27 @@ module Devise
199
203
  # it may be wrapped as well. For instance, database authenticatable
200
204
  # provides a `find_for_database_authentication` that wraps a call to
201
205
  # this method. This allows you to customize both database authenticatable
202
- # or the whole authenticate stack by customize `find_for_authentication.`
206
+ # or the whole authenticate stack by customize `find_for_authentication.`
203
207
  #
204
208
  # Overwrite to add customized conditions, create a join, or maybe use a
205
209
  # namedscope to filter records while authenticating.
206
210
  # Example:
207
211
  #
208
- # def self.find_for_authentication(tainted_conditions)
209
- # find_first_by_auth_conditions(tainted_conditions, :active => true)
212
+ # def self.find_for_authentication(conditions={})
213
+ # conditions[:active] = true
214
+ # super
210
215
  # end
211
216
  #
212
217
  # Finally, notice that Devise also queries for users in other scenarios
213
218
  # besides authentication, for example when retrieving an user to send
214
219
  # an e-mail for password reset. In such cases, find_for_authentication
215
220
  # is not called.
216
- def find_for_authentication(tainted_conditions)
217
- find_first_by_auth_conditions(tainted_conditions)
221
+ def find_for_authentication(conditions)
222
+ find_first_by_auth_conditions(conditions)
218
223
  end
219
224
 
220
- def find_first_by_auth_conditions(tainted_conditions, opts={})
221
- to_adapter.find_first(devise_param_filter.filter(tainted_conditions).merge(opts))
225
+ def find_first_by_auth_conditions(conditions)
226
+ to_adapter.find_first devise_param_filter.filter(conditions)
222
227
  end
223
228
 
224
229
  # Find an initialize a record setting an error if it can't be found.
@@ -19,6 +19,8 @@ module Devise
19
19
  # db field to be setup (t.reconfirmable in migrations). Until confirmed new email is
20
20
  # stored in unconfirmed email column, and copied to email column on successful
21
21
  # confirmation.
22
+ # * +confirm_within+: the time before a sent confirmation token becomes invalid.
23
+ # You can use this to force the user to confirm within a set period of time.
22
24
  #
23
25
  # == Examples
24
26
  #
@@ -28,6 +30,7 @@ module Devise
28
30
  #
29
31
  module Confirmable
30
32
  extend ActiveSupport::Concern
33
+ include ActionView::Helpers::DateHelper
31
34
 
32
35
  included do
33
36
  before_create :generate_confirmation_token, :if => :confirmation_required?
@@ -47,6 +50,12 @@ module Devise
47
50
  # add errors
48
51
  def confirm!
49
52
  pending_any_confirmation do
53
+ if confirmation_period_expired?
54
+ self.errors.add(:email, :confirmation_period_expired,
55
+ :period => Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
56
+ return false
57
+ end
58
+
50
59
  self.confirmation_token = nil
51
60
  self.confirmed_at = Time.now.utc
52
61
 
@@ -83,7 +92,10 @@ module Devise
83
92
 
84
93
  # Resend confirmation token. This method does not need to generate a new token.
85
94
  def resend_confirmation_token
86
- pending_any_confirmation { send_confirmation_instructions }
95
+ pending_any_confirmation do
96
+ self.confirmation_token = nil if confirmation_period_expired?
97
+ send_confirmation_instructions
98
+ end
87
99
  end
88
100
 
89
101
  # Overwrites active_for_authentication? for confirmation
@@ -156,9 +168,25 @@ module Devise
156
168
  confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
157
169
  end
158
170
 
171
+ # Checks if the user confirmation happens before the token becomes invalid
172
+ # Examples:
173
+ #
174
+ # # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
175
+ # confirmation_period_expired? # returns false
176
+ #
177
+ # # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
178
+ # confirmation_period_expired? # returns true
179
+ #
180
+ # # confirm_within = nil
181
+ # confirmation_period_expired? # will always return false
182
+ #
183
+ def confirmation_period_expired?
184
+ self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
185
+ end
186
+
159
187
  # Checks whether the record requires any confirmation.
160
188
  def pending_any_confirmation
161
- if !confirmed? || pending_reconfirmation?
189
+ if (!confirmed? || pending_reconfirmation?)
162
190
  yield
163
191
  else
164
192
  self.errors.add(:email, :already_confirmed)
@@ -177,6 +205,11 @@ module Devise
177
205
  generate_confirmation_token && save(:validate => false)
178
206
  end
179
207
 
208
+ def after_password_reset
209
+ super
210
+ confirm! unless confirmed?
211
+ end
212
+
180
213
  def postpone_email_change_until_confirmation
181
214
  @reconfirmation_required = true
182
215
  self.unconfirmed_email = self.email
@@ -230,7 +263,7 @@ module Devise
230
263
  find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
231
264
  end
232
265
 
233
- Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable)
266
+ Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
234
267
  end
235
268
  end
236
269
  end