FEATURE: Allow connecting associated accounts when two-factor is enabled (#6754)

Previously the 'reconnect' process was a bit magic - IF you were already logged into discourse, and followed the auth flow, your account would be reconnected and you would be 'logged in again'.

Now, we explicitly check for a reconnect=true parameter when the flow is started, store it in the session, and then only follow the reconnect logic if that variable is present. Setting this parameter also skips the 'logged in again' step, which means reconnect now works with 2fa enabled.
This commit is contained in:
David Taylor 2018-12-11 13:19:00 +00:00 committed by GitHub
parent 285ff3bfbd
commit c7c56af397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 5 deletions

View File

@ -243,7 +243,7 @@ export default Ember.Controller.extend(
},
connectAccount(method) {
method.doLogin();
method.doLogin(true);
}
}
}

View File

@ -24,7 +24,7 @@ const LoginMethod = Ember.Object.extend({
);
},
doLogin() {
doLogin(reconnect = false) {
const name = this.get("name");
const customLogin = this.get("customLogin");
@ -33,6 +33,10 @@ const LoginMethod = Ember.Object.extend({
} else {
let authUrl = this.get("custom_url") || Discourse.getURL("/auth/" + name);
if (reconnect) {
authUrl += "?reconnect=true";
}
if (this.get("full_screen_login")) {
document.cookie = "fsl=true";
window.location = authUrl;
@ -45,7 +49,8 @@ const LoginMethod = Ember.Object.extend({
const width = this.get("frame_width") || 800;
if (name === "facebook") {
authUrl = authUrl + "?display=popup";
authUrl += authUrl.includes("?") ? "&" : "?";
authUrl += "display=popup";
}
const w = window.open(

View File

@ -25,8 +25,18 @@ class Users::OmniauthCallbacksController < ApplicationController
authenticator = self.class.find_authenticator(params[:provider])
provider = DiscoursePluginRegistry.auth_providers.find { |p| p.name == params[:provider] }
if authenticator.can_connect_existing_user? && current_user
if session.delete(:auth_reconnect) && authenticator.can_connect_existing_user? && current_user
# If we're reconnecting, don't actually try and log the user in
@auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
if provider&.full_screen_login || cookies['fsl']
cookies.delete('fsl')
return redirect_to Discourse.base_uri("/my/preferences/account")
else
return respond_to do |format|
format.html
format.json { render json: @auth_result.to_client_hash }
end
end
else
@auth_result = authenticator.after_authenticate(auth)
end
@ -64,7 +74,7 @@ class Users::OmniauthCallbacksController < ApplicationController
@auth_result.authenticator_name = authenticator.name
complete_response_data
if (provider && provider.full_screen_login) || cookies['fsl']
if provider&.full_screen_login || cookies['fsl']
cookies.delete('fsl')
cookies['_bypass_cache'] = true
cookies[:authentication_data] = @auth_result.to_client_hash.to_json

View File

@ -17,6 +17,12 @@ class Middleware::OmniauthBypassMiddleware
authenticator.register_middleware(self)
end
end
@omniauth.before_request_phase do |env|
# If the user is trying to reconnect to an existing account, store in session
request = ActionDispatch::Request.new(env)
request.session[:auth_reconnect] = !!request.params["reconnect"]
end
end
def call(env)

View File

@ -338,6 +338,86 @@ RSpec.describe Users::OmniauthCallbacksController do
end
end
context 'when attempting reconnect' do
let(:user2) { Fabricate(:user) }
before do
GoogleUserInfo.create!(google_user_id: '12345', user: user)
GoogleUserInfo.create!(google_user_id: '123456', user: user2)
OmniAuth.config.mock_auth[:google_oauth2] = OmniAuth::AuthHash.new(
provider: 'google_oauth2',
uid: '12345',
info: OmniAuth::AuthHash::InfoHash.new(
email: 'someother_email@test.com',
name: 'Some name'
),
extra: {
raw_info: OmniAuth::AuthHash.new(
email_verified: true,
email: 'someother_email@test.com',
family_name: 'Huh',
given_name: user.name,
gender: 'male',
name: "#{user.name} Huh",
)
},
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
end
it 'should not reconnect normally' do
# Log in normally
get "/auth/google_oauth2"
expect(response.status).to eq(302)
expect(session[:auth_reconnect]).to eq(false)
get "/auth/google_oauth2/callback.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(user.id)
# Log into another user
OmniAuth.config.mock_auth[:google_oauth2].uid = "123456"
get "/auth/google_oauth2"
expect(response.status).to eq(302)
expect(session[:auth_reconnect]).to eq(false)
get "/auth/google_oauth2/callback.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(user2.id)
expect(GoogleUserInfo.count).to eq(2)
end
it 'should reconnect if parameter supplied' do
# Log in normally
get "/auth/google_oauth2?reconnect=true"
expect(response.status).to eq(302)
expect(session[:auth_reconnect]).to eq(true)
get "/auth/google_oauth2/callback.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(user.id)
# Clear cookie after login
expect(session[:auth_reconnect]).to eq(nil)
# Disconnect
GoogleUserInfo.find_by(user_id: user.id).destroy
# Reconnect flow:
get "/auth/google_oauth2?reconnect=true"
expect(response.status).to eq(302)
expect(session[:auth_reconnect]).to eq(true)
OmniAuth.config.mock_auth[:google_oauth2].uid = "123456"
get "/auth/google_oauth2/callback.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(user.id)
expect(GoogleUserInfo.count).to eq(1)
end
end
context 'after changing email' do
require_dependency 'email_updater'