diff --git a/README.md b/README.md index 08bf0df9de..79aa3045c0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Remark42 is a self-hosted, lightweight and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles, or any other place where readers add comments. -* Social login via Google, Facebook, Microsoft, GitHub, Apple, Yandex, Patreon and Telegram +* Social login via Google, Facebook, Microsoft, GitHub, Apple, Yandex, Patreon, Discord and Telegram * Login via email * Optional anonymous access * Multi-level nested comments with both tree and plain presentations diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 1f6cb0b644..3810c5ca0c 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -107,6 +107,7 @@ type ServerCommand struct { Yandex AuthGroup `group:"yandex" namespace:"yandex" env-namespace:"YANDEX" description:"Yandex OAuth"` Twitter AuthGroup `group:"twitter" namespace:"twitter" env-namespace:"TWITTER" description:"[deprecated, doesn't work] Twitter OAuth"` Patreon AuthGroup `group:"patreon" namespace:"patreon" env-namespace:"PATREON" description:"Patreon OAuth"` + Discord AuthGroup `group:"discord" namespace:"discord" env-namespace:"DISCORD" description:"Discord OAuth"` Telegram bool `long:"telegram" env:"TELEGRAM" description:"Enable Telegram auth (using token from telegram.token)"` Dev bool `long:"dev" env:"DEV" description:"enable dev (local) oauth2"` Anonymous bool `long:"anon" env:"ANON" description:"enable anonymous login"` @@ -321,6 +322,7 @@ func (s *ServerCommand) Execute(_ []string) error { "AUTH_TWITTER_CSEC", "AUTH_YANDEX_CSEC", "AUTH_PATREON_CSEC", + "AUTH_DISCORD_CSEC", "TELEGRAM_TOKEN", "SMTP_PASSWORD", "ADMIN_PASSWD", @@ -952,6 +954,10 @@ func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) error { authenticator.AddProvider("patreon", s.Auth.Patreon.CID, s.Auth.Patreon.CSEC) providersCount++ } + if s.Auth.Discord.CID != "" && s.Auth.Discord.CSEC != "" { + authenticator.AddProvider("discord", s.Auth.Discord.CID, s.Auth.Discord.CSEC) + providersCount++ + } if s.Auth.Dev { log.Print("[INFO] dev access enabled") diff --git a/backend/app/cmd/server_test.go b/backend/app/cmd/server_test.go index 9cd03c65de..7ff6ac0377 100644 --- a/backend/app/cmd/server_test.go +++ b/backend/app/cmd/server_test.go @@ -79,7 +79,7 @@ func TestServerApp_DevMode(t *testing.T) { waitForHTTPServerStart(port) providers := app.restSrv.Authenticator.Providers() - require.Equal(t, 10+1, len(providers), "extra auth provider") + require.Equal(t, 11+1, len(providers), "extra auth provider") assert.Equal(t, "dev", providers[len(providers)-2].Name(), "dev auth provider") // send ping resp, err := http.Get(fmt.Sprintf("http://localhost:%d/api/v1/ping", port)) @@ -807,6 +807,7 @@ func prepServerApp(t *testing.T, fn func(o ServerCommand) ServerCommand) (*serve cmd.Auth.Microsoft.CSEC, cmd.Auth.Microsoft.CID = "csec", "cid" cmd.Auth.Twitter.CSEC, cmd.Auth.Twitter.CID = "csec", "cid" cmd.Auth.Patreon.CSEC, cmd.Auth.Patreon.CID = "csec", "cid" + cmd.Auth.Discord.CSEC, cmd.Auth.Discord.CID = "csec", "cid" cmd.Auth.Telegram = true cmd.Telegram.Token = "token" cmd.Auth.Email.Enable = true diff --git a/backend/go.mod b/backend/go.mod index a40d7f54df..2e97a4e5fe 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 - github.com/go-pkgz/auth v1.24.2 + github.com/go-pkgz/auth v1.24.3-0.20241007090635-78537e6f812d github.com/go-pkgz/jrpc v0.3.0 github.com/go-pkgz/lcw/v2 v2.0.0 github.com/go-pkgz/lgr v0.11.1 diff --git a/backend/go.sum b/backend/go.sum index d054239c1a..9f2d6d6bfb 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -60,8 +60,8 @@ github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ= github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ= -github.com/go-pkgz/auth v1.24.2 h1:imMjUvTM0c8iOvP/GNGcuNcB/7gF3jFTF9dIPzlAOqI= -github.com/go-pkgz/auth v1.24.2/go.mod h1:xmnzq6g8mhemW1nHnkuByXkBXsHrNf9/qkiVwJugWIs= +github.com/go-pkgz/auth v1.24.3-0.20241007090635-78537e6f812d h1:6iwosbIwyRm7k0lprEv5mFWpGg1qQKLWJNHL088+Bcs= +github.com/go-pkgz/auth v1.24.3-0.20241007090635-78537e6f812d/go.mod h1:xmnzq6g8mhemW1nHnkuByXkBXsHrNf9/qkiVwJugWIs= github.com/go-pkgz/email v0.5.0 h1:fdtMDGJ8NwyBACLR0LYHaCIK/OeUwZHMhH7Q0+oty9U= github.com/go-pkgz/email v0.5.0/go.mod h1:BdxglsQnymzhfdbnncEE72a6DrucZHy6I+42LK2jLEc= github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs= diff --git a/backend/vendor/github.com/go-pkgz/auth/README.md b/backend/vendor/github.com/go-pkgz/auth/README.md index 2ddb757a34..9e48ae4ca8 100644 --- a/backend/vendor/github.com/go-pkgz/auth/README.md +++ b/backend/vendor/github.com/go-pkgz/auth/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 @@ -144,7 +144,7 @@ In addition to oauth2 providers `auth.Service` allows to use direct user-defined ```go service.AddDirectProvider("local", provider.CredCheckerFunc(func(user, password string) (ok bool, err error) { - ok, err := checkUserSomehow(user, password) + ok, err = checkUserSomehow(user, password) return ok, err })) ``` @@ -205,7 +205,7 @@ used as `Sender`. The API for this provider: - - `GET /auth//login?user=&address=&aud=&from=` - send confirmation request to user + - `GET /auth//login?user=&address=
&aud=&from=` - send confirmation request to user - `GET /auth//login?token=&sess=[1|0]` - authorize with confirmation token The provider acts like any other, i.e. will be registered as `/auth/email/login`. @@ -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/backend/vendor/github.com/go-pkgz/auth/auth.go b/backend/vendor/github.com/go-pkgz/auth/auth.go index 5bd9d87965..33366b2034 100644 --- a/backend/vendor/github.com/go-pkgz/auth/auth.go +++ b/backend/vendor/github.com/go-pkgz/auth/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/backend/vendor/github.com/go-pkgz/auth/provider/providers.go b/backend/vendor/github.com/go-pkgz/auth/provider/providers.go index 4487b4cb03..9711781f1e 100644 --- a/backend/vendor/github.com/go-pkgz/auth/provider/providers.go +++ b/backend/vendor/github.com/go-pkgz/auth/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/backend/vendor/modules.txt b/backend/vendor/modules.txt index 37bea2804d..48c71aee91 100644 --- a/backend/vendor/modules.txt +++ b/backend/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/go-chi/render github.com/go-oauth2/oauth2/v4 github.com/go-oauth2/oauth2/v4/errors github.com/go-oauth2/oauth2/v4/server -# github.com/go-pkgz/auth v1.24.2 +# github.com/go-pkgz/auth v1.24.3-0.20241007090635-78537e6f812d ## explicit; go 1.21 github.com/go-pkgz/auth github.com/go-pkgz/auth/avatar diff --git a/compose-dev-backend.yml b/compose-dev-backend.yml index 108f90e8b1..eaa2a1cc96 100644 --- a/compose-dev-backend.yml +++ b/compose-dev-backend.yml @@ -68,5 +68,7 @@ services: - AUTH_FACEBOOK_CSEC=1111 - AUTH_PATREON_CID=1111 - AUTH_PATREON_CSEC=1111 + - AUTH_DISCORD_CID=1111 + - AUTH_DISCORD_CSEC=1111 volumes: - ./var:/srv/var diff --git a/frontend/apps/remark42/app/assets/social/discord.svg b/frontend/apps/remark42/app/assets/social/discord.svg new file mode 100644 index 0000000000..bb6324e481 --- /dev/null +++ b/frontend/apps/remark42/app/assets/social/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/apps/remark42/app/common/types.ts b/frontend/apps/remark42/app/common/types.ts index 173cf17e92..fd18f8ebc3 100644 --- a/frontend/apps/remark42/app/common/types.ts +++ b/frontend/apps/remark42/app/common/types.ts @@ -99,6 +99,7 @@ export type OAuthProvider = | 'github' | 'microsoft' | 'patreon' + | 'discord' | 'telegram' | 'dev'; export type FormProvider = 'email' | 'anonymous'; diff --git a/frontend/apps/remark42/app/components/auth/components/oauth.consts.ts b/frontend/apps/remark42/app/components/auth/components/oauth.consts.ts index 61e217a12c..d7d86d7f0a 100644 --- a/frontend/apps/remark42/app/components/auth/components/oauth.consts.ts +++ b/frontend/apps/remark42/app/components/auth/components/oauth.consts.ts @@ -9,6 +9,7 @@ export const OAUTH_DATA = { facebook: require('assets/social/facebook.svg').default as string, twitter: require('assets/social/twitter.svg').default as string, patreon: require('assets/social/patreon.svg').default as string, + discord: require('assets/social/discord.svg').default as string, google: require('assets/social/google.svg').default as string, microsoft: require('assets/social/microsoft.svg').default as string, yandex: require('assets/social/yandex.svg').default as string, diff --git a/frontend/packages/api/clients/index.ts b/frontend/packages/api/clients/index.ts index 3b648e3168..a262d9ecdd 100644 --- a/frontend/packages/api/clients/index.ts +++ b/frontend/packages/api/clients/index.ts @@ -25,6 +25,7 @@ export type OAuthProvider = | 'github' | 'microsoft' | 'patreon' + | 'discord' | 'telegram' | 'dev' export type FormProvider = 'email' | 'anonymous' diff --git a/site/src/docs/configuration/authorization/index.md b/site/src/docs/configuration/authorization/index.md index 541bdccafa..244019b6db 100644 --- a/site/src/docs/configuration/authorization/index.md +++ b/site/src/docs/configuration/authorization/index.md @@ -105,6 +105,13 @@ For more details refer to [Yandex OAuth](https://yandex.com/dev/oauth/doc/dg/con 3. In the field **Redirect URIs** enter the correct URI constructed as domain + `/auth/patreon/callback`, i.e., `https://example.mysite.com/auth/patreon/callback` 4. Expand client details and note the **Client ID** and **Client Secret**. Those will be used as `AUTH_PATREON_CID` and `AUTH_PATREON_CSEC` +### Discord Auth Provider + +1. Click on **New Application** to create Oauth client https://discord.com/developers/applications +2. After filling **"NAME"**, navigate to **"OAuth2"** option on the left sidebar +3. Under **"Redirects"** enter the correct url constructed as domain + `/auth/discord/callback`. ie `https://remark42.mysite.com/auth/discord/callback` +4. Take note of the **CLIENT ID** and **CLIENT SECRET**, as they are values for `AUTH_DISCORD_CID` and `AUTH_DISCORD_CSEC` respectively + ### Telegram 1. Contact [@BotFather](https://t.me/botfather) and follow his instructions to create your bot (call it, for example, "My site auth bot") diff --git a/site/src/docs/configuration/parameters/index.md b/site/src/docs/configuration/parameters/index.md index 3b915df74b..af4e34a364 100644 --- a/site/src/docs/configuration/parameters/index.md +++ b/site/src/docs/configuration/parameters/index.md @@ -95,6 +95,8 @@ services: | auth.github.csec | AUTH_GITHUB_CSEC | | GitHub OAuth client secret | | auth.patreon.cid | AUTH_PATREON_CID | | Patreon OAuth Client ID | | auth.patreon.csec | AUTH_PATREON_CSEC | | Patreon OAuth Client Secret | +| auth.discord.cid | AUTH_DISCORD_CID | | Discord OAuth Client ID | +| auth.discord.csec | AUTH_DISCORD_CSEC | | Discord OAuth Client Secret | | auth.telegram | AUTH_TELEGRAM | `false` | Enable Telegram auth (telegram.token must be present) | | auth.yandex.cid | AUTH_YANDEX_CID | | Yandex OAuth client ID | | auth.yandex.csec | AUTH_YANDEX_CSEC | | Yandex OAuth client secret |