2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-09-06 10:50:21 +08:00
discourse/spec/requests/users_email_controller_spec.rb
Jeff Wong f4f8a293e7 FEATURE: Implement 2factor login TOTP
implemented review items.

Blocking previous codes - valid 2-factor auth tokens can only be authenticated once/30 seconds.
I played with updating the “last used” any time the token was attempted but that seemed to be overkill, and frustrating as to why a token would fail.
Translatable texts.
Move second factor logic to a helper class.
Move second factor specific controller endpoints to its own controller.
Move serialization logic for 2-factor details in admin user views.
Add a login ember component for de-duplication
Fix up code formatting
Change verbiage of google authenticator

add controller tests:
second factor controller tests
change email tests
change password tests
admin login tests

add qunit tests - password reset, preferences

fix: check for 2factor on change email controller
fix: email controller - only show second factor errors on attempt
fix: check against 'true' to enable second factor.

Add modal for explaining what 2fa with links to Google Authenticator/FreeOTP

add two factor to email signin link

rate limit if second factor token present

add rate limiter test for second factor attempts
2018-02-21 09:04:07 +08:00

208 lines
6.3 KiB
Ruby

require 'rails_helper'
describe UsersEmailController do
describe '.confirm' do
it 'errors out for invalid tokens' do
get "/u/authorize-email/asdfasdf"
expect(response).to be_success
expect(response.body).to include(I18n.t('change_email.already_done'))
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 "/u/authorize-email/#{user.email_tokens.last.token}"
expect(response).to be_success
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'))
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
user.user_stat.update_columns(bounce_score: 42, reset_bounce_score_after: 1.week.from_now)
events = DiscourseEvent.track_events do
get "/u/authorize-email/#{user.email_tokens.last.token}"
end
expect(events.map { |event| event[:event_name] }).to include(
:user_logged_in, :user_first_logged_in
)
expect(response).to be_success
expect(response.body).to include(I18n.t('change_email.confirmed'))
user.reload
expect(user.user_stat.bounce_score).to eq(0)
expect(user.user_stat.reset_bounce_score_after).to eq(nil)
end
context 'second factor required' do
second_factor_data = "rcyryaqage3jexfj"
before do
user.user_second_factor = UserSecondFactor.create(user_id: user.id, method: "totp", data: second_factor_data, enabled: true)
end
it 'requires a second factor token' do
get "/u/authorize-email/#{user.email_tokens.last.token}"
expect(response.body).to include(I18n.t("login.second_factor_title"))
expect(response.body).not_to include(I18n.t("login.invalid_second_factor_code"))
end
it 'adds an error on a second factor attempt' do
get "/u/authorize-email/#{user.email_tokens.last.token}", params: {
second_factor_token: "000000"
}
expect(response.body).to include(I18n.t("login.invalid_second_factor_code"))
end
it 'confirms with a correct second token' do
get "/u/authorize-email/#{user.email_tokens.last.token}", params: {
second_factor_token: ROTP::TOTP.new(second_factor_data).now
}
expect(response).to be_success
expect(response.body).not_to include(I18n.t("login.second_factor_title"))
expect(response.body).not_to include(I18n.t("login.invalid_second_factor_code"))
end
end
end
end
describe '.update' do
let(:new_email) { 'bubblegum@adventuretime.ooo' }
it "requires you to be logged in" do
put "/u/asdf/preferences/email.json"
expect(response.status).to eq(403)
end
context 'when logged in' do
let(:user) { Fabricate(:user) }
before do
sign_in(user)
end
it 'raises an error without an email parameter' do
put "/u/#{user.username}/preferences/email.json"
expect(response.status).to eq(400)
end
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)
put "/u/#{user.username}/preferences/email.json", params: { email: new_email }
expect(response).to be_forbidden
end
context 'when the new email address is taken' do
let!(:other_user) { Fabricate(:coding_horror) }
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
}
expect(response).to_not be_success
end
it 'raises an error if there is whitespace too' do
put "/u/#{user.username}/preferences/email.json", params: {
email: "#{other_user.email} "
}
expect(response).to_not be_success
end
end
context 'hide_email_address_taken is enabled' do
before do
SiteSetting.hide_email_address_taken = true
end
it 'responds with success' do
put "/u/#{user.username}/preferences/email.json", params: {
email: other_user.email
}
expect(response).to be_success
end
end
end
context 'when new email is different case of existing email' do
let!(:other_user) { Fabricate(:user, email: 'case.insensitive@gmail.com') }
it 'raises an error' do
put "/u/#{user.username}/preferences/email.json", params: {
email: other_user.email.upcase
}
expect(response).to_not be_success
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"
put "/u/#{user.username}/preferences/email.json", params: {
email: "not_good@mailinator.com"
}
expect(response).to_not be_success
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"
put "/u/#{user.username}/preferences/email.json", params: {
email: new_email
}
expect(response).to_not be_success
end
context 'success' do
it 'has an email token' do
expect do
put "/u/#{user.username}/preferences/email.json", params: {
email: new_email
}
end.to change(EmailChangeRequest, :count)
end
end
end
end
end