mirror of
https://github.com/heartcombo/devise.git
synced 2022-11-09 12:18:31 -05:00
Add email confirmation when it is changed by a user
This commit is contained in:
parent
ef4eb47d50
commit
1961de6b5d
6 changed files with 169 additions and 10 deletions
|
@ -1,5 +1,5 @@
|
|||
<p>Welcome <%= @resource.email %>!</p>
|
||||
|
||||
<p>You can confirm your account through the link below:</p>
|
||||
<p>You can confirm your account email through the link below:</p>
|
||||
|
||||
<p><%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %></p>
|
||||
|
|
|
@ -139,6 +139,9 @@ module Devise
|
|||
mattr_accessor :confirmation_keys
|
||||
@@confirmation_keys = [ :email ]
|
||||
|
||||
mattr_accessor :confirmation_on_email_change
|
||||
@@confirmation_on_email_change = false
|
||||
|
||||
# Time interval to timeout the user session without activity.
|
||||
mattr_accessor :timeout_in
|
||||
@@timeout_in = 30.minutes
|
||||
|
|
|
@ -27,15 +27,20 @@ module Devise
|
|||
included do
|
||||
before_create :generate_confirmation_token, :if => :confirmation_required?
|
||||
after_create :send_confirmation_instructions, :if => :confirmation_required?
|
||||
before_update :prevent_email_change, :if => :prevent_email_change?
|
||||
after_update :send_confirmation_instructions, :if => :email_change_confirmation_required?
|
||||
end
|
||||
|
||||
# Confirm a user by setting its confirmed_at to actual time. If the user
|
||||
# is already confirmed, add en error to email field
|
||||
# Confirm a user by setting it's confirmed_at to actual time. If the user
|
||||
# is already confirmed, add en error to email field. If the user is invalid
|
||||
# add errors
|
||||
def confirm!
|
||||
unless_confirmed do
|
||||
self.confirmation_token = nil
|
||||
self.confirmed_at = Time.now
|
||||
save(:validate => false)
|
||||
self.email = unconfirmed_email if unconfirmed_email.present?
|
||||
self.unconfirmed_email = nil
|
||||
save
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -46,6 +51,7 @@ module Devise
|
|||
|
||||
# Send confirmation instructions by email
|
||||
def send_confirmation_instructions
|
||||
@email_change_confirmation_required = false
|
||||
generate_confirmation_token! if self.confirmation_token.nil?
|
||||
::Devise.mailer.confirmation_instructions(self).deliver
|
||||
end
|
||||
|
@ -104,10 +110,10 @@ module Devise
|
|||
confirmation_sent_at && confirmation_sent_at.utc >= self.class.confirm_within.ago
|
||||
end
|
||||
|
||||
# Checks whether the record is confirmed or not, yielding to the block
|
||||
# Checks whether the record is confirmed or not or a new email has been added, yielding to the block
|
||||
# if it's already confirmed, otherwise adds an error to email.
|
||||
def unless_confirmed
|
||||
unless confirmed?
|
||||
unless confirmed? && unconfirmed_email.blank?
|
||||
yield
|
||||
else
|
||||
self.errors.add(:email, :already_confirmed)
|
||||
|
@ -118,7 +124,6 @@ module Devise
|
|||
# Generates a new random token for confirmation, and stores the time
|
||||
# this token is being generated
|
||||
def generate_confirmation_token
|
||||
self.confirmed_at = nil
|
||||
self.confirmation_token = self.class.confirmation_token
|
||||
self.confirmation_sent_at = Time.now.utc
|
||||
end
|
||||
|
@ -132,13 +137,29 @@ module Devise
|
|||
confirm! unless confirmed?
|
||||
end
|
||||
|
||||
def prevent_email_change
|
||||
@email_change_confirmation_required = true
|
||||
self.unconfirmed_email = self.email
|
||||
self.email = self.email_was
|
||||
end
|
||||
|
||||
def prevent_email_change?
|
||||
self.class.confirmation_on_email_change && email_changed? && email != unconfirmed_email_was
|
||||
end
|
||||
|
||||
def email_change_confirmation_required?
|
||||
self.class.confirmation_on_email_change && @email_change_confirmation_required
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Attempt to find a user by its email. If a record is found, send new
|
||||
# confirmation instructions to it. If not user is found, returns a new user
|
||||
# with an email not found error.
|
||||
# confirmation instructions to it. If not, try searching for a user by unconfirmed_email
|
||||
# field. If no user is found, returns a new user with an email not found error.
|
||||
# Options must contain the user email
|
||||
def send_confirmation_instructions(attributes={})
|
||||
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
|
||||
temp = find_by_unconfirmed_email(confirmation_keys, attributes, :not_found)
|
||||
confirmable = temp if temp.persisted?
|
||||
confirmable.resend_confirmation_token if confirmable.persisted?
|
||||
confirmable
|
||||
end
|
||||
|
@ -158,7 +179,14 @@ module Devise
|
|||
generate_token(:confirmation_token)
|
||||
end
|
||||
|
||||
Devise::Models.config(self, :confirm_within, :confirmation_keys)
|
||||
# Find a record for confirmation by unconfirmed email field
|
||||
def find_by_unconfirmed_email(required_attributes, attributes, error=:invalid)
|
||||
confirmation_keys_with_replaced_email = required_attributes.map{ |k| k == :email ? :unconfirmed_email : k }
|
||||
attributes[:unconfirmed_email] = attributes.delete(:email)
|
||||
find_or_initialize_with_errors(confirmation_keys_with_replaced_email, attributes, :not_found)
|
||||
end
|
||||
|
||||
Devise::Models.config(self, :confirm_within, :confirmation_keys, :confirmation_on_email_change)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,7 @@ module Devise
|
|||
apply_devise_schema :confirmation_token, String
|
||||
apply_devise_schema :confirmed_at, DateTime
|
||||
apply_devise_schema :confirmation_sent_at, DateTime
|
||||
apply_devise_schema :unconfirmed_email, String
|
||||
end
|
||||
|
||||
# Creates reset_password_token and reset_password_sent_at.
|
||||
|
|
|
@ -177,3 +177,53 @@ class ConfirmationTest < ActionController::IntegrationTest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ConfirmationOnChangeTest < ConfirmationTest
|
||||
def setup
|
||||
Devise.confirmation_on_email_change = true
|
||||
end
|
||||
|
||||
def teardown
|
||||
Devise.confirmation_on_email_change = false
|
||||
end
|
||||
|
||||
test 'user should be able to request a new confirmation after email changed' do
|
||||
user = create_user(:confirm => true)
|
||||
user.update_attributes(:email => 'new_test@example.com')
|
||||
ActionMailer::Base.deliveries.clear
|
||||
|
||||
visit new_user_session_path
|
||||
click_link "Didn't receive confirmation instructions?"
|
||||
|
||||
fill_in 'email', :with => user.unconfirmed_email
|
||||
click_button 'Resend confirmation instructions'
|
||||
|
||||
assert_current_url '/users/sign_in'
|
||||
assert_contain 'You will receive an email with instructions about how to confirm your account in a few minutes'
|
||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
|
||||
test 'user with valid confirmation token should be able to confirm email after email changed' do
|
||||
user = create_user(:confirm => true)
|
||||
user.update_attributes(:email => 'new_test@example.com')
|
||||
assert 'new_test@example.com', user.unconfirmed_email
|
||||
visit_user_confirmation_with_token(user.confirmation_token)
|
||||
|
||||
assert_contain 'Your account was successfully confirmed.'
|
||||
assert_current_url '/'
|
||||
assert user.reload.confirmed?
|
||||
end
|
||||
|
||||
test 'user who changed email should get a detailed message about email being not unique' do
|
||||
user = create_user(:confirm => true)
|
||||
user.update_attributes(:email => 'new_test@example.com')
|
||||
assert 'new_test@example.com', user.unconfirmed_email
|
||||
|
||||
@user = nil
|
||||
create_user(:email => 'new_test@example.com', :confirm => true)
|
||||
|
||||
visit_user_confirmation_with_token(user.confirmation_token)
|
||||
|
||||
assert_contain /Email.*already.*taken/
|
||||
end
|
||||
end
|
||||
|
|
|
@ -236,3 +236,80 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ConfirmableOnChangeTest < ConfirmableTest
|
||||
def setup
|
||||
Devise.confirmation_on_email_change = true
|
||||
end
|
||||
|
||||
def teardown
|
||||
Devise.confirmation_on_email_change = false
|
||||
end
|
||||
|
||||
def test_should_not_resend_email_instructions_if_the_user_change_his_email
|
||||
#behaves differently
|
||||
end
|
||||
|
||||
def test_should_not_reset_confirmation_status_or_token_when_updating_email
|
||||
#behaves differently
|
||||
end
|
||||
|
||||
test 'should generate confirmation token after changing email' do
|
||||
user = create_user
|
||||
assert user.confirm!
|
||||
assert_nil user.confirmation_token
|
||||
assert user.update_attributes(:email => 'new_test@example.com')
|
||||
assert_not_nil user.confirmation_token
|
||||
end
|
||||
|
||||
test 'should send confirmation instructions by email after changing email' do
|
||||
user = create_user
|
||||
assert user.confirm!
|
||||
assert_email_sent do
|
||||
assert user.update_attributes(:email => 'new_test@example.com')
|
||||
end
|
||||
end
|
||||
|
||||
test 'should not send confirmation by email after changing password' do
|
||||
user = create_user
|
||||
assert user.confirm!
|
||||
assert_email_not_sent do
|
||||
assert user.update_attributes(:password => 'newpass', :password_confirmation => 'newpass')
|
||||
end
|
||||
end
|
||||
|
||||
test 'should stay confirmed when email is changed' do
|
||||
user = create_user
|
||||
assert user.confirm!
|
||||
assert user.update_attributes(:email => 'new_test@example.com')
|
||||
assert user.confirmed?
|
||||
end
|
||||
|
||||
test 'should update email only when it is confirmed' do
|
||||
user = create_user
|
||||
assert user.confirm!
|
||||
assert user.update_attributes(:email => 'new_test@example.com')
|
||||
assert_not_equal 'new_test@example.com', user.email
|
||||
assert user.confirm!
|
||||
assert_equal 'new_test@example.com', user.email
|
||||
end
|
||||
|
||||
test 'should find a user by send confirmation instructions with unconfirmed_email' do
|
||||
user = create_user
|
||||
assert user.confirm!
|
||||
assert user.update_attributes(:email => 'new_test@example.com')
|
||||
confirmation_user = User.send_confirmation_instructions(:email => user.unconfirmed_email)
|
||||
assert_equal confirmation_user, user
|
||||
end
|
||||
|
||||
test 'should return a new user if no email or unconfirmed_email was found' do
|
||||
confirmation_user = User.send_confirmation_instructions(:email => "invalid@email.com")
|
||||
assert_not confirmation_user.persisted?
|
||||
end
|
||||
|
||||
test 'should add error to new user email if no email or unconfirmed_email was found' do
|
||||
confirmation_user = User.send_confirmation_instructions(:email => "invalid@email.com")
|
||||
assert confirmation_user.errors[:email]
|
||||
assert_equal "not found", confirmation_user.errors[:email].join
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue