SECURITY: Support for confirm old as well as new email accounts

This commit is contained in:
Robin Ward 2016-03-07 14:40:11 -05:00
parent d62689fa76
commit 5771d2aee2
47 changed files with 454 additions and 114 deletions

View File

@ -20,7 +20,8 @@ class Admin::EmailTemplatesController < Admin::AdminController
"system_messages.unblocked", "system_messages.user_automatically_blocked",
"system_messages.welcome_invite", "system_messages.welcome_user", "test_mailer",
"user_notifications.account_created", "user_notifications.admin_login",
"user_notifications.authorize_email", "user_notifications.forgot_password",
"user_notifications.confirm_new_email", "user_notifications.confirm_old_email",
"user_notifications.notify_old_email", "user_notifications.forgot_password",
"user_notifications.set_password", "user_notifications.signup",
"user_notifications.signup_after_approval",
"user_notifications.user_invited_to_private_message_pm",

View File

@ -5,7 +5,7 @@ require_dependency 'rate_limiter'
class UsersController < ApplicationController
skip_before_filter :authorize_mini_profiler, only: [:avatar]
skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :authorize_email, :user_preferences_redirect, :avatar, :my_redirect, :toggle_anon, :admin_login]
skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :user_preferences_redirect, :avatar, :my_redirect, :toggle_anon, :admin_login]
before_filter :ensure_logged_in, only: [:username, :update, :user_preferences_redirect, :upload_user_image, :pick_avatar, :destroy_user_image, :destroy, :check_emails]
before_filter :respond_to_suspicious_request, only: [:create]
@ -21,7 +21,6 @@ class UsersController < ApplicationController
:activate_account,
:perform_account_activation,
:send_activation_email,
:authorize_email,
:password_reset,
:confirm_email_token,
:admin_login]
@ -471,16 +470,6 @@ class UsersController < ApplicationController
end
end
def authorize_email
expires_now()
if @user = EmailToken.confirm(params[:token])
log_on_user(@user)
else
flash[:error] = I18n.t('change_email.error')
end
render layout: 'no_ember'
end
def account_created
@message = session['user_created_message'] || I18n.t('activation.missing_session')
expires_now

View File

@ -1,9 +1,13 @@
require_dependency 'rate_limiter'
require_dependency 'email_validator'
require_dependency 'email_updater'
class UsersEmailController < ApplicationController
before_filter :ensure_logged_in
before_filter :ensure_logged_in, only: [:index, :update]
skip_before_filter :check_xhr, only: [:confirm]
skip_before_filter :redirect_to_login_if_required, only: [:confirm]
def index
end
@ -11,31 +15,32 @@ class UsersEmailController < ApplicationController
def update
params.require(:email)
user = fetch_user_from_params
guardian.ensure_can_edit_email!(user)
lower_email = Email.downcase(params[:email]).strip
RateLimiter.new(user, "change-email-hr-#{request.remote_ip}", 6, 1.hour).performed!
RateLimiter.new(user, "change-email-min-#{request.remote_ip}", 3, 1.minute).performed!
EmailValidator.new(attributes: :email).validate_each(user, :email, lower_email)
return render_json_error(user.errors.full_messages) if user.errors[:email].present?
updater = EmailUpdater.new(guardian, user)
updater.change_to(params[:email])
# Raise an error if the email is already in use
return render_json_error(I18n.t('change_email.error')) if User.find_by_email(lower_email)
email_token = user.email_tokens.create(email: lower_email)
Jobs.enqueue(
:user_email,
to_address: lower_email,
type: :authorize_email,
user_id: user.id,
email_token: email_token.token
)
if updater.errors.present?
return render_json_error(updater.errors.full_messages)
end
render nothing: true
rescue RateLimiter::LimitExceeded
render_json_error(I18n.t("rate_limiter.slow_down"))
end
def confirm
expires_now
updater = EmailUpdater.new
@update_result = updater.confirm(params[:token])
# Log in the user if the process is complete (and they're not logged in)
log_on_user(updater.user) if @update_result == :complete
render layout: 'no_ember'
end
end

