DEV: Improve robustness of associate_accounts_controller
This handles a few edge cases which are extremely rare (due to the UI layout), but still technically possible: - Ensure users are authenticated before attempting association. - Add a message and logic for when a user already has an association for a given auth provider.
This commit is contained in:
parent
2cae29f644
commit
46dc189850
|
@ -3,9 +3,14 @@ import { ajax } from "discourse/lib/ajax";
|
||||||
import { next } from "@ember/runloop";
|
import { next } from "@ember/runloop";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import cookie from "discourse/lib/cookie";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
beforeModel() {
|
beforeModel(transition) {
|
||||||
|
if (!this.currentUser) {
|
||||||
|
cookie("destination_url", transition.intent.url);
|
||||||
|
return this.replaceWith("login");
|
||||||
|
}
|
||||||
const params = this.paramsFor("associate-account");
|
const params = this.paramsFor("associate-account");
|
||||||
this.replaceWith(`preferences.account`, this.currentUser).then(() =>
|
this.replaceWith(`preferences.account`, this.currentUser).then(() =>
|
||||||
next(() =>
|
next(() =>
|
||||||
|
|
|
@ -10,14 +10,24 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if model.account_description}}
|
{{#if model.existing_account_description}}
|
||||||
{{i18n "user.associated_accounts.confirm_description.account_specific"
|
<p>
|
||||||
provider=(i18n (concat "login." model.provider_name ".name"))
|
{{i18n "user.associated_accounts.confirm_description.disconnect"
|
||||||
account_description=model.account_description}}
|
provider=(i18n (concat "login." model.provider_name ".name"))
|
||||||
{{else}}
|
account_description=model.existing_account_description}}
|
||||||
{{i18n "user.associated_accounts.confirm_description.generic"
|
</p>
|
||||||
provider=(i18n (concat "login." model.provider_name ".name"))}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{#if model.account_description}}
|
||||||
|
{{i18n "user.associated_accounts.confirm_description.account_specific"
|
||||||
|
provider=(i18n (concat "login." model.provider_name ".name"))
|
||||||
|
account_description=model.account_description}}
|
||||||
|
{{else}}
|
||||||
|
{{i18n "user.associated_accounts.confirm_description.generic"
|
||||||
|
provider=(i18n (concat "login." model.provider_name ".name"))}}
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
{{/d-modal-body}}
|
{{/d-modal-body}}
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
|
@ -3,41 +3,51 @@
|
||||||
class Users::AssociateAccountsController < ApplicationController
|
class Users::AssociateAccountsController < ApplicationController
|
||||||
SECURE_SESSION_PREFIX ||= "omniauth_reconnect"
|
SECURE_SESSION_PREFIX ||= "omniauth_reconnect"
|
||||||
|
|
||||||
|
before_action :ensure_logged_in
|
||||||
|
|
||||||
def connect_info
|
def connect_info
|
||||||
auth = get_auth_hash
|
account_description = authenticator.description_for_auth_hash(auth_hash)
|
||||||
|
existing_account_description = authenticator.description_for_user(current_user).presence
|
||||||
provider_name = auth.provider
|
render json: {
|
||||||
authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name }
|
token: params[:token],
|
||||||
raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) if authenticator.nil?
|
provider_name: auth_hash.provider,
|
||||||
|
account_description: account_description,
|
||||||
account_description = authenticator.description_for_auth_hash(auth)
|
existing_account_description: existing_account_description
|
||||||
|
}
|
||||||
render json: { token: params[:token], provider_name: provider_name, account_description: account_description }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def connect
|
def connect
|
||||||
auth = get_auth_hash
|
if authenticator.description_for_user(current_user).present? && authenticator.can_revoke?
|
||||||
secure_session[self.class.key(params[:token])] = nil
|
authenticator.revoke(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
provider_name = auth.provider
|
DiscourseEvent.trigger(:before_auth, authenticator, auth_hash, session, cookies, request)
|
||||||
authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name }
|
|
||||||
raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) if authenticator.nil?
|
|
||||||
|
|
||||||
DiscourseEvent.trigger(:before_auth, authenticator, auth, session, cookies, request)
|
|
||||||
auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
|
auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
|
||||||
DiscourseEvent.trigger(:after_auth, authenticator, auth_result, session, cookies, request)
|
DiscourseEvent.trigger(:after_auth, authenticator, auth_result, session, cookies, request)
|
||||||
|
|
||||||
|
secure_session[self.class.key(params[:token])] = nil
|
||||||
|
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def get_auth_hash
|
def auth_hash
|
||||||
token = params[:token]
|
@auth_hash ||= begin
|
||||||
json = secure_session[self.class.key(token)]
|
token = params[:token]
|
||||||
raise Discourse::NotFound if json.nil?
|
json = secure_session[self.class.key(token)]
|
||||||
|
raise Discourse::NotFound if json.nil?
|
||||||
|
|
||||||
OmniAuth::AuthHash.new(JSON.parse(json))
|
OmniAuth::AuthHash.new(JSON.parse(json))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authenticator
|
||||||
|
provider_name = auth_hash.provider
|
||||||
|
authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name }
|
||||||
|
raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) if authenticator.nil?
|
||||||
|
raise Discourse::InvalidAccess.new(I18n.t('authenticator_no_connect')) if !authenticator.can_connect_existing_user?
|
||||||
|
authenticator
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.key(token)
|
def self.key(token)
|
||||||
|
|
|
@ -1359,6 +1359,7 @@ en:
|
||||||
not_connected: "(not connected)"
|
not_connected: "(not connected)"
|
||||||
confirm_modal_title: "Connect %{provider} Account"
|
confirm_modal_title: "Connect %{provider} Account"
|
||||||
confirm_description:
|
confirm_description:
|
||||||
|
disconnect: "Your existing %{provider} account '%{account_description}' will be disconnected."
|
||||||
account_specific: "Your %{provider} account '%{account_description}' will be used for authentication."
|
account_specific: "Your %{provider} account '%{account_description}' will be used for authentication."
|
||||||
generic: "Your %{provider} account will be used for authentication."
|
generic: "Your %{provider} account will be used for authentication."
|
||||||
|
|
||||||
|
|
|
@ -286,6 +286,7 @@ en:
|
||||||
not_found: "The requested URL or resource could not be found."
|
not_found: "The requested URL or resource could not be found."
|
||||||
invalid_access: "You are not permitted to view the requested resource."
|
invalid_access: "You are not permitted to view the requested resource."
|
||||||
authenticator_not_found: "Authentication method does not exist, or has been disabled."
|
authenticator_not_found: "Authentication method does not exist, or has been disabled."
|
||||||
|
authenticator_no_connect: "This authentication provider does not allow connection to an existing forum account."
|
||||||
invalid_api_credentials: "You are not permitted to view the requested resource. The API username or key is invalid."
|
invalid_api_credentials: "You are not permitted to view the requested resource. The API username or key is invalid."
|
||||||
provider_not_enabled: "You are not permitted to view the requested resource. The authentication provider is not enabled."
|
provider_not_enabled: "You are not permitted to view the requested resource. The authentication provider is not enabled."
|
||||||
provider_not_found: "You are not permitted to view the requested resource. The authentication provider does not exist."
|
provider_not_found: "You are not permitted to view the requested resource. The authentication provider does not exist."
|
||||||
|
|
|
@ -96,11 +96,19 @@ RSpec.describe Users::AssociateAccountsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the correct response for non-existent tokens" do
|
it "returns the correct response for non-existent tokens" do
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
get "/associate/12345678901234567890123456789012.json"
|
get "/associate/12345678901234567890123456789012.json"
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
|
|
||||||
get "/associate/shorttoken.json"
|
get "/associate/shorttoken.json"
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "requires login" do
|
||||||
|
# XHR should 403
|
||||||
|
get "/associate/#{SecureRandom.hex}.json"
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue