1
0
Fork 0
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:
mandaryn 2011-04-03 21:26:14 +02:00 committed by Brian Rose
parent ef4eb47d50
commit 1961de6b5d
6 changed files with 169 additions and 10 deletions

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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