mirror of
https://github.com/discourse/discourse.git
synced 2025-06-23 03:51:43 +08:00
FEATURE: add link to "associated accounts" providers (#33275)
This adds a link for each authentication providers that are listed in /my/preferences/account in the "Associated Accounts" section. This is particularly useful when Discourse is being used in the PWA or in the DiscourseMobile app where there's no browser bar available and the only way to visit the provider's website is to open a browser window. That way, they can _just_ click the provider's name. Internal ref - t/156255 --- **BEFORE**  **AFTER** 
This commit is contained in:
@ -87,16 +87,12 @@ export default class AccountController extends Controller {
|
|||||||
|
|
||||||
@discourseComputed("model.associated_accounts.[]")
|
@discourseComputed("model.associated_accounts.[]")
|
||||||
authProviders(accounts) {
|
authProviders(accounts) {
|
||||||
const allMethods = findAll();
|
return findAll()
|
||||||
|
.map((method) => ({
|
||||||
const result = allMethods.map((method) => {
|
|
||||||
return {
|
|
||||||
method,
|
method,
|
||||||
account: accounts.find((account) => account.name === method.name), // Will be undefined if no account
|
account: accounts.find(({ name }) => name === method.name),
|
||||||
};
|
}))
|
||||||
});
|
.filter((value) => value.account || value.method.can_connect);
|
||||||
|
|
||||||
return result.filter((value) => value.account || value.method.can_connect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
|
@ -168,7 +168,17 @@ export default RouteTemplate(
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="associated-account__name">
|
<div class="associated-account__name">
|
||||||
{{authProvider.method.prettyName}}
|
{{#if authProvider.method.provider_url}}
|
||||||
|
<a
|
||||||
|
href={{authProvider.method.provider_url}}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{authProvider.method.prettyName}}
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
{{authProvider.method.prettyName}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="associated-account__description">
|
<div class="associated-account__description">
|
||||||
{{authProvider.account.description}}
|
{{authProvider.account.description}}
|
||||||
@ -206,7 +216,17 @@ export default RouteTemplate(
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="associated-account__name">
|
<div class="associated-account__name">
|
||||||
{{authProvider.method.prettyName}}
|
{{#if authProvider.method.provider_url}}
|
||||||
|
<a
|
||||||
|
href={{authProvider.method.provider_url}}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{authProvider.method.prettyName}}
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
{{authProvider.method.prettyName}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="associated-account__description">
|
<div class="associated-account__description">
|
||||||
{{authProvider.account.description}}
|
{{authProvider.account.description}}
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AuthProviderSerializer < ApplicationSerializer
|
class AuthProviderSerializer < ApplicationSerializer
|
||||||
attributes :name,
|
attributes :can_connect,
|
||||||
:custom_url,
|
|
||||||
:pretty_name_override,
|
|
||||||
:title_override,
|
|
||||||
:frame_width,
|
|
||||||
:frame_height,
|
|
||||||
:can_connect,
|
|
||||||
:can_revoke,
|
:can_revoke,
|
||||||
:icon
|
:custom_url,
|
||||||
|
:frame_height,
|
||||||
|
:frame_width,
|
||||||
|
:icon,
|
||||||
|
:name,
|
||||||
|
:pretty_name_override,
|
||||||
|
:provider_url,
|
||||||
|
:title_override
|
||||||
|
|
||||||
def title_override
|
# ensures that the "/custom" route doesn't trigger the magic custom_url helper in ActionDispatch
|
||||||
return SiteSetting.get(object.title_setting) if object.title_setting
|
def custom_url
|
||||||
object.title
|
object.custom_url
|
||||||
end
|
end
|
||||||
|
|
||||||
def pretty_name_override
|
def pretty_name_override
|
||||||
return SiteSetting.get(object.pretty_name_setting) if object.pretty_name_setting
|
object.pretty_name_setting ? SiteSetting.get(object.pretty_name_setting) : object.pretty_name
|
||||||
object.pretty_name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_url
|
def title_override
|
||||||
# ensures that the "/custom" route doesn't trigger the magic custom_url helper in ActionDispatch
|
object.title_setting ? SiteSetting.get(object.title_setting) : object.title
|
||||||
object.custom_url
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,23 +10,19 @@ class Auth::AuthProvider
|
|||||||
def self.auth_attributes
|
def self.auth_attributes
|
||||||
%i[
|
%i[
|
||||||
authenticator
|
authenticator
|
||||||
pretty_name
|
|
||||||
title
|
|
||||||
frame_width
|
|
||||||
frame_height
|
|
||||||
pretty_name_setting
|
|
||||||
title_setting
|
|
||||||
custom_url
|
custom_url
|
||||||
|
frame_height
|
||||||
|
frame_width
|
||||||
icon
|
icon
|
||||||
|
pretty_name
|
||||||
|
pretty_name_setting
|
||||||
|
title
|
||||||
|
title_setting
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor(*auth_attributes)
|
attr_accessor(*auth_attributes)
|
||||||
|
|
||||||
def name
|
|
||||||
authenticator.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_connect
|
def can_connect
|
||||||
authenticator.can_connect_existing_user?
|
authenticator.can_connect_existing_user?
|
||||||
end
|
end
|
||||||
@ -34,4 +30,12 @@ class Auth::AuthProvider
|
|||||||
def can_revoke
|
def can_revoke
|
||||||
authenticator.can_revoke?
|
authenticator.can_revoke?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
authenticator.name
|
||||||
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
authenticator.provider_url
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -13,6 +13,11 @@ class Auth::Authenticator
|
|||||||
name
|
name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used in /my/preferences/account to link to the provider's website
|
||||||
|
def provider_url
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
@ -46,6 +46,10 @@ class Auth::DiscordAuthenticator < Auth::ManagedAuthenticator
|
|||||||
"Discord"
|
"Discord"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
"https://discord.com"
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_discord_logins?
|
SiteSetting.enable_discord_logins?
|
||||||
end
|
end
|
||||||
|
@ -33,6 +33,10 @@ class Auth::DiscourseIdAuthenticator < Auth::ManagedAuthenticator
|
|||||||
"Discourse ID"
|
"Discourse ID"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
site
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_discourse_id && SiteSetting.discourse_id_client_id.present? &&
|
SiteSetting.enable_discourse_id && SiteSetting.discourse_id_client_id.present? &&
|
||||||
SiteSetting.discourse_id_client_secret.present?
|
SiteSetting.discourse_id_client_secret.present?
|
||||||
|
@ -11,6 +11,10 @@ class Auth::FacebookAuthenticator < Auth::ManagedAuthenticator
|
|||||||
"Facebook"
|
"Facebook"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
"https://www.facebook.com"
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_facebook_logins
|
SiteSetting.enable_facebook_logins
|
||||||
end
|
end
|
||||||
|
@ -11,6 +11,10 @@ class Auth::GithubAuthenticator < Auth::ManagedAuthenticator
|
|||||||
"GitHub"
|
"GitHub"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
"https://github.com"
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_github_logins
|
SiteSetting.enable_github_logins
|
||||||
end
|
end
|
||||||
|
@ -14,6 +14,10 @@ class Auth::GoogleOAuth2Authenticator < Auth::ManagedAuthenticator
|
|||||||
"Google"
|
"Google"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
"https://accounts.google.com"
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_google_oauth2_logins
|
SiteSetting.enable_google_oauth2_logins
|
||||||
end
|
end
|
||||||
|
@ -49,6 +49,10 @@ class Auth::LinkedInOidcAuthenticator < Auth::ManagedAuthenticator
|
|||||||
"LinkedIn"
|
"LinkedIn"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
"https://www.linkedin.com"
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_linkedin_oidc_logins
|
SiteSetting.enable_linkedin_oidc_logins
|
||||||
end
|
end
|
||||||
|
@ -9,6 +9,10 @@ class Auth::TwitterAuthenticator < Auth::ManagedAuthenticator
|
|||||||
"X / Twitter"
|
"X / Twitter"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def provider_url
|
||||||
|
"https://x.com"
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?
|
def enabled?
|
||||||
SiteSetting.enable_twitter_logins
|
SiteSetting.enable_twitter_logins
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
describe "User preferences | Avatar", type: :system do
|
describe "User preferences | Account", type: :system do
|
||||||
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
||||||
let(:user_account_preferences_page) { PageObjects::Pages::UserPreferencesAccount.new }
|
let(:user_account_preferences_page) { PageObjects::Pages::UserPreferencesAccount.new }
|
||||||
let(:avatar_selector_modal) { PageObjects::Modals::AvatarSelector.new }
|
let(:avatar_selector_modal) { PageObjects::Modals::AvatarSelector.new }
|
||||||
|
|
||||||
before { sign_in(user) }
|
before { sign_in(user) }
|
||||||
|
|
||||||
describe "avatar-selector modal" do
|
describe "avatar-selector modal" do
|
||||||
@ -33,4 +34,64 @@ describe "User preferences | Avatar", type: :system do
|
|||||||
expect(avatar_selector_modal).to have_no_avatar_upload_button
|
expect(avatar_selector_modal).to have_no_avatar_upload_button
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "external login provider URLs" do
|
||||||
|
it "shows provider URLs as links when available" do
|
||||||
|
SiteSetting.enable_discord_logins = true
|
||||||
|
SiteSetting.enable_facebook_logins = true
|
||||||
|
SiteSetting.enable_github_logins = true
|
||||||
|
SiteSetting.enable_google_oauth2_logins = true
|
||||||
|
|
||||||
|
# Let's connect at least 1 external account
|
||||||
|
UserAssociatedAccount.create!(
|
||||||
|
user:,
|
||||||
|
provider_name: "google_oauth2",
|
||||||
|
provider_uid: "123456",
|
||||||
|
info: {
|
||||||
|
"email" => user.email,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
user_account_preferences_page.visit(user)
|
||||||
|
|
||||||
|
name = find(".pref-associated-accounts table tr.discord .associated-account__name")
|
||||||
|
expect(name).to have_link("Discord", href: "https://discord.com")
|
||||||
|
|
||||||
|
name = find(".pref-associated-accounts table tr.facebook .associated-account__name")
|
||||||
|
expect(name).to have_link("Facebook", href: "https://www.facebook.com")
|
||||||
|
|
||||||
|
name = find(".pref-associated-accounts table tr.github .associated-account__name")
|
||||||
|
expect(name).to have_link("GitHub", href: "https://github.com")
|
||||||
|
|
||||||
|
name = find(".pref-associated-accounts table tr.google-oauth2 .associated-account__name")
|
||||||
|
expect(name).to have_link("Google", href: "https://accounts.google.com")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows provider names without links when provider_url is not implemented" do
|
||||||
|
begin
|
||||||
|
authenticator =
|
||||||
|
Class
|
||||||
|
.new(Auth::ManagedAuthenticator) do
|
||||||
|
def name
|
||||||
|
"test_no_url"
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
.new
|
||||||
|
|
||||||
|
provider = Auth::AuthProvider.new(authenticator:, icon: "flash")
|
||||||
|
DiscoursePluginRegistry.register_auth_provider(provider)
|
||||||
|
|
||||||
|
user_account_preferences_page.visit(user)
|
||||||
|
|
||||||
|
name = find(".pref-associated-accounts table tr.test-no-url .associated-account__name")
|
||||||
|
expect(name).not_to have_css("a")
|
||||||
|
ensure
|
||||||
|
DiscoursePluginRegistry.reset!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user