discourse/lib/email_updater.rb
Bianca Nenciu 9bd436c20b
FIX: Do not add same email multiple times (#12322)
The user and an admin could create multiple email change requests for
the same user. If any of the requests was validated and it became
primary, the other request could not be deleted anymore.
2021-03-10 14:49:26 +02:00

142 lines
4.5 KiB
Ruby

# frozen_string_literal: true
class EmailUpdater
include HasErrors
attr_reader :user
def self.human_attribute_name(name, options = {})
User.human_attribute_name(name, options)
end
def initialize(guardian: nil, user: nil)
@guardian = guardian
@user = user
end
def change_to(email, add: false)
@guardian.ensure_can_edit_email!(@user)
email = Email.downcase(email.strip)
EmailValidator.new(attributes: :email).validate_each(self, :email, email)
return if errors.present?
if existing_user = User.find_by_email(email)
if SiteSetting.hide_email_address_taken
Jobs.enqueue(:critical_user_email, type: :account_exists, user_id: existing_user.id)
else
error_message = +'change_email.error'
error_message << '_staged' if existing_user.staged?
errors.add(:base, I18n.t(error_message))
end
end
return if errors.present? || existing_user.present?
old_email = @user.email if !add
if @guardian.is_staff? && @guardian.user != @user
StaffActionLogger.new(@guardian.user).log_add_email(@user)
else
UserHistory.create!(action: UserHistory.actions[:add_email], acting_user_id: @user.id)
end
change_req = EmailChangeRequest.find_or_initialize_by(user_id: @user.id, new_email: email)
if change_req.new_record?
change_req.requested_by = @guardian.user
change_req.old_email = old_email
change_req.new_email = email
end
if change_req.change_state.blank? || change_req.change_state == EmailChangeRequest.states[:complete]
change_req.change_state = if @user.staff?
# Staff users must confirm their old email address first.
EmailChangeRequest.states[:authorizing_old]
else
EmailChangeRequest.states[:authorizing_new]
end
end
if change_req.change_state == EmailChangeRequest.states[:authorizing_old]
change_req.old_email_token = @user.email_tokens.create!(email: @user.email)
send_email(add ? :confirm_old_email_add : :confirm_old_email, change_req.old_email_token)
elsif change_req.change_state == EmailChangeRequest.states[:authorizing_new]
change_req.new_email_token = @user.email_tokens.create!(email: email)
send_email(:confirm_new_email, change_req.new_email_token)
end
change_req.save!
end
def confirm(token)
confirm_result = nil
User.transaction do
result = EmailToken.atomic_confirm(token)
if result[:success]
token = result[:email_token]
@user = token.user
change_req = @user.email_change_requests
.where('old_email_token_id = :token_id OR new_email_token_id = :token_id', token_id: token.id)
.first
case change_req.try(:change_state)
when EmailChangeRequest.states[:authorizing_old]
change_req.update!(
change_state: EmailChangeRequest.states[:authorizing_new],
new_email_token: @user.email_tokens.create(email: change_req.new_email)
)
send_email(:confirm_new_email, change_req.new_email_token)
confirm_result = :authorizing_new
when EmailChangeRequest.states[:authorizing_new]
change_req.update!(change_state: EmailChangeRequest.states[:complete])
if !@user.staff?
# Send an email notification only to users who did not confirm old
# email.
send_email_notification(change_req.old_email, change_req.new_email)
end
update_user_email(change_req.old_email, change_req.new_email)
confirm_result = :complete
end
else
errors.add(:base, I18n.t('change_email.already_done'))
confirm_result = :error
end
end
confirm_result || :error
end
def update_user_email(old_email, new_email)
if old_email.present?
@user.user_emails.find_by(email: old_email).update!(email: new_email)
else
@user.user_emails.create!(email: new_email)
end
@user.reload
DiscourseEvent.trigger(:user_updated, @user)
@user.set_automatic_groups
end
protected
def send_email(type, email_token)
Jobs.enqueue :critical_user_email,
to_address: email_token.email,
type: type,
user_id: @user.id,
email_token: email_token.token
end
def send_email_notification(old_email, new_email)
Jobs.enqueue :critical_user_email,
to_address: @user.email,
type: old_email ? :notify_old_email : :notify_old_email_add,
user_id: @user.id,
new_email: new_email
end
end