mirror of
https://github.com/discourse/discourse.git
synced 2025-03-09 14:34:35 +00:00
See https://meta.discourse.org/t/changing-a-users-email/164512 for context. When admin changes an email for a user, we were incorrectly sending the password reset email to the user's old address. Also the new email does not come into effect until the reset password process is done, so this PR adds some notes to the admin to make this clearer.
335 lines
11 KiB
Ruby
335 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe EmailUpdater do
|
|
let(:old_email) { 'old.email@example.com' }
|
|
let(:new_email) { 'new.email@example.com' }
|
|
|
|
it "provides better error message when a staged user has the same email" do
|
|
Fabricate(:user, staged: true, email: new_email)
|
|
|
|
user = Fabricate(:user, email: old_email)
|
|
updater = EmailUpdater.new(guardian: user.guardian, user: user)
|
|
updater.change_to(new_email)
|
|
|
|
expect(updater.errors).to be_present
|
|
expect(updater.errors.messages[:base].first).to be I18n.t("change_email.error_staged")
|
|
end
|
|
|
|
context "when an admin is changing the email of another user" do
|
|
let(:admin) { Fabricate(:admin) }
|
|
let(:updater) { EmailUpdater.new(guardian: admin.guardian, user: user) }
|
|
|
|
def expect_old_email_job
|
|
expect_enqueued_with(job: :critical_user_email, args: { to_address: old_email, type: :notify_old_email, user_id: user.id }) do
|
|
yield
|
|
end
|
|
end
|
|
|
|
def expect_forgot_password_job
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :forgot_password, user_id: user.id }) do
|
|
yield
|
|
end
|
|
end
|
|
|
|
context "for a regular user" do
|
|
let(:user) { Fabricate(:user, email: old_email) }
|
|
|
|
it "does not send an email to the user for them to confirm their new email but still sends the notification to the old email" do
|
|
expect_old_email_job do
|
|
expect_forgot_password_job do
|
|
updater.change_to(new_email)
|
|
|
|
expect(Jobs::CriticalUserEmail.jobs.size).to eq(2)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
it "creates a change request authorizing the new email and immediately confirms it " do
|
|
updater.change_to(new_email)
|
|
user.reload
|
|
expect(user.reload.email).to eq(new_email)
|
|
end
|
|
|
|
it "sends a reset password email to the user so they can set a password for their new email" do
|
|
expect_old_email_job do
|
|
expect_forgot_password_job do
|
|
updater.change_to(new_email)
|
|
end
|
|
end
|
|
|
|
expect(EmailToken.where(user: user).last.email).to eq(new_email)
|
|
end
|
|
end
|
|
|
|
context "for a staff user" do
|
|
let(:user) { Fabricate(:moderator, email: old_email) }
|
|
|
|
before do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_old_email, to_address: old_email }) do
|
|
updater.change_to(new_email)
|
|
end
|
|
|
|
@change_req = user.email_change_requests.first
|
|
end
|
|
|
|
it "starts the old confirmation process" do
|
|
expect(updater.errors).to be_blank
|
|
|
|
expect(@change_req.old_email).to eq(old_email)
|
|
expect(@change_req.new_email).to eq(new_email)
|
|
expect(@change_req).to be_present
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:authorizing_old])
|
|
|
|
expect(@change_req.old_email_token.email).to eq(old_email)
|
|
expect(@change_req.new_email_token).to be_blank
|
|
end
|
|
|
|
it "does not immediately confirm the request" do
|
|
expect(@change_req.change_state).not_to eq(EmailChangeRequest.states[:complete])
|
|
end
|
|
end
|
|
|
|
context "when changing their own email" do
|
|
let(:user) { admin }
|
|
|
|
before do
|
|
admin.update(email: old_email)
|
|
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_old_email, to_address: old_email }) do
|
|
updater.change_to(new_email)
|
|
end
|
|
|
|
@change_req = user.email_change_requests.first
|
|
end
|
|
|
|
it "starts the old confirmation process" do
|
|
expect(updater.errors).to be_blank
|
|
|
|
expect(@change_req.old_email).to eq(old_email)
|
|
expect(@change_req.new_email).to eq(new_email)
|
|
expect(@change_req).to be_present
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:authorizing_old])
|
|
|
|
expect(@change_req.old_email_token.email).to eq(old_email)
|
|
expect(@change_req.new_email_token).to be_blank
|
|
end
|
|
|
|
it "does not immediately confirm the request" do
|
|
expect(@change_req.change_state).not_to eq(EmailChangeRequest.states[:complete])
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'as a regular user' do
|
|
let(:user) { Fabricate(:user, email: old_email) }
|
|
let(:updater) { EmailUpdater.new(guardian: user.guardian, user: user) }
|
|
|
|
context "changing primary email" do
|
|
before do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_new_email, to_address: new_email }) do
|
|
updater.change_to(new_email)
|
|
end
|
|
|
|
@change_req = user.email_change_requests.first
|
|
end
|
|
|
|
it "starts the new confirmation process" do
|
|
expect(updater.errors).to be_blank
|
|
|
|
expect(@change_req).to be_present
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:authorizing_new])
|
|
|
|
expect(@change_req.old_email).to eq(old_email)
|
|
expect(@change_req.new_email).to eq(new_email)
|
|
expect(@change_req.old_email_token).to be_blank
|
|
expect(@change_req.new_email_token.email).to eq(new_email)
|
|
end
|
|
|
|
context 'confirming an invalid token' do
|
|
it "produces an error" do
|
|
updater.confirm('random')
|
|
expect(updater.errors).to be_present
|
|
expect(user.reload.email).not_to eq(new_email)
|
|
end
|
|
end
|
|
|
|
context 'confirming a valid token' do
|
|
it "updates the user's email" do
|
|
event = DiscourseEvent.track_events {
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :notify_old_email, to_address: old_email }) do
|
|
updater.confirm(@change_req.new_email_token.token)
|
|
end
|
|
}.last
|
|
|
|
expect(updater.errors).to be_blank
|
|
expect(user.reload.email).to eq(new_email)
|
|
|
|
expect(event[:event_name]).to eq(:user_updated)
|
|
expect(event[:params].first).to eq(user)
|
|
|
|
@change_req.reload
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:complete])
|
|
end
|
|
end
|
|
end
|
|
|
|
context "adding an email" do
|
|
before do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_new_email, to_address: new_email }) do
|
|
updater.change_to(new_email, add: true)
|
|
end
|
|
|
|
@change_req = user.email_change_requests.first
|
|
end
|
|
|
|
context 'confirming a valid token' do
|
|
it "adds a user email" do
|
|
expect(UserHistory.where(action: UserHistory.actions[:add_email], acting_user_id: user.id).last).to be_present
|
|
|
|
event = DiscourseEvent.track_events {
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :notify_old_email_add, to_address: old_email }) do
|
|
updater.confirm(@change_req.new_email_token.token)
|
|
end
|
|
}.last
|
|
|
|
expect(updater.errors).to be_blank
|
|
expect(UserEmail.where(user_id: user.id).pluck(:email)).to contain_exactly(user.email, new_email)
|
|
|
|
expect(event[:event_name]).to eq(:user_updated)
|
|
expect(event[:params].first).to eq(user)
|
|
|
|
@change_req.reload
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:complete])
|
|
end
|
|
end
|
|
|
|
context 'that was deleted before' do
|
|
it 'works' do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :notify_old_email_add, to_address: old_email }) do
|
|
updater.confirm(@change_req.new_email_token.token)
|
|
end
|
|
|
|
expect(user.reload.user_emails.pluck(:email)).to contain_exactly(old_email, new_email)
|
|
|
|
user.user_emails.where(email: new_email).delete_all
|
|
expect(user.reload.user_emails.pluck(:email)).to contain_exactly(old_email)
|
|
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_new_email, to_address: new_email }) do
|
|
updater.change_to(new_email, add: true)
|
|
end
|
|
|
|
@change_req = user.email_change_requests.first
|
|
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :notify_old_email_add, to_address: old_email }) do
|
|
updater.confirm(@change_req.new_email_token.token)
|
|
end
|
|
|
|
expect(user.reload.user_emails.pluck(:email)).to contain_exactly(old_email, new_email)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'as a staff user' do
|
|
let(:user) { Fabricate(:moderator, email: old_email) }
|
|
let(:updater) { EmailUpdater.new(guardian: user.guardian, user: user) }
|
|
|
|
before do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_old_email, to_address: old_email }) do
|
|
updater.change_to(new_email)
|
|
end
|
|
|
|
@change_req = user.email_change_requests.first
|
|
end
|
|
|
|
it "starts the old confirmation process" do
|
|
expect(updater.errors).to be_blank
|
|
|
|
expect(@change_req.old_email).to eq(old_email)
|
|
expect(@change_req.new_email).to eq(new_email)
|
|
expect(@change_req).to be_present
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:authorizing_old])
|
|
|
|
expect(@change_req.old_email_token.email).to eq(old_email)
|
|
expect(@change_req.new_email_token).to be_blank
|
|
end
|
|
|
|
context 'confirming an invalid token' do
|
|
it "produces an error" do
|
|
updater.confirm('random')
|
|
expect(updater.errors).to be_present
|
|
expect(user.reload.email).not_to eq(new_email)
|
|
end
|
|
end
|
|
|
|
context 'confirming a valid token' do
|
|
before do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :confirm_new_email, to_address: new_email }) do
|
|
updater.confirm(@change_req.old_email_token.token)
|
|
end
|
|
|
|
@change_req.reload
|
|
end
|
|
|
|
it "starts the new update process" do
|
|
expect(updater.errors).to be_blank
|
|
expect(user.reload.email).to eq(old_email)
|
|
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:authorizing_new])
|
|
expect(@change_req.new_email_token).to be_present
|
|
end
|
|
|
|
it "cannot be confirmed twice" do
|
|
updater.confirm(@change_req.old_email_token.token)
|
|
expect(updater.errors).to be_present
|
|
expect(user.reload.email).to eq(old_email)
|
|
|
|
@change_req.reload
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:authorizing_new])
|
|
expect(@change_req.new_email_token.email).to eq(new_email)
|
|
end
|
|
|
|
context "completing the new update process" do
|
|
before do
|
|
expect_not_enqueued_with(job: :critical_user_email, args: { type: :notify_old_email, to_address: old_email }) do
|
|
updater.confirm(@change_req.new_email_token.token)
|
|
end
|
|
end
|
|
|
|
it "updates the user's email" do
|
|
expect(updater.errors).to be_blank
|
|
expect(user.reload.email).to eq(new_email)
|
|
|
|
@change_req.reload
|
|
expect(@change_req.change_state).to eq(EmailChangeRequest.states[:complete])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'hide_email_address_taken is enabled' do
|
|
before do
|
|
SiteSetting.hide_email_address_taken = true
|
|
end
|
|
|
|
let(:user) { Fabricate(:user, email: old_email) }
|
|
let(:existing) { Fabricate(:user, email: new_email) }
|
|
let(:updater) { EmailUpdater.new(guardian: user.guardian, user: user) }
|
|
|
|
it "doesn't error if user exists with new email" do
|
|
updater.change_to(existing.email)
|
|
expect(updater.errors).to be_blank
|
|
expect(user.email_change_requests).to be_empty
|
|
end
|
|
|
|
it 'sends an email to the owner of the account with the new email' do
|
|
expect_enqueued_with(job: :critical_user_email, args: { type: :account_exists, user_id: existing.id }) do
|
|
updater.change_to(existing.email)
|
|
end
|
|
end
|
|
end
|
|
end
|