View File

@ -109,6 +109,10 @@ module Jobs
email_args[:email_token] = email_token
end
if type == 'notify_old_email'
email_args[:new_email] = user.email
end
message = UserNotifications.send(type, user, email_args)
# Update the to address if we have a custom one

View File

@ -23,9 +23,23 @@ class UserNotifications < ActionMailer::Base
new_user_tips: I18n.t('system_messages.usage_tips.text_body_template', base_url: Discourse.base_url, locale: locale))
end
def authorize_email(user, opts={})
def notify_old_email(user, opts={})
build_email(user.email,
template: "user_notifications.authorize_email",
template: "user_notifications.notify_old_email",
locale: user_locale(user),
new_email: opts[:new_email])
end
def confirm_old_email(user, opts={})
build_email(user.email,
template: "user_notifications.confirm_old_email",
locale: user_locale(user),
email_token: opts[:email_token])
end
def confirm_new_email(user, opts={})
build_email(user.email,
template: "user_notifications.confirm_new_email",
locale: user_locale(user),
email_token: opts[:email_token])
end

View File

@ -0,0 +1,9 @@
class EmailChangeRequest < ActiveRecord::Base
belongs_to :old_email_token, class_name: 'EmailToken'
belongs_to :new_email_token, class_name: 'EmailToken'
def self.states
@states ||= Enum.new(authorizing_old: 1, authorizing_new: 2, complete: 3)
end
end

View File

@ -41,28 +41,41 @@ class EmailToken < ActiveRecord::Base
return token.present? && token =~ /[a-f0-9]{#{token.length/2}}/i
end
def self.confirm(token)
return unless valid_token_format?(token)
def self.atomic_confirm(token)
failure = { success: false }
return failure unless valid_token_format?(token)
email_token = confirmable(token)
return if email_token.blank?
return failure if email_token.blank?
user = email_token.user
failure[:user] = user
row_count = EmailToken.where(id: email_token.id, expired: false).update_all 'confirmed = true'
if row_count == 1
return { success: true, user: user, email_token: email_token }
end
return failure
end
def self.confirm(token)
User.transaction do
row_count = EmailToken.where(id: email_token.id, expired: false).update_all 'confirmed = true'
if row_count == 1
result = atomic_confirm(token)
user = result[:user]
if result[:success]
# If we are activating the user, send the welcome message
user.send_welcome_message = !user.active?
user.active = true
user.email = email_token.email
user.email = result[:email_token].email
user.save!
end
end
# redeem invite, if available
return User.find_by(email: Email.downcase(user.email)) if Invite.redeem_from_email(user.email).present?
user
if user
return User.find_by(email: Email.downcase(user.email)) if Invite.redeem_from_email(user.email).present?
user
end
end
rescue ActiveRecord::RecordInvalid
# If the user's email is already taken, just return nil (failure)
end

View File

@ -36,6 +36,7 @@ class User < ActiveRecord::Base
has_many :uploads
has_many :warnings
has_many :user_archived_messages, dependent: :destroy
has_many :email_change_requests, dependent: :destroy
has_one :user_option, dependent: :destroy

View File

@ -1,12 +0,0 @@
<div id="simple-container">
<%if flash[:error]%>
<div class='alert alert-error'>
<%=flash[:error]%>
</div>
<%else%>
<h2><%= t 'change_email.confirmed' %></h2>
<br>
<a class="btn" href="/"><%= t('change_email.please_continue', site_name: SiteSetting.title) %></a>
<%= render partial: 'auto_redirect_home' %>
<%end%>
</div>

View File

@ -0,0 +1,15 @@
<div id="simple-container">
<% if @update_result == :authorizing_new %>
<h2><%= t 'change_email.authorizing_old.title' %></h2>
<br>
<p><%= t 'change_email.authorizing_old.description' %></p>
<% elsif @update_result == :complete %>
<h2><%= t 'change_email.confirmed' %></h2>
<br>
<a class="btn" href="/"><%= t('change_email.please_continue', site_name: SiteSetting.title) %></a>
<% else %>
<div class='alert alert-error'>
<%=t 'change_email.error' %>
</div>
<% end %>
</div>

View File

@ -1874,7 +1874,7 @@ ar:
انقر على الرابط التالي لاختيار كلمة مرور لحسابك الجديد:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] تأكيد البريد الإلكتروني الجديد "
text_body_template: |
قم بتاكيد عنوان بريدك الالكتروني لـ %{site_name} عن طريق الضغط علي الرابط التالي:

