diff --git a/app/controllers/admin/email_templates_controller.rb b/app/controllers/admin/email_templates_controller.rb
index ccbb3eaf692..b32f0b91940 100644
--- a/app/controllers/admin/email_templates_controller.rb
+++ b/app/controllers/admin/email_templates_controller.rb
@@ -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",
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 08564f52fb6..586e7fded0b 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -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
diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb
index c7e501f52a7..da4ce692340 100644
--- a/app/controllers/users_email_controller.rb
+++ b/app/controllers/users_email_controller.rb
@@ -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
diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb
index 9ef71e08670..ca7b73dc710 100644
--- a/app/jobs/regular/user_email.rb
+++ b/app/jobs/regular/user_email.rb
@@ -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
diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb
index d63dd23b92b..943b2a127a5 100644
--- a/app/mailers/user_notifications.rb
+++ b/app/mailers/user_notifications.rb
@@ -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
diff --git a/app/models/email_change_request.rb b/app/models/email_change_request.rb
new file mode 100644
index 00000000000..64d1fa104f8
--- /dev/null
+++ b/app/models/email_change_request.rb
@@ -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
diff --git a/app/models/email_token.rb b/app/models/email_token.rb
index 146c3d7c5e3..c919b619033 100644
--- a/app/models/email_token.rb
+++ b/app/models/email_token.rb
@@ -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
diff --git a/app/models/user.rb b/app/models/user.rb
index 667cfb6b55f..fb865b213c4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/views/users/authorize_email.html.erb b/app/views/users/authorize_email.html.erb
deleted file mode 100644
index 8eef69bc663..00000000000
--- a/app/views/users/authorize_email.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-
\ No newline at end of file
diff --git a/app/views/users_email/confirm.html.erb b/app/views/users_email/confirm.html.erb
new file mode 100644
index 00000000000..f02bb9fbe9d
--- /dev/null
+++ b/app/views/users_email/confirm.html.erb
@@ -0,0 +1,15 @@
+
+ <% if @update_result == :authorizing_new %>
+
<%= t 'change_email.authorizing_old.title' %>
+
+
<%= t 'change_email.authorizing_old.description' %>
+ <% elsif @update_result == :complete %>
+
<%= t 'change_email.confirmed' %>
+
+
<%= t('change_email.please_continue', site_name: SiteSetting.title) %>
+ <% else %>
+
+ <%=t 'change_email.error' %>
+
+ <% end %>
+
diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml
index 8a9a652988b..fb8cdfee185 100644
--- a/config/locales/server.ar.yml
+++ b/config/locales/server.ar.yml
@@ -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} عن طريق الضغط علي الرابط التالي:
diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml
index bcce781f8c6..4f4d692b35d 100644
--- a/config/locales/server.bs_BA.yml
+++ b/config/locales/server.bs_BA.yml
@@ -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:
diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml
index 2269a88b9ae..44cacdfe134 100644
--- a/config/locales/server.cs.yml
+++ b/config/locales/server.cs.yml
@@ -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:
diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml
index dd05eed3cb4..ff84e1fa6c7 100644
--- a/config/locales/server.da.yml
+++ b/config/locales/server.da.yml
@@ -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:
diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml
index a65ccd10976..100250b1f35 100644
--- a/config/locales/server.de.yml
+++ b/config/locales/server.de.yml
@@ -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:
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index efbacec9ab0..e76ba73b4a8 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -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: |
diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml
index 4a666bfea9e..83e4627ac03 100644
--- a/config/locales/server.es.yml
+++ b/config/locales/server.es.yml
@@ -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:
diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml
index d96cbb7f1ef..9de4a7cc6b3 100644
--- a/config/locales/server.fa_IR.yml
+++ b/config/locales/server.fa_IR.yml
@@ -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:
diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml
index efcf5c357da..ae2c4febebe 100644
--- a/config/locales/server.fi.yml
+++ b/config/locales/server.fi.yml
@@ -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ä:
diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml
index 8adea1b9834..b6d224561f8 100644
--- a/config/locales/server.fr.yml
+++ b/config/locales/server.fr.yml
@@ -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 :
diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml
index e79e6d50eb2..6b645d3f54f 100644
--- a/config/locales/server.he.yml
+++ b/config/locales/server.he.yml
@@ -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:
diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml
index 969ba7c39d3..e564607c9ba 100644
--- a/config/locales/server.it.yml
+++ b/config/locales/server.it.yml
@@ -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:
diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml
index 98e5f8cdd5e..c9fe9b9d3ce 100644
--- a/config/locales/server.ja.yml
+++ b/config/locales/server.ja.yml
@@ -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} 用のメールアドレスを確認してください:
diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml
index dc8cfc2971b..055850ef95f 100644
--- a/config/locales/server.ko.yml
+++ b/config/locales/server.ko.yml
@@ -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} 사이트에서 사용할 새로운 이메일을 아래 링크를 클릭하여 확인하세요:
diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml
index 99daac07329..513fa6abf40 100644
--- a/config/locales/server.nl.yml
+++ b/config/locales/server.nl.yml
@@ -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:
diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml
index fe7f69a1817..e837906227e 100644
--- a/config/locales/server.pl_PL.yml
+++ b/config/locales/server.pl_PL.yml
@@ -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:
diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml
index bcd2b1a06d5..9df7a566e92 100644
--- a/config/locales/server.pt.yml
+++ b/config/locales/server.pt.yml
@@ -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:
diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml
index e335373c0c6..9693df9c36c 100644
--- a/config/locales/server.pt_BR.yml
+++ b/config/locales/server.pt_BR.yml
@@ -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:
diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml
index 8904158bbdd..1ccbc6ec441 100644
--- a/config/locales/server.ro.yml
+++ b/config/locales/server.ro.yml
@@ -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ă:
diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml
index 3e7c8949165..10b35f2b2a2 100644
--- a/config/locales/server.ru.yml
+++ b/config/locales/server.ru.yml
@@ -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}, перейдя по следующей ссылке:
diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml
index 6cc8e3a3b42..11ba25a367c 100644
--- a/config/locales/server.sk.yml
+++ b/config/locales/server.sk.yml
@@ -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:
diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml
index a2c0e784690..9c06a0b8bf8 100644
--- a/config/locales/server.sq.yml
+++ b/config/locales/server.sq.yml
@@ -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:
diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml
index b5eb9d97c8a..ab2f2cd8c44 100644
--- a/config/locales/server.sv.yml
+++ b/config/locales/server.sv.yml
@@ -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:
diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml
index 0f044e9a0ca..ea2e66abdc7 100644
--- a/config/locales/server.tr_TR.yml
+++ b/config/locales/server.tr_TR.yml
@@ -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:
diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml
index c8a3312658c..a4b9b794082 100644
--- a/config/locales/server.uk.yml
+++ b/config/locales/server.uk.yml
@@ -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}, перейшовши за посиланням:
diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml
index 15dc847ba85..ab2a5822eb4 100644
--- a/config/locales/server.vi.yml
+++ b/config/locales/server.vi.yml
@@ -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}!"
diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml
index 1a9b11270fe..1e86066dae9 100644
--- a/config/locales/server.zh_CN.yml
+++ b/config/locales/server.zh_CN.yml
@@ -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} 上的新电子邮箱地址:
diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml
index 2a93ce0121e..f04a2abed44 100644
--- a/config/locales/server.zh_TW.yml
+++ b/config/locales/server.zh_TW.yml
@@ -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} 上的新電子郵箱位址:
diff --git a/config/routes.rb b/config/routes.rb
index a33b0b80e82..d28299a7bd4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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'
diff --git a/db/migrate/20160307190919_create_email_change_requests.rb b/db/migrate/20160307190919_create_email_change_requests.rb
new file mode 100644
index 00000000000..010c3e356d0
--- /dev/null
+++ b/db/migrate/20160307190919_create_email_change_requests.rb
@@ -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
diff --git a/db/migrate/20160308193142_rename_confirm_translation_key.rb b/db/migrate/20160308193142_rename_confirm_translation_key.rb
new file mode 100644
index 00000000000..b121bf9a943
--- /dev/null
+++ b/db/migrate/20160308193142_rename_confirm_translation_key.rb
@@ -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
diff --git a/lib/email_updater.rb b/lib/email_updater.rb
new file mode 100644
index 00000000000..71c57f05c82
--- /dev/null
+++ b/lib/email_updater.rb
@@ -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
diff --git a/spec/components/email_updater_spec.rb b/spec/components/email_updater_spec.rb
new file mode 100644
index 00000000000..b868e6c9a57
--- /dev/null
+++ b/spec/components/email_updater_spec.rb
@@ -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
+
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 4f753041c68..ff0e0d58c4e 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -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)
diff --git a/spec/controllers/users_email_controller_spec.rb b/spec/controllers/users_email_controller_spec.rb
index a470246a501..0a2bfe99ce3 100644
--- a/spec/controllers/users_email_controller_spec.rb
+++ b/spec/controllers/users_email_controller_spec.rb
@@ -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
diff --git a/spec/jobs/user_email_spec.rb b/spec/jobs/user_email_spec.rb
index 5054089b5d0..92fd5abd2a3 100644
--- a/spec/jobs/user_email_spec.rb
+++ b/spec/jobs/user_email_spec.rb
@@ -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
diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb
index a3092909791..ebc9b27e4c5 100644
--- a/spec/mailers/user_notifications_spec.rb
+++ b/spec/mailers/user_notifications_spec.rb
@@ -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) { "" }