Files
discourse/lib/auth/discord_authenticator.rb
Régis Hanol 0de08e780b UX: better error message when social login fails (#32772)
This improves the error message(s) displayed when an error happens while
using a social login to log in / sign up into a Discourse community.

Unfortunately, we can't be super precise in the reason behind the error
(it can be the user clicked "cancel" during the authorization phase, or
any of the plethora of other possible errors) because the reason isn't
provided (either for security reasons, or because it's just hard to do
so in a reliable & consistent way accross all social logins).

The best I could do was to

- add the name of the "social login" provider in the error message
- tweak the copy a bit for the different cases handle
- fix the CSS/HTML to match the one used by the main application

In order to show the name of the provider, we use either the "provider"
or the "strategy" query parameter, or use the name of the authenticator
(if it's the only one enabled).

Internal ref - t/153662

---

**BEFORE**


![before](https://github.com/user-attachments/assets/67579a3e-5a61-4e8f-aa72-4dc375547e39)

**AFTER**


![after](https://github.com/user-attachments/assets/954f9c8a-e865-44ff-b1e3-841393a26edf)
2025-05-20 16:22:38 +02:00

86 lines
2.3 KiB
Ruby

# frozen_string_literal: true
class Auth::DiscordAuthenticator < Auth::ManagedAuthenticator
class DiscordStrategy < OmniAuth::Strategies::OAuth2
option :name, "discord"
option :scope, "identify email guilds"
option :client_options,
site: "https://discord.com/api",
authorize_url: "oauth2/authorize",
token_url: "oauth2/token"
option :authorize_options, %i[scope permissions]
uid { raw_info["id"] }
info do
{
name: raw_info["global_name"] || raw_info["username"],
nickname: raw_info["username"],
email: raw_info["verified"] ? raw_info["email"] : nil,
image: "https://cdn.discordapp.com/avatars/#{raw_info["id"]}/#{raw_info["avatar"]}",
}
end
extra { { "raw_info" => raw_info } }
def raw_info
@raw_info ||=
access_token
.get("users/@me")
.parsed
.merge(guilds: access_token.get("users/@me/guilds").parsed)
end
def callback_url
full_host + script_name + callback_path
end
end
def name
"discord"
end
def display_name
"Discord"
end
def enabled?
SiteSetting.enable_discord_logins?
end
def register_middleware(omniauth)
omniauth.provider DiscordStrategy,
setup:
lambda { |env|
strategy = env["omniauth.strategy"]
strategy.options[:client_id] = SiteSetting.discord_client_id
strategy.options[:client_secret] = SiteSetting.discord_secret
}
end
def after_authenticate(auth_token, existing_account: nil)
allowed_guild_ids = SiteSetting.discord_trusted_guilds.split("|")
if allowed_guild_ids.length > 0
user_guild_ids = auth_token.extra[:raw_info][:guilds].map { |g| g["id"] }
if (user_guild_ids & allowed_guild_ids).empty? # User is not in any allowed guilds
return(
Auth::Result.new.tap do |auth_result|
auth_result.failed = true
auth_result.failed_reason = I18n.t("discord.not_in_allowed_guild")
end
)
end
end
super
end
# the `info` block above only picks the email from Discord API if it's verified
def primary_email_verified?(auth_token)
true
end
end