View File

@ -1029,7 +1029,7 @@ bs_BA:
Kliknite na link ispod da kreirate šifru za vaš nalog:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Potvrdite vaš novi email"
text_body_template: |
Confirm your new email address for %{site_name} by clicking on the following link:

View File

@ -805,7 +805,7 @@ cs:
%{base_url}/users/password-reset/%{email_token}
admin_login:
subject_template: "[%{site_name}] Přihlášení"
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Potvrďte vaši novou emailovou adresu"
text_body_template: |
Potvrďte vaši novou emailovou adresu pro %{site_name} kliknutím na následující odkaz:

View File

@ -780,7 +780,7 @@ da:
%{base_url}/users/password-reset/%{email_token}
account_created:
subject_template: "[%{site_name}] Din nye konto"
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Bekræft din nye e-mail-adresse"
text_body_template: |
Bekræft din nye e-mail-adresse på %{site_name} ved at klikke på følgende link:

View File

@ -1573,7 +1573,7 @@ de:
Klicke auf den folgenden Link, um ein Passwort für dein neues Konto festzulegen:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Bestätige deine neue Mailadresse"
text_body_template: |
Um deine Mailadresse auf %{site_name} zu bestätigen, klicke auf den folgenden Link:

View File

@ -517,6 +517,10 @@ en:
confirmed: "Your email has been updated."
please_continue: "Continue to %{site_name}"
error: "There was an error changing your email address. Perhaps the address is already in use?"
already_done: "Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?"
authorizing_old:
title: "Thanks for confirming your current email address"
description: "We're now emailing your new address for confirmation."
activation:
action: "Click here to activate your account"
@ -2245,13 +2249,35 @@ en:
Click the following link to choose a password for your new account:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirm your new email address"
text_body_template: |
Confirm your new email address for %{site_name} by clicking on the following link:
%{base_url}/users/authorize-email/%{email_token}
confirm_old_email:
subject_template: "[%{site_name}] Confirm your current email address"
text_body_template: |
Before we can change your email address, we need you to confirm that you control
the current email account. After you complete this step, we will have you confirm
the new email address.
Confirm your current email address for %{site_name} by clicking on the following link:
%{base_url}/users/authorize-email/%{email_token}
notify_old_email:
subject_template: "[%{site_name}] Your email address has been changed"
text_body_template: |
This is an automated message to let you know that your email address for
%{site_name} has been changed. If this was done in error, please contact
a site administrator.
Your email address has been changed to:
%{new_email}
signup_after_approval:
subject_template: "You've been approved on %{site_name}!"
text_body_template: |

View File

@ -1702,7 +1702,7 @@ es:
Pulsa en el siguiente enlace para escoger una contraseña para tu nueva cuenta:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirma tu nueva dirección de email"
text_body_template: |
Confirma tu nueva dirección de email para %{site_name} haciendo clic en el siguiente enlace:

View File

@ -1297,7 +1297,7 @@ fa_IR:
account_created:
subject_template: "[%{site_name}] حساب کاربری جدید شما"
text_body_template: "حساب کاربری جدید برای شما ساخته شد در %{site_name}\n\nبر روری پیوند پیش رو کلیک کنید برای انتخاب رمز برای حساب کاربری جدیدتان: \n\n%{base_url}/users/password-reset/%{email_token}\n"
authorize_email:
confirm_new_email:
subject_template: "آدرس ایمیل جدید را تایید کنید برای [%{site_name}]"
text_body_template: "آدرس ایمیل جدید را تایید کنید برای %{site_name} با کلیک کردن بر پیوند پیش رو : \n\n\n%{base_url}/users/authorize-email/%{email_token}\n"
signup_after_approval:

