2019-04-29 20:27:42 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-03-07 13:45:33 -05:00
|
|
|
require 'rails_helper'
|
2019-12-16 19:33:51 -05:00
|
|
|
require 'rotp'
|
2016-03-07 13:45:33 -05:00
|
|
|
|
|
|
|
describe UsersEmailController do
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
fab!(:user) { Fabricate(:user) }
|
|
|
|
fab!(:moderator) { Fabricate(:moderator) }
|
|
|
|
|
|
|
|
describe "#confirm-new-email" do
|
|
|
|
it 'redirects to login for signed out accounts' do
|
|
|
|
get "/u/confirm-new-email/asdfasdf"
|
|
|
|
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
expect(response.redirect_url).to eq("http://test.localhost/login")
|
|
|
|
end
|
|
|
|
|
2016-03-07 14:40:11 -05:00
|
|
|
it 'errors out for invalid tokens' do
|
2019-11-20 02:31:25 -05:00
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
get "/u/confirm-new-email/asdfasdf"
|
2017-08-31 00:06:56 -04:00
|
|
|
|
2018-06-07 04:11:09 -04:00
|
|
|
expect(response.status).to eq(200)
|
2017-10-20 12:04:56 -04:00
|
|
|
expect(response.body).to include(I18n.t('change_email.already_done'))
|
2016-03-07 14:40:11 -05:00
|
|
|
end
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
it 'does not change email if accounts mismatch' do
|
|
|
|
updater = EmailUpdater.new(user.guardian, user)
|
|
|
|
updater.change_to('new.n.cool@example.com')
|
2016-03-07 14:40:11 -05:00
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
old_email = user.email
|
2016-03-07 14:40:11 -05:00
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
sign_in(moderator)
|
2017-08-31 00:06:56 -04:00
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
put "/u/confirm-new-email", params: {
|
|
|
|
token: "#{user.email_tokens.last.token}"
|
|
|
|
}
|
2017-08-31 00:06:56 -04:00
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
user.reload
|
|
|
|
expect(user.email).to eq(old_email)
|
2016-03-07 14:40:11 -05:00
|
|
|
end
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
context "with a valid user" do
|
2016-03-07 14:40:11 -05:00
|
|
|
let(:updater) { EmailUpdater.new(user.guardian, user) }
|
|
|
|
|
|
|
|
before do
|
2019-11-20 02:31:25 -05:00
|
|
|
sign_in(user)
|
2016-03-07 14:40:11 -05:00
|
|
|
updater.change_to('new.n.cool@example.com')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'confirms with a correct token' do
|
2017-02-20 04:37:01 -05:00
|
|
|
user.user_stat.update_columns(bounce_score: 42, reset_bounce_score_after: 1.week.from_now)
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
put "/u/confirm-new-email", params: {
|
|
|
|
token: "#{user.email_tokens.last.token}"
|
|
|
|
}
|
2017-06-01 04:19:42 -04:00
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
expect(response.status).to eq(302)
|
|
|
|
expect(response.redirect_url).to include("done")
|
2017-02-20 04:37:01 -05:00
|
|
|
|
|
|
|
user.reload
|
|
|
|
|
|
|
|
expect(user.user_stat.bounce_score).to eq(0)
|
|
|
|
expect(user.user_stat.reset_bounce_score_after).to eq(nil)
|
2019-11-20 02:31:25 -05:00
|
|
|
expect(user.email).to eq("new.n.cool@example.com")
|
2018-05-08 15:23:45 -04:00
|
|
|
end
|
|
|
|
|
2017-12-21 20:18:12 -05:00
|
|
|
context 'second factor required' do
|
2019-05-06 23:12:20 -04:00
|
|
|
fab!(:second_factor) { Fabricate(:user_second_factor_totp, user: user) }
|
2019-11-14 18:19:23 -05:00
|
|
|
fab!(:backup_code) { Fabricate(:user_second_factor_backup, user: user) }
|
2017-12-21 20:18:12 -05:00
|
|
|
|
|
|
|
it 'requires a second factor token' do
|
2019-11-20 02:31:25 -05:00
|
|
|
get "/u/confirm-new-email/#{user.email_tokens.last.token}"
|
2018-02-20 01:44:51 -05:00
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
response_body = response.body
|
|
|
|
|
|
|
|
expect(response_body).to include(I18n.t("login.second_factor_title"))
|
|
|
|
expect(response_body).not_to include(I18n.t("login.invalid_second_factor_code"))
|
2017-12-21 20:18:12 -05:00
|
|
|
end
|
|
|
|
|
2019-11-14 18:19:23 -05:00
|
|
|
it 'requires a backup token' do
|
2019-11-20 02:31:25 -05:00
|
|
|
get "/u/confirm-new-email/#{user.email_tokens.last.token}?show_backup=true"
|
2019-11-14 18:19:23 -05:00
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
response_body = response.body
|
|
|
|
|
|
|
|
expect(response_body).to include(I18n.t("login.second_factor_backup_title"))
|
|
|
|
end
|
|
|
|
|
2017-12-21 20:18:12 -05:00
|
|
|
it 'adds an error on a second factor attempt' do
|
2019-11-20 02:31:25 -05:00
|
|
|
put "/u/confirm-new-email", params: {
|
|
|
|
token: user.email_tokens.last.token,
|
2018-06-28 04:12:32 -04:00
|
|
|
second_factor_token: "000000",
|
|
|
|
second_factor_method: UserSecondFactor.methods[:totp]
|
2018-02-20 01:44:51 -05:00
|
|
|
}
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
expect(response.status).to eq(302)
|
|
|
|
expect(flash[:invalid_second_factor]).to eq(true)
|
2017-12-21 20:18:12 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'confirms with a correct second token' do
|
2019-11-20 02:31:25 -05:00
|
|
|
put "/u/confirm-new-email", params: {
|
2018-06-28 04:12:32 -04:00
|
|
|
second_factor_token: ROTP::TOTP.new(second_factor.data).now,
|
2019-11-20 02:31:25 -05:00
|
|
|
second_factor_method: UserSecondFactor.methods[:totp],
|
|
|
|
token: user.email_tokens.last.token
|
2018-02-20 01:44:51 -05:00
|
|
|
}
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
expect(response.status).to eq(302)
|
2018-02-20 01:44:51 -05:00
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
user.reload
|
|
|
|
expect(user.email).to eq("new.n.cool@example.com")
|
2017-12-21 20:18:12 -05:00
|
|
|
end
|
|
|
|
end
|
2016-03-07 14:40:11 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-11-20 02:31:25 -05:00
|
|
|
describe '#confirm-old-email' do
|
|
|
|
|
|
|
|
it 'redirects to login for signed out accounts' do
|
|
|
|
get "/u/confirm-old-email/asdfasdf"
|
|
|
|
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
expect(response.redirect_url).to eq("http://test.localhost/login")
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'errors out for invalid tokens' do
|
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
get "/u/confirm-old-email/asdfasdf"
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.body).to include(I18n.t('change_email.already_done'))
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'bans change when accounts do not match' do
|
|
|
|
|
|
|
|
sign_in(user)
|
|
|
|
updater = EmailUpdater.new(moderator.guardian, moderator)
|
|
|
|
updater.change_to('new.n.cool@example.com')
|
|
|
|
|
|
|
|
get "/u/confirm-old-email/#{moderator.email_tokens.last.token}"
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(body).to include("alert-error")
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'valid old address token' do
|
|
|
|
|
|
|
|
it 'confirms with a correct token' do
|
|
|
|
# NOTE: only moderators need to confirm both old and new
|
|
|
|
sign_in(moderator)
|
|
|
|
updater = EmailUpdater.new(moderator.guardian, moderator)
|
|
|
|
updater.change_to('new.n.cool@example.com')
|
|
|
|
|
|
|
|
get "/u/confirm-old-email/#{moderator.email_tokens.last.token}"
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
body = CGI.unescapeHTML(response.body)
|
|
|
|
|
|
|
|
expect(body)
|
|
|
|
.to include(I18n.t('change_email.authorizing_old.title'))
|
|
|
|
|
|
|
|
expect(body)
|
|
|
|
.to include(I18n.t('change_email.authorizing_old.description'))
|
|
|
|
|
|
|
|
put "/u/confirm-old-email", params: {
|
|
|
|
token: moderator.email_tokens.last.token
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
expect(response.redirect_url).to include("done=true")
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-02-20 01:44:51 -05:00
|
|
|
describe '#update' do
|
2016-03-07 13:45:33 -05:00
|
|
|
let(:new_email) { 'bubblegum@adventuretime.ooo' }
|
|
|
|
|
|
|
|
it "requires you to be logged in" do
|
2018-02-20 01:44:51 -05:00
|
|
|
put "/u/#{user.username}/preferences/email.json", params: { email: new_email }
|
2018-01-11 22:15:10 -05:00
|
|
|
expect(response.status).to eq(403)
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'when logged in' do
|
2017-08-31 00:06:56 -04:00
|
|
|
before do
|
|
|
|
sign_in(user)
|
|
|
|
end
|
2016-03-07 13:45:33 -05:00
|
|
|
|
|
|
|
it 'raises an error without an email parameter' do
|
2018-01-11 22:15:10 -05:00
|
|
|
put "/u/#{user.username}/preferences/email.json"
|
|
|
|
expect(response.status).to eq(400)
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
|
2018-08-20 21:54:34 -04:00
|
|
|
it 'raises an error without an invalid email' do
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: { email: "sam@not-email.com'" }
|
|
|
|
expect(response.status).to eq(422)
|
|
|
|
expect(response.body).to include("email is invalid")
|
|
|
|
end
|
|
|
|
|
2016-03-07 13:45:33 -05:00
|
|
|
it "raises an error if you can't edit the user's email" do
|
|
|
|
Guardian.any_instance.expects(:can_edit_email?).with(user).returns(false)
|
2017-08-31 00:06:56 -04:00
|
|
|
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: { email: new_email }
|
|
|
|
|
2016-03-07 13:45:33 -05:00
|
|
|
expect(response).to be_forbidden
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the new email address is taken' do
|
2019-05-06 23:12:20 -04:00
|
|
|
fab!(:other_user) { Fabricate(:coding_horror) }
|
2017-08-31 00:06:56 -04:00
|
|
|
|
2017-10-04 11:41:08 -04:00
|
|
|
context 'hide_email_address_taken is disabled' do
|
|
|
|
before do
|
|
|
|
SiteSetting.hide_email_address_taken = false
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an error' do
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: other_user.email
|
|
|
|
}
|
|
|
|
|
2018-06-05 03:29:17 -04:00
|
|
|
expect(response).to_not be_successful
|
2017-10-04 11:41:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an error if there is whitespace too' do
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: "#{other_user.email} "
|
|
|
|
}
|
|
|
|
|
2018-06-05 03:29:17 -04:00
|
|
|
expect(response).to_not be_successful
|
2017-10-04 11:41:08 -04:00
|
|
|
end
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
|
2017-10-04 11:41:08 -04:00
|
|
|
context 'hide_email_address_taken is enabled' do
|
|
|
|
before do
|
|
|
|
SiteSetting.hide_email_address_taken = true
|
|
|
|
end
|
2017-08-31 00:06:56 -04:00
|
|
|
|
2017-10-04 11:41:08 -04:00
|
|
|
it 'responds with success' do
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: other_user.email
|
|
|
|
}
|
|
|
|
|
2018-06-07 04:11:09 -04:00
|
|
|
expect(response.status).to eq(200)
|
2017-10-04 11:41:08 -04:00
|
|
|
end
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when new email is different case of existing email' do
|
2019-05-06 23:12:20 -04:00
|
|
|
fab!(:other_user) { Fabricate(:user, email: 'case.insensitive@gmail.com') }
|
2016-03-07 13:45:33 -05:00
|
|
|
|
|
|
|
it 'raises an error' do
|
2017-08-31 00:06:56 -04:00
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: other_user.email.upcase
|
|
|
|
}
|
|
|
|
|
2018-06-05 03:29:17 -04:00
|
|
|
expect(response).to_not be_successful
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an error when new email domain is present in email_domains_blacklist site setting' do
|
|
|
|
SiteSetting.email_domains_blacklist = "mailinator.com"
|
2017-08-31 00:06:56 -04:00
|
|
|
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: "not_good@mailinator.com"
|
|
|
|
}
|
|
|
|
|
2018-06-05 03:29:17 -04:00
|
|
|
expect(response).to_not be_successful
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an error when new email domain is not present in email_domains_whitelist site setting' do
|
|
|
|
SiteSetting.email_domains_whitelist = "discourse.org"
|
2017-08-31 00:06:56 -04:00
|
|
|
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: new_email
|
|
|
|
}
|
|
|
|
|
2018-06-05 03:29:17 -04:00
|
|
|
expect(response).to_not be_successful
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'success' do
|
|
|
|
it 'has an email token' do
|
2017-08-31 00:06:56 -04:00
|
|
|
expect do
|
|
|
|
put "/u/#{user.username}/preferences/email.json", params: {
|
|
|
|
email: new_email
|
|
|
|
}
|
|
|
|
end.to change(EmailChangeRequest, :count)
|
2016-03-07 13:45:33 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|