mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
SECURITY: Support for confirm old as well as new email accounts
This commit is contained in:
parent
d62689fa76
commit
5771d2aee2
47 changed files with 454 additions and 114 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
9
app/models/email_change_request.rb
Normal file
9
app/models/email_change_request.rb
Normal 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
|
|
@ -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
|
||||
User.transaction do
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
15
app/views/users_email/confirm.html.erb
Normal file
15
app/views/users_email/confirm.html.erb
Normal 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>
|
|
@ -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} عن طريق الضغط علي الرابط التالي:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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: |
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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ä:
|
||||
|
|
|
@ -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 :
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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} 用のメールアドレスを確認してください:
|
||||
|
|
|
@ -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} 사이트에서 사용할 새로운 이메일을 아래 링크를 클릭하여 확인하세요:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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ă:
|
||||
|
|
|
@ -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}, перейдя по следующей ссылке:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}, перейшовши за посиланням:
|
||||
|
|
|
@ -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}!"
|
||||
|
|
|
@ -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} 上的新电子邮箱地址:
|
||||
|
|
|
@ -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} 上的新電子郵箱位址:
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
15
db/migrate/20160307190919_create_email_change_requests.rb
Normal file
15
db/migrate/20160307190919_create_email_change_requests.rb
Normal 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
|
|
@ -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
113
lib/email_updater.rb
Normal 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
|
124
spec/components/email_updater_spec.rb
Normal file
124
spec/components/email_updater_spec.rb
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) { "" }
|
||||
|
|
Loading…
Reference in a new issue