View File

@ -1829,7 +1829,7 @@ fi:
Klikkaa seuraavaa linkkiä asettaaksesi salasanan uudelle tunnuksellesi:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Vahvista uusi sähköpostiosoite"
text_body_template: |
Vahvista uusi sähköpostiosoite sivustolle %{site_name} klikkaamalla alla olevaa linkkiä:

View File

@ -1793,7 +1793,7 @@ fr:
Cliquez sur le lien ci-dessous pour choisir un mot de passe pour votre nouveau compte :
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirmation de votre nouvelle adresse de courriel"
text_body_template: |
Confirmez votre nouvelle adresse de courriel pour %{site_name} en cliquant sur le lien suivant :

View File

@ -1472,7 +1472,7 @@ he:
הקישו על הקישור המצורף כדי להגדיר סיסמא לחשבונך החדש:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirm your new email address"
text_body_template: |
Confirm your new email address for %{site_name} by clicking on the following link:

View File

@ -1207,7 +1207,7 @@ it:
Fai clic sul seguente collegamento per scegliere una password per il tuo nuovo account:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Conferma il tuo nuovo indirizzo email"
text_body_template: |
Conferma il tuo nuovo indirizzo email per %{site_name} cliccando sul seguente collegamento:

View File

@ -1319,7 +1319,7 @@ ja:
以下のリンクをクリックしてパスワードを設定してください:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] メールアドレスの確認"
text_body_template: |
次のリンクをクリックして %{site_name} 用のメールアドレスを確認してください:

View File

@ -1424,7 +1424,7 @@ ko:
비밀번호 설정 페이지:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] 이메일 확인"
text_body_template: |
%{site_name} 사이트에서 사용할 새로운 이메일을 아래 링크를 클릭하여 확인하세요:

View File

@ -1381,7 +1381,7 @@ nl:
Klik op deze link om een wachtwoord in te stellen voor je nieuwe account:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Bevestig je nieuwe e-mailadres"
text_body_template: |
Bevestig je nieuwe e-mailadres voor %{site_name} door op de volgende link te klikken:

View File

@ -1143,7 +1143,7 @@ pl_PL:
Kliknij na linku poniżej, aby ustawić swoje hasło:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Potwierdź nowy adres email"
text_body_template: |
Potwierdź Twój nowy adres email na forum %{site_name} przez kliknięcie na poniższy link:

View File

@ -1812,7 +1812,7 @@ pt:
Clique na hiperligação seguinte para escolher uma palavra-passe para a sua nova conta.
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirme o seu novo endereço de email"
text_body_template: |
Confirme o seu novo endereço de email para %{site_name} ao clicar na seguinte hiperligação:

View File

@ -1417,7 +1417,7 @@ pt_BR:
Clique no link a seguir para escolher uma senha para a sua nova conta:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirma o seu novo endereço de email"
text_body_template: |
Confirma o seu endereço de email novo para %{site_name} clicando no seguinte link:

View File

@ -950,7 +950,7 @@ ro:
%{base_url}/users/password-reset/%{email_token}
admin_login:
subject_template: "[%{site_name}] Autentificare"
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirmă noua adresă de email"
text_body_template: |
Confirmă noua adresă de email pentru %{site_name} făcând click pe următoarea adresă:

View File

@ -1522,7 +1522,7 @@ ru:
Чтобы установить пароль, пройдите по следующей ссылке:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Подтвердите новый адрес электронной почты"
text_body_template: |
Подтвердите ваш новый адрес электронной почты для сайта %{site_name}, перейдя по следующей ссылке:

View File

@ -1790,7 +1790,7 @@ sk:
Kliknite na nasledujúci odkaz pre nastavenie hesla k Vášmu novému účtu:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Potvrďte Vašu novú email adresu"
text_body_template: |
Potvrďte Vašu novú emailovú adresu pre %{site_name} kliknutím na nasledujúci odkaz:

View File

