FEATURE: Disallow login via omniauth when user has 2FA enabled.

This commit is contained in:
Guo Xiang Tan
2018-03-01 15:47:07 +08:00
parent 0fabf80dca
commit fb75f188ba
7 changed files with 77 additions and 25 deletions

View File

@ -27,16 +27,20 @@ export default Ember.Controller.extend(ModalFunctionality, {
loggingIn: false, loggingIn: false,
loggedIn: false, loggedIn: false,
processingEmailLink: false, processingEmailLink: false,
showLoginButtons: true,
canLoginLocal: setting('enable_local_logins'), canLoginLocal: setting('enable_local_logins'),
canLoginLocalWithEmail: setting('enable_local_logins_via_email'), canLoginLocalWithEmail: setting('enable_local_logins_via_email'),
loginRequired: Em.computed.alias('application.loginRequired'), loginRequired: Em.computed.alias('application.loginRequired'),
resetForm: function() { resetForm: function() {
this.set('authenticate', null); this.setProperties({
this.set('loggingIn', false); 'authenticate': null,
this.set('loggedIn', false); 'loggingIn': false,
this.set('secondFactorRequired', false); 'loggedIn': false,
'secondFactorRequired': false,
'showLoginButtons': true,
});
$("#credentials").show(); $("#credentials").show();
$("#second-factor").hide(); $("#second-factor").hide();
}, },
@ -250,16 +254,28 @@ export default Ember.Controller.extend(ModalFunctionality, {
}).property('authenticate'), }).property('authenticate'),
authenticationComplete(options) { authenticationComplete(options) {
const self = this; const self = this;
function loginError(errorMsg, className) { function loginError(errorMsg, className, callback) {
showModal('login'); showModal('login');
Ember.run.next(function() {
Ember.run.next(() => {
callback();
self.flash(errorMsg, className || 'success'); self.flash(errorMsg, className || 'success');
self.set('authenticate', null); self.set('authenticate', null);
}); });
} }
if (options.omniauth_disallow_totp) {
return loginError(I18n.t('login.omniauth_disallow_totp'), 'error', () => {
this.setProperties({
'loginName': options.email,
'showLoginButtons': false,
});
$('#login-account-password').focus();
});
}
for (let i=0; i<AuthErrors.length; i++) { for (let i=0; i<AuthErrors.length; i++) {
const cond = AuthErrors[i]; const cond = AuthErrors[i];
if (options[cond]) { if (options[cond]) {

View File

@ -1,10 +1,12 @@
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action="login"}} {{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action="login"}}
{{#d-modal-body title="login.title" class="login-modal"}} {{#d-modal-body title="login.title" class="login-modal"}}
{{login-buttons {{#if showLoginButtons}}
canLoginLocalWithEmail=canLoginLocalWithEmail {{login-buttons
processingEmailLink=processingEmailLink canLoginLocalWithEmail=canLoginLocalWithEmail
emailLogin='emailLogin' processingEmailLink=processingEmailLink
externalLogin='externalLogin'}} emailLogin='emailLogin'
externalLogin='externalLogin'}}
{{/if}}
{{#if canLoginLocal}} {{#if canLoginLocal}}
<form id='login-form' method='post'> <form id='login-form' method='post'>

View File

@ -1,10 +1,12 @@
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action="login"}} {{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action="login"}}
{{#d-modal-body title="login.title" class="login-modal"}} {{#d-modal-body title="login.title" class="login-modal"}}
{{login-buttons {{#if showLoginButtons}}
canLoginLocalWithEmail=canLoginLocalWithEmail {{login-buttons
processingEmailLink=processingEmailLink canLoginLocalWithEmail=canLoginLocalWithEmail
emailLogin='emailLogin' processingEmailLink=processingEmailLink
externalLogin='externalLogin'}} emailLogin='emailLogin'
externalLogin='externalLogin'}}
{{/if}}
{{#if canLoginLocal}} {{#if canLoginLocal}}
<form id='login-form' method='post'> <form id='login-form' method='post'>

View File

@ -114,6 +114,11 @@ class Users::OmniauthCallbacksController < ApplicationController
end end
def user_found(user) def user_found(user)
if user.totp_enabled?
@auth_result.omniauth_disallow_totp = true
return
end
# automatically activate/unstage any account if a provider marked the email valid # automatically activate/unstage any account if a provider marked the email valid
if @auth_result.email_valid && @auth_result.email == user.email if @auth_result.email_valid && @auth_result.email == user.email
user.update!(staged: false) user.update!(staged: false)

View File

@ -1140,6 +1140,7 @@ en:
not_allowed_from_ip_address: "You can't login from that IP address." not_allowed_from_ip_address: "You can't login from that IP address."
admin_not_allowed_from_ip_address: "You can't log in as admin from that IP address." admin_not_allowed_from_ip_address: "You can't log in as admin from that IP address."
resend_activation_email: "Click here to send the activation email again." resend_activation_email: "Click here to send the activation email again."
omniauth_disallow_totp: "Your account has 2FA enabled. Please login with your password."
resend_title: "Resend Activation Email" resend_title: "Resend Activation Email"
change_email: "Change Email Address" change_email: "Change Email Address"

View File

@ -4,7 +4,7 @@ class Auth::Result
:awaiting_approval, :authenticated, :authenticator_name, :awaiting_approval, :authenticated, :authenticator_name,
:requires_invite, :not_allowed_from_ip_address, :requires_invite, :not_allowed_from_ip_address,
:admin_not_allowed_from_ip_address, :omit_username, :admin_not_allowed_from_ip_address, :omit_username,
:skip_email_validation, :destination_url :skip_email_validation, :destination_url, :omniauth_disallow_totp
attr_accessor( attr_accessor(
:failed, :failed,
@ -42,13 +42,22 @@ class Auth::Result
date: I18n.l(user.suspended_till, format: :date_only), reason: user.suspend_reason) date: I18n.l(user.suspended_till, format: :date_only), reason: user.suspend_reason)
} }
else else
result = { result =
authenticated: !!authenticated, if omniauth_disallow_totp
awaiting_activation: !!awaiting_activation, {
awaiting_approval: !!awaiting_approval, omniauth_disallow_totp: !!omniauth_disallow_totp,
not_allowed_from_ip_address: !!not_allowed_from_ip_address, email: email
admin_not_allowed_from_ip_address: !!admin_not_allowed_from_ip_address }
} else
{
authenticated: !!authenticated,
awaiting_activation: !!awaiting_activation,
awaiting_approval: !!awaiting_approval,
not_allowed_from_ip_address: !!not_allowed_from_ip_address,
admin_not_allowed_from_ip_address: !!admin_not_allowed_from_ip_address
}
end
result[:destination_url] = destination_url if authenticated && destination_url.present? result[:destination_url] = destination_url if authenticated && destination_url.present?
result result
end end

View File

@ -132,6 +132,23 @@ RSpec.describe Users::OmniauthCallbacksController do
expect(user.registration_ip_address).to be_present expect(user.registration_ip_address).to be_present
end end
context 'when user has second factor enabled' do
before do
user.create_totp(enabled: true)
end
it 'should return the right response' do
get "/auth/google_oauth2/callback.json"
expect(response).to be_success
response_body = JSON.parse(response.body)
expect(response_body["email"]).to eq(user.email)
expect(response_body["omniauth_disallow_totp"]).to eq(true)
end
end
context 'when user has not verified his email' do context 'when user has not verified his email' do
before do before do
GoogleUserInfo.create!(google_user_id: '12345', user: user) GoogleUserInfo.create!(google_user_id: '12345', user: user)