FEATURE: Login with Discord (#8053)

This migrates the functionality of discourse-plugin-discord-auth into core. 

The plugin will automatically disable itself when core is updated: fd0867844d?w=1

For setup instructions, visit https://meta.discourse.org/t/configuring-discord-login-for-discourse/127129
This commit is contained in:
David Taylor 2019-08-30 10:54:19 +01:00 committed by GitHub
parent ac7d68a745
commit be96c4478e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 166 additions and 1 deletions

View File

@ -231,6 +231,12 @@
background: lighten($github, 20%);
}
}
&.discord {
background: $discord;
&:hover {
background: darken($discord, 10%);
}
}
}
// Button Sizes

View File

@ -23,6 +23,7 @@ $facebook: #4267b2 !default;
$cas: #70ba61 !default;
$twitter: #1da1f2 !default;
$github: #100e0f !default;
$discord: #7289da !default;
// Badge color variables
// --------------------------------------------------

View File

@ -1469,6 +1469,10 @@ en:
name: "GitHub"
title: "with GitHub"
message: "Authenticating with GitHub (make sure pop up blockers are not enabled)"
discord:
name: "Discord"
title: "with Discord"
message: "Authenticating with Discord"
invites:
accept_title: "Invitation"
welcome_to: "Welcome to %{site_name}!"

View File

@ -1551,6 +1551,11 @@ en:
github_client_id: "Client id for Github authentication, registered at <a href='https://github.com/settings/developers/' target='_blank'>https://github.com/settings/developers</a>"
github_client_secret: "Client secret for Github authentication, registered at <a href='https://github.com/settings/developers/' target='_blank'>https://github.com/settings/developers</a>"
enable_discord_logins: 'Allow users to authenticate using Discord?'
discord_client_id: 'Discord Client ID (need one? visit <a href="https://discordapp.com/developers/applications/me">the Discord developer portal</a>)'
discord_secret: 'Discord Secret Key'
discord_trusted_guilds: 'Only allow members of these Discord guilds to login via Discord. Use the numeric ID for the guild. For more information, check the instructions <a href="https://meta.discourse.org/t/configuring-discord-login-for-discourse/127129">here</a>. Leave blank to allow any guild.'
readonly_mode_during_backup: "Enable read only mode while taking a backup"
enable_backups: "Allow administrators to create backups of the forum"
allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to restore a backup"
@ -4569,3 +4574,6 @@ en:
email_style:
html_missing_placeholder: "The html template must include %{placeholder}"
discord:
not_in_allowed_guild: 'Authentication failed. You are not a member of a permitted Discord guild.'

View File

@ -390,6 +390,16 @@ login:
default: ""
regex: "^[a-f0-9]+$"
secret: true
enable_discord_logins:
default: false
discord_client_id:
default: ''
discord_secret:
default: ''
secret: true
discord_trusted_guilds:
default: ''
type: list
enable_sso:
client: true
default: false

View File

@ -12,3 +12,4 @@ require_dependency 'auth/github_authenticator'
require_dependency 'auth/twitter_authenticator'
require_dependency 'auth/google_oauth2_authenticator'
require_dependency 'auth/instagram_authenticator'
require_dependency 'auth/discord_authenticator'

View File

@ -0,0 +1,73 @@
# 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://discordapp.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['username'],
email: raw_info['verified'] ? raw_info['email'] : nil,
image: "https://cdn.discordapp.com/avatars/#{raw_info['id']}/#{raw_info['avatar']}"
}
end
extra do
{
'raw_info' => raw_info
}
end
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 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
end

View File

@ -254,7 +254,8 @@ module Discourse
Auth::AuthProvider.new(authenticator: Auth::GoogleOAuth2Authenticator.new, frame_width: 850, frame_height: 500), # Custom icon implemented in client
Auth::AuthProvider.new(authenticator: Auth::GithubAuthenticator.new, icon: "fab-github"),
Auth::AuthProvider.new(authenticator: Auth::TwitterAuthenticator.new, icon: "fab-twitter"),
Auth::AuthProvider.new(authenticator: Auth::InstagramAuthenticator.new, icon: "fab-instagram")
Auth::AuthProvider.new(authenticator: Auth::InstagramAuthenticator.new, icon: "fab-instagram"),
Auth::AuthProvider.new(authenticator: Auth::DiscordAuthenticator.new, icon: "fab-discord", full_screen_login: true)
]
def self.auth_providers

View File

@ -71,6 +71,7 @@ module SvgSprite
"fab-android",
"fab-apple",
"fab-chrome",
"fab-discord",
"fab-discourse",
"fab-facebook-square",
"fab-facebook",

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'rails_helper'
describe Auth::DiscordAuthenticator do
let(:hash) {
OmniAuth::AuthHash.new(
provider: "facebook",
extra: {
raw_info: {
id: "100",
username: "bobbob",
guilds: [
{
"id": "80351110224678912",
"name": "1337 Krew",
"icon": "8342729096ea3675442027381ff50dfe",
"owner": true,
"permissions": 36953089
}
]
}
},
info: {
email: "bob@bob.com",
name: "bobbob"
},
uid: "100"
)
}
let(:authenticator) { described_class.new }
context 'after_authenticate' do
it 'works normally' do
result = authenticator.after_authenticate(hash)
expect(result.user).to eq(nil)
expect(result.failed).to eq(false)
expect(result.name).to eq("bobbob")
expect(result.email).to eq("bob@bob.com")
end
it 'denies access when guilds are restricted' do
SiteSetting.discord_trusted_guilds = ["someguildid", "someotherguildid"].join("|")
result = authenticator.after_authenticate(hash)
expect(result.user).to eq(nil)
expect(result.failed).to eq(true)
expect(result.failed_reason).to eq(I18n.t("discord.not_in_allowed_guild"))
end
it 'allows access when in an allowed guild' do
SiteSetting.discord_trusted_guilds = ["80351110224678912", "anothertrustedguild"].join("|")
result = authenticator.after_authenticate(hash)
expect(result.user).to eq(nil)
expect(result.failed).to eq(false)
expect(result.name).to eq("bobbob")
expect(result.email).to eq("bob@bob.com")
end
end
end