@ -1493,7 +1493,7 @@ sq:
Click the following link to choose a password for your new account:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirm your new email address"
text_body_template: |
Confirm your new email address for %{site_name} by clicking on the following link:

View File

@ -983,7 +983,7 @@ sv:
subject_template: "[%{site_name}] Logga in"
account_created:
subject_template: "[%{site_name}] Ditt nya konto"
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Confirm your new email address"
text_body_template: |
Confirm your new email address for %{site_name} by clicking on the following link:

View File

@ -1421,7 +1421,7 @@ tr_TR:
Yeni hesabınıza ait bir parola oluşturmak için aşağıdaki bağlantıya tıklayın:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Yeni e-posta adresinizi onaylayın"
text_body_template: |
Aşağıdaki bağlantıya tıklayarak %{site_name} sitesindeki yeni e-posta adresinizi onaylayın:

View File

@ -457,7 +457,7 @@ uk:
Перейдіть за посиланням, щоб обрати пароль:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Підтвердіть свою електронну скриньку"
text_body_template: |
Підтвердіть свою нову електронну скриньку для сайта %{site_name}, перейшовши за посиланням:

View File

@ -1111,7 +1111,7 @@ vi:
subject_template: "[%{site_name}] Đăng nhập"
account_created:
subject_template: "[%{site_name}] Tài khoản mới"
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] Xác nhận địa chỉ email mới của bạn"
signup_after_approval:
subject_template: "Bạn đã được kiểm duyệt ở %{site_name}!"

View File

@ -1850,7 +1850,7 @@ zh_CN:
点击下面的链接来为新账户设置密码:
%{base_url}/users/password-reset/%{email_token}
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] 确认你的新电子邮箱地址"
text_body_template: |
点击下面的链接来确认你在 %{site_name} 上的新电子邮箱地址:

View File

@ -943,7 +943,7 @@ zh_TW:
subject_template: "[%{site_name}] 設定密碼"
account_created:
subject_template: "[%{site_name}] 你的新帳號"
authorize_email:
confirm_new_email:
subject_template: "[%{site_name}] 確認你的新電子郵箱位址"
text_body_template: |
點擊下面的連結來確認你在 %{site_name} 上的新電子郵箱位址:

View File

@ -274,7 +274,7 @@ Discourse::Application.routes.draw do
put "users/password-reset/:token" => "users#password_reset"
get "users/activate-account/:token" => "users#activate_account"
put "users/activate-account/:token" => "users#perform_account_activation", as: 'perform_activate_account'
get "users/authorize-email/:token" => "users#authorize_email"
get "users/authorize-email/:token" => "users_email#confirm"
get "users/hp" => "users#get_honeypot_value"
get "my/*path", to: 'users#my_redirect'

View File

@ -0,0 +1,15 @@
class CreateEmailChangeRequests < ActiveRecord::Migration
def change
create_table :email_change_requests do |t|
t.integer :user_id, null: false
t.string :old_email, length: 513, null: false
t.string :new_email, length: 513, null: false
t.integer :old_email_token_id, null: true
t.integer :new_email_token_id, null: true
t.integer :change_state, null: false
t.timestamps null: false
end
add_index :email_change_requests, :user_id
end
end

View File

@ -0,0 +1,8 @@
class RenameConfirmTranslationKey < ActiveRecord::Migration
def change
execute "UPDATE translation_overrides SET translation_key = 'user_notifications.confirm_new_email.subject_template'
WHERE translation_key = 'user_notifications.authorize_email.subject_template'"
execute "UPDATE translation_overrides SET translation_key = 'user_notifications.confirm_new_email.text_body_template'
WHERE translation_key = 'user_notifications.authorize_email.text_body_template'"
end
end

113
lib/email_updater.rb Normal file
View File

