From 78537e6f812df08d95ae8a5f9c049dfb59247b2e Mon Sep 17 00:00:00 2001 From: Mrigank Badola <43956098+mgkbadola@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:36:35 +0530 Subject: [PATCH] feature: Discord OAuth2 Provider (#223) * feature: add discord integration * fix: linting issue on NewDiscord * fixes: add provider to v2, remove email scope and cleanup indent in README * fix: comment on token validator * fix: changes involving email removal in test script * fix: changes involving email removal in test script (v1) --------- Co-authored-by: badolamgk --- README.md | 9 ++++++++- _example/frontend/index.html | 4 ++-- _example/main.go | 6 +++++- auth.go | 2 ++ auth_test.go | 3 ++- provider/providers.go | 26 ++++++++++++++++++++++++++ provider/providers_test.go | 13 +++++++++++++ v2/auth.go | 2 ++ v2/auth_test.go | 3 ++- v2/provider/providers.go | 26 ++++++++++++++++++++++++++ v2/provider/providers_test.go | 13 +++++++++++++ 11 files changed, 101 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d273ddb1..9e48ae4c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # auth - authentication via oauth2, direct and email [![Build Status](https://github.com/go-pkgz/auth/workflows/build/badge.svg)](https://github.com/go-pkgz/auth/actions) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/auth/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/auth?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/auth?status.svg)](https://pkg.go.dev/github.com/go-pkgz/auth?tab=doc) -This library provides "social login" with Github, Google, Facebook, Microsoft, Twitter, Yandex, Battle.net, Apple, Patreon and Telegram as well as custom auth providers and email verification. +This library provides "social login" with Github, Google, Facebook, Microsoft, Twitter, Yandex, Battle.net, Apple, Patreon, Discord and Telegram as well as custom auth providers and email verification. - Multiple oauth2 providers can be used at the same time - Special `dev` provider allows local testing and development @@ -604,6 +604,13 @@ For more details refer to [Complete Guide of Battle.net OAuth API and Login Butt 1. Under **"Redirect URIs"** enter the correct url constructed as domain + `/auth/patreon/callback`. ie `https://example.mysite.com/auth/patreon/callback` 1. Take note of the **Client ID** and **Client Secret** +#### Discord Auth Provider #### +1. Log into Discord Developer Portal https://discord.com/developers/applications +2. Click on **New Application** to create the application required for Oauth +3. After filling **"NAME"**, navigate to **"OAuth2"** option on the left sidebar +4. Under **"Redirects"** enter the correct url constructed as domain + `/auth/discord/callback`. ie `https://remark42.mysite.com/auth/discord/callback` +5. Take note of the **CLIENT ID** and **CLIENT SECRET** + #### Twitter Auth Provider 1. Create a new twitter application https://developer.twitter.com/en/apps 1. Fill **App name** and **Description** and **URL** of your site diff --git a/_example/frontend/index.html b/_example/frontend/index.html index e51fc961..128ecb35 100644 --- a/_example/frontend/index.html +++ b/_example/frontend/index.html @@ -10,7 +10,7 @@

GO-PKGZ/AUTHExample page

-

This library provides “social login” with Github, Google, Microsoft, Facebook, Yandex, Battle.net, Patreon and Telegram.

+

This library provides “social login” with Github, Google, Microsoft, Facebook, Yandex, Battle.net, Patreon, Discord and Telegram.

Status: unauthorized
@@ -25,4 +25,4 @@

GO-PKGZ/AUTHgo-pkgz/auth - \ No newline at end of file + diff --git a/_example/main.go b/_example/main.go index 3dd315c8..dd9c5857 100644 --- a/_example/main.go +++ b/_example/main.go @@ -63,7 +63,10 @@ func main() { if strings.HasPrefix(claims.User.ID, "microsoft_") { // allow all users with ms auth return true } - if strings.HasPrefix(claims.User.ID, "patreon_") { // allow all users with ms auth + if strings.HasPrefix(claims.User.ID, "patreon_") { // allow all users with patreon auth + return true + } + if strings.HasPrefix(claims.User.ID, "discord_") { // allow all users with discord auth return true } if strings.HasPrefix(claims.User.Name, "dev_") { // non-guthub allow only dev_* names @@ -84,6 +87,7 @@ func main() { service.AddProvider("twitter", os.Getenv("AEXMPL_TWITTER_APIKEY"), os.Getenv("AEXMPL_TWITTER_APISEC")) service.AddProvider("microsoft", os.Getenv("AEXMPL_MS_APIKEY"), os.Getenv("AEXMPL_MS_APISEC")) service.AddProvider("patreon", os.Getenv("AEXMPL_PATREON_CID"), os.Getenv("AEXMPL_PATREON_CSEC")) + service.AddProvider("discord", os.Getenv("AEXMPL_DISCORD_CID"), os.Getenv("AEXMPL_DISCORD_CSEC")) // allow sign with apple id appleCfg := provider.AppleConfig{ diff --git a/auth.go b/auth.go index 5bd9d879..33366b20 100644 --- a/auth.go +++ b/auth.go @@ -257,6 +257,8 @@ func (s *Service) addProviderByName(name string, p provider.Params) { prov = provider.NewTwitter(p) case "patreon": prov = provider.NewPatreon(p) + case "discord": + prov = provider.NewDiscord(p) case "dev": prov = provider.NewDev(p) default: diff --git a/auth_test.go b/auth_test.go index 9f9fbece..3be93721 100644 --- a/auth_test.go +++ b/auth_test.go @@ -61,6 +61,7 @@ func TestProvider(t *testing.T) { svc.AddProvider("twitter", "cid", "csecret") svc.AddProvider("battlenet", "cid", "csecret") svc.AddProvider("patreon", "cid", "csecret") + svc.AddProvider("discord", "cid", "csecret") svc.AddProvider("bad", "cid", "csecret") c := customHandler{} @@ -81,7 +82,7 @@ func TestProvider(t *testing.T) { assert.Equal(t, "github", op.Name()) pp := svc.Providers() - assert.Equal(t, 10, len(pp)) + assert.Equal(t, 11, len(pp)) ch, err := svc.Provider("telegramBotMySiteCom") assert.NoError(t, err) diff --git a/provider/providers.go b/provider/providers.go index 4487b4cb..9711781f 100644 --- a/provider/providers.go +++ b/provider/providers.go @@ -265,3 +265,29 @@ func NewPatreon(p Params) Oauth2Handler { }, }) } + +// NewDiscord makes discord oauth2 provider +func NewDiscord(p Params) Oauth2Handler { + return initOauth2Handler(p, Oauth2Handler{ + name: "discord", + // see https://discord.com/developers/docs/topics/oauth2 + endpoint: oauth2.Endpoint{ + AuthURL: "https://discord.com/oauth2/authorize", + TokenURL: "https://discord.com/api/oauth2/token", + }, + infoURL: "https://discord.com/api/v10/users/@me", + scopes: []string{"identify"}, + mapUser: func(data UserData, _ []byte) token.User { + userInfo := token.User{ + ID: "discord_" + token.HashID(sha1.New(), data.Value("id")), + Name: data.Value("username"), + Picture: fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.webp", data.Value("id"), data.Value("avatar")), + } + + for k, v := range p.UserAttributes { + userInfo.SetStrAttr(v, data.Value(k)) + } + return userInfo + }, + }) +} diff --git a/provider/providers_test.go b/provider/providers_test.go index 88ea28ce..8c2a5a48 100644 --- a/provider/providers_test.go +++ b/provider/providers_test.go @@ -208,3 +208,16 @@ func TestProviders_NewPatreon(t *testing.T) { user, ) } + +func TestProviders_NewDiscord(t *testing.T) { + r := NewDiscord(Params{URL: "http://demo.remark42.com", Cid: "cid", Csecret: "cs"}) + assert.Equal(t, "discord", r.Name()) + + t.Run("With all data", func(t *testing.T) { + udata := UserData{"id": "248533295981532", "username": "test_user", "avatar": "374384984773"} + user := r.mapUser(udata, nil) + assert.Equal(t, token.User{Name: "test_user", ID: "discord_9b472605c1318483fb4b88f9acf22cdd4219f9a0", + Picture: "https://cdn.discordapp.com/avatars/248533295981532/374384984773.webp"}, user, "got %+v", user) + }) + +} diff --git a/v2/auth.go b/v2/auth.go index 30d4a613..9077b2d5 100644 --- a/v2/auth.go +++ b/v2/auth.go @@ -257,6 +257,8 @@ func (s *Service) addProviderByName(name string, p provider.Params) { prov = provider.NewTwitter(p) case "patreon": prov = provider.NewPatreon(p) + case "discord": + prov = provider.NewDiscord(p) case "dev": prov = provider.NewDev(p) default: diff --git a/v2/auth_test.go b/v2/auth_test.go index 0031d55e..3e5ddbbe 100644 --- a/v2/auth_test.go +++ b/v2/auth_test.go @@ -61,6 +61,7 @@ func TestProvider(t *testing.T) { svc.AddProvider("twitter", "cid", "csecret") svc.AddProvider("battlenet", "cid", "csecret") svc.AddProvider("patreon", "cid", "csecret") + svc.AddProvider("discord", "cid", "csecret") svc.AddProvider("bad", "cid", "csecret") c := customHandler{} @@ -81,7 +82,7 @@ func TestProvider(t *testing.T) { assert.Equal(t, "github", op.Name()) pp := svc.Providers() - assert.Equal(t, 10, len(pp)) + assert.Equal(t, 11, len(pp)) ch, err := svc.Provider("telegramBotMySiteCom") assert.NoError(t, err) diff --git a/v2/provider/providers.go b/v2/provider/providers.go index 5f8758e0..471fa312 100644 --- a/v2/provider/providers.go +++ b/v2/provider/providers.go @@ -265,3 +265,29 @@ func NewPatreon(p Params) Oauth2Handler { }, }) } + +// NewDiscord makes discord oauth2 provider +func NewDiscord(p Params) Oauth2Handler { + return initOauth2Handler(p, Oauth2Handler{ + name: "discord", + // see https://discord.com/developers/docs/topics/oauth2 + endpoint: oauth2.Endpoint{ + AuthURL: "https://discord.com/oauth2/authorize", + TokenURL: "https://discord.com/api/oauth2/token", + }, + infoURL: "https://discord.com/api/v10/users/@me", + scopes: []string{"identify"}, + mapUser: func(data UserData, _ []byte) token.User { + userInfo := token.User{ + ID: "discord_" + token.HashID(sha1.New(), data.Value("id")), + Name: data.Value("username"), + Picture: fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.webp", data.Value("id"), data.Value("avatar")), + } + + for k, v := range p.UserAttributes { + userInfo.SetStrAttr(v, data.Value(k)) + } + return userInfo + }, + }) +} diff --git a/v2/provider/providers_test.go b/v2/provider/providers_test.go index 7d3b8fed..f3b91a8a 100644 --- a/v2/provider/providers_test.go +++ b/v2/provider/providers_test.go @@ -208,3 +208,16 @@ func TestProviders_NewPatreon(t *testing.T) { user, ) } + +func TestProviders_NewDiscord(t *testing.T) { + r := NewDiscord(Params{URL: "http://demo.remark42.com", Cid: "cid", Csecret: "cs"}) + assert.Equal(t, "discord", r.Name()) + + t.Run("With all data", func(t *testing.T) { + udata := UserData{"id": "248533295981532", "username": "test_user", "avatar": "374384984773"} + user := r.mapUser(udata, nil) + assert.Equal(t, token.User{Name: "test_user", ID: "discord_9b472605c1318483fb4b88f9acf22cdd4219f9a0", + Picture: "https://cdn.discordapp.com/avatars/248533295981532/374384984773.webp"}, user, "got %+v", user) + }) + +}