@ -0,0 +1,113 @@
require_dependency 'email'
require_dependency 'has_errors'
require_dependency 'email_validator'
class EmailUpdater
include HasErrors
attr_reader :user
def initialize(guardian=nil, user=nil)
@guardian = guardian
@user = user
end
def self.human_attribute_name(name, options={})
User.human_attribute_name(name, options)
end
def authorize_both?
@user.staff?
end
def change_to(email_input)
@guardian.ensure_can_edit_email!(@user)
email = Email.downcase(email_input.strip)
EmailValidator.new(attributes: :email).validate_each(self, :email, email)
errors.add(:base, I18n.t('change_email.error')) if User.find_by_email(email)
if errors.blank?
args = {
old_email: @user.email,
new_email: email,
}
if authorize_both?
args[:change_state] = EmailChangeRequest.states[:authorizing_old]
email_token = @user.email_tokens.create(email: args[:old_email])
args[:old_email_token] = email_token
else
args[:change_state] = EmailChangeRequest.states[:authorizing_new]
email_token = @user.email_tokens.create(email: args[:new_email])
args[:new_email_token] = email_token
end
@user.email_change_requests.create(args)
if args[:change_state] == EmailChangeRequest.states[:authorizing_new]
send_email(:confirm_new_email, email_token)
elsif args[:change_state] == EmailChangeRequest.states[:authorizing_old]
send_email(:confirm_old_email, email_token)
end
end
end
def confirm(token)
confirm_result = nil
change_req = 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
# Simple state machine
case change_req.try(:change_state)
when EmailChangeRequest.states[:authorizing_old]
new_token = user.email_tokens.create(email: change_req.new_email)
change_req.update_columns(change_state: EmailChangeRequest.states[:authorizing_new],
new_email_token_id: new_token.id)
send_email(:confirm_new_email, new_token)
confirm_result = :authorizing_new
when EmailChangeRequest.states[:authorizing_new]
change_req.update_column(:change_state, EmailChangeRequest.states[:complete])
user.update_column(:email, token.email)
confirm_result = :complete
end
else
errors.add(:base, I18n.t('change_email.already_done'))
confirm_result = :error
end
end
if confirm_result == :complete && change_req.old_email_token_id.blank?
notify_old(change_req.old_email, token.email)
end
confirm_result || :error
end
protected
def notify_old(old_email, new_email)
Jobs.enqueue :user_email,
to_address: old_email,
type: :notify_old_email,
user_id: @user.id
end
def send_email(type, email_token)
Jobs.enqueue :user_email,
to_address: email_token.email,
type: type,
user_id: @user.id,
email_token: email_token.token
end
end

View File

@ -0,0 +1,124 @@
require 'rails_helper'
require_dependency 'email_updater'
describe EmailUpdater do
let(:old_email) { 'old.email@example.com' }
let(:new_email) { 'new.email@example.com' }
context 'as a regular user' do
let(:user) { Fabricate(:user, email: old_email) }
let(:updater) { EmailUpdater.new(user.guardian, user) }
before do
Jobs.expects(:enqueue).once.with(:user_email, has_entries(type: :confirm_new_email, to_address: new_email))
updater.change_to(new_email)
@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
Jobs.expects(:enqueue).once.with(:user_email, has_entries(type: :notify_old_email, to_address: old_email))
updater.confirm(@change_req.new_email_token.token)
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
context 'as a staff user' do
let(:user) { Fabricate(:moderator, email: old_email) }
let(:updater) { EmailUpdater.new(user.guardian, user) }
before do
Jobs.expects(:enqueue).once.with(:user_email, has_entries(type: :confirm_old_email, to_address: old_email))
updater.change_to(new_email)
@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
Jobs.expects(:enqueue).once.with(:user_email, has_entries(type: :confirm_new_email, to_address: new_email))
updater.confirm(@change_req.old_email_token.token)
@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
Jobs.expects(:enqueue).with(:user_email, has_entries(type: :notify_old_email, to_address: old_email)).never
updater.confirm(@change_req.new_email_token.token)
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
end

View File

@ -104,26 +104,6 @@ describe UsersController do
end
end
describe '.authorize_email' do
it 'errors out for invalid tokens' do
get :authorize_email, token: 'asdfasdf'
expect(response).to be_success
expect(flash[:error]).to be_present
end
context 'valid token' do
it 'authorizes with a correct token' do
user = Fabricate(:user)
email_token = user.email_tokens.create(email: user.email)
get :authorize_email, token: email_token.token
expect(response).to be_success
expect(flash[:error]).to be_blank
expect(session[:current_user_id]).to be_present
end
end
end
describe '.activate_account' do
before do
UsersController.any_instance.stubs(:honeypot_or_challenge_fails?).returns(false)

View File

@ -2,6 +2,44 @@ require 'rails_helper'
describe UsersEmailController do
describe '.confirm' do
it 'errors out for invalid tokens' do
get :confirm, token: 'asdfasdf'
expect(response).to be_success
expect(assigns(:update_result)).to eq(:error)
end
context 'valid old address token' do
let(:user) { Fabricate(:moderator) }
let(:updater) { EmailUpdater.new(user.guardian, user) }
before do
updater.change_to('new.n.cool@example.com')
end
it 'confirms with a correct token' do
get :confirm, token: user.email_tokens.last.token
expect(response).to be_success
expect(assigns(:update_result)).to eq(:authorizing_new)
end
end
context 'valid new address token' do
let(:user) { Fabricate(:user) }
let(:updater) { EmailUpdater.new(user.guardian, user) }
before do
updater.change_to('new.n.cool@example.com')
end
it 'confirms with a correct token' do
get :confirm, token: user.email_tokens.last.token
expect(response).to be_success
expect(assigns(:update_result)).to eq(:complete)
end
end
end
describe '.update' do
let(:new_email) { 'bubblegum@adventuretime.ooo' }
@ -57,14 +95,8 @@ describe UsersEmailController do
end
context 'success' do
it 'has an email token' do
expect { xhr :put, :update, username: user.username, email: new_email }.to change(EmailToken, :count)
end
it 'enqueues an email authorization' do
Jobs.expects(:enqueue).with(:user_email, has_entries(type: :authorize_email, user_id: user.id, to_address: new_email))
xhr :put, :update, username: user.username, email: new_email
expect { xhr :put, :update, username: user.username, email: new_email }.to change(EmailChangeRequest, :count)
end
end
end

View File

@ -38,9 +38,9 @@ describe Jobs::UserEmail do
context 'to_address' do
it 'overwrites a to_address when present' do
UserNotifications.expects(:authorize_email).returns(mailer)
UserNotifications.expects(:confirm_new_email).returns(mailer)
Email::Sender.any_instance.expects(:send)
Jobs::UserEmail.new.execute(type: :authorize_email, user_id: user.id, to_address: 'jake@adventuretime.ooo')
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, to_address: 'jake@adventuretime.ooo')
expect(mailer.to).to eq(['jake@adventuretime.ooo'])
end
end

View File

@ -405,7 +405,8 @@ describe UserNotifications do
context "user locale has been set" do
%w(signup signup_after_approval authorize_email forgot_password admin_login account_created).each do |mail_type|
%w(signup signup_after_approval confirm_old_email notify_old_email confirm_new_email
forgot_password admin_login account_created).each do |mail_type|
include_examples "notification derived from template" do
SiteSetting.default_locale = "en"
let(:locale) { "fr" }
@ -418,7 +419,8 @@ describe UserNotifications do
end
context "user locale has not been set" do
%w(signup signup_after_approval authorize_email forgot_password admin_login account_created).each do |mail_type|
%w(signup signup_after_approval notify_old_email confirm_old_email confirm_new_email
forgot_password admin_login account_created).each do |mail_type|
include_examples "notification derived from template" do
SiteSetting.default_locale = "en"
let(:locale) { nil }
@ -431,7 +433,8 @@ describe UserNotifications do
end
context "user locale is an empty string" do
%w(signup signup_after_approval authorize_email forgot_password admin_login account_created).each do |mail_type|
%w(signup signup_after_approval notify_old_email confirm_new_email confirm_old_email
forgot_password admin_login account_created).each do |mail_type|
include_examples "notification derived from template" do
SiteSetting.default_locale = "en"
let(:locale) { "" }