From d1fdddd14947fa723c3d79c34278a8c05d115025 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Fri, 9 Aug 2024 23:08:13 +0200 Subject: [PATCH 1/2] twitter oauth --- app/app.go | 3 +++ app/config.go | 15 +++++++++++++++ docs/api.md | 1 + docs/config.md | 28 +++++++++++++++++++--------- lib/oauth/twitter.go | 40 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 lib/oauth/twitter.go diff --git a/app/app.go b/app/app.go index 77333c6d6..56b660328 100644 --- a/app/app.go +++ b/app/app.go @@ -132,6 +132,9 @@ func initializeOAuthProviders(cfg *Config) (map[string]oauth.Provider, error) { if cfg.FacebookOauthCredentials != nil { oauthProviders["facebook"] = *oauth.NewFacebookProvider(cfg.FacebookOauthCredentials) } + if cfg.TwitterOauthCredentials != nil { + oauthProviders["twitter"] = *oauth.NewTwitterProvider(cfg.TwitterOauthCredentials) + } if cfg.DiscordOauthCredentials != nil { oauthProviders["discord"] = *oauth.NewDiscordProvider(cfg.DiscordOauthCredentials) } diff --git a/app/config.go b/app/config.go index 50fe9de1d..f2ceb938e 100644 --- a/app/config.go +++ b/app/config.go @@ -74,6 +74,7 @@ type Config struct { GoogleOauthCredentials *oauth.Credentials GitHubOauthCredentials *oauth.Credentials FacebookOauthCredentials *oauth.Credentials + TwitterOauthCredentials *oauth.Credentials DiscordOauthCredentials *oauth.Credentials MicrosoftOauthCredentials *oauth.Credentials AppleOAuthCredentials *oauth.Credentials @@ -85,6 +86,7 @@ func (c *Config) OAuthEnabled() bool { return c.GoogleOauthCredentials != nil || c.GitHubOauthCredentials != nil || c.FacebookOauthCredentials != nil || + c.TwitterOauthCredentials != nil || c.DiscordOauthCredentials != nil || c.MicrosoftOauthCredentials != nil || c.AppleOAuthCredentials != nil @@ -626,6 +628,19 @@ var configurers = []configurer{ return nil }, + // TWITTER_OAUTH_CREDENTIALS is a credential pair in the format `id:secret`. When specified, + // AuthN will enable routes for Twitter OAuth signin. + func(c *Config) error { + if val, ok := os.LookupEnv("TWITTER_OAUTH_CREDENTIALS"); ok { + credentials, err := oauth.NewCredentials(val) + if err == nil { + c.TwitterOauthCredentials = credentials + } + return err + } + return nil + }, + // DISCORD_OAUTH_CREDENTIALS is a credential pair in the format `id:secret`. When specified, // AuthN will enable routes for Discord OAuth signin. func(c *Config) error { diff --git a/docs/api.md b/docs/api.md index a85884651..ae209f44f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -674,6 +674,7 @@ Visibility: Public * google * github * facebook | +* twitter | This is the return URL that must be registered with a provider when provisioning credentials. From here, a user will proceed to the `redirect_uri` specified at the [Begin OAuth](#begin-oauth) step. diff --git a/docs/config.md b/docs/config.md index 272cfcde9..9847c6ca6 100644 --- a/docs/config.md +++ b/docs/config.md @@ -4,7 +4,7 @@ * Databases: [`DATABASE_URL`](#database_url) • [`REDIS_URL`](#redis_url) • [`REDIS_IS_SENTINEL_MODE`](#redis_is_sentinel_mode) • [`REDIS_SENTINEL_MASTER`](#redis_sentinel_master) • [`REDIS_SENTINEL_NODES`](#redis_sentinel_nodes) • [`REDIS_SENTINEL_PASSWORD`](#redis_sentinel_password) * Sessions: [`ACCESS_TOKEN_TTL`](#access_token_ttl) • [`REFRESH_TOKEN_TTL`](#refresh_token_ttl)• [`REFRESH_TOKEN_EXPLICIT_EXPIRY`](#refresh_token_explicit_expiry) • [`SESSION_KEY_SALT`](#session_key_salt) • [`DB_ENCRYPTION_KEY_SALT`](#db_encryption_key_salt) • [`RSA_PRIVATE_KEY`](#rsa_private_key) • [`SAME_SITE`](#same_site) -* OAuth Clients: [`FACEBOOK_OAUTH_CREDENTIALS`](#facebook_oauth_credentials) • [`GITHUB_OAUTH_CREDENTIALS`](#github_oauth_credentials) • [`GOOGLE_OAUTH_CREDENTIALS`](#google_oauth_credentials) • [`DISCORD_OAUTH_CREDENTIALS`](#discord_oauth_credentials) • [`MICROSOFT_OAUTH_CREDENTIALS`](#microsoft_oauth_credentials) +* OAuth Clients: [`FACEBOOK_OAUTH_CREDENTIALS`](#facebook_oauth_credentials) • [`TWITTER_OAUTH_CREDENTIALS`](#twitter_oauth_credentials) • [`GITHUB_OAUTH_CREDENTIALS`](#github_oauth_credentials) • [`GOOGLE_OAUTH_CREDENTIALS`](#google_oauth_credentials) • [`DISCORD_OAUTH_CREDENTIALS`](#discord_oauth_credentials) • [`MICROSOFT_OAUTH_CREDENTIALS`](#microsoft_oauth_credentials) * Username Policy: [`USERNAME_IS_EMAIL`](#username_is_email) • [`EMAIL_USERNAME_DOMAINS`](#email_username_domains) * Password Policy: [`PASSWORD_POLICY_SCORE`](#password_policy_score) • [`PASSWORD_CHANGE_LOGOUT`](#password_change_logout) • [`BCRYPT_COST`](#bcrypt_cost) * Password Resets: [`APP_PASSWORD_RESET_URL`](#app_password_reset_url) • [`PASSWORD_RESET_TOKEN_TTL`](#password_reset_token_ttl) • [`APP_PASSWORD_CHANGED_URL`](#app_password_changed_url) @@ -86,7 +86,6 @@ This value is commonly a 64-byte string, and can be generated with [`SecureRando May be set to a falsy value to disable the signup endpoint. If signup is disabled, all users must be created via the private [Import Account endpoint](api.md#import-account). - ## Databases ### `DATABASE_URL` @@ -190,7 +189,6 @@ This setting controls how frequently a refresh token must be used to keep a sess This setting controls cookie expiration behavior for refresh tokens. The cookie will be written without any expiration / max age and treated by browsers as a session cookie by default. If set to true, the cookie will be written as a persistent cookie with explicit expiration based on [`REFRESH_TOKEN_TTL`](#refresh_token_ttl). - ### `SESSION_KEY_SALT` | | | @@ -274,7 +272,7 @@ Additional credentialing data can be passed to the apple provider as key-value p `APPLE_OAUTH_CREDENTIALS=appID:appSecret:key1=val1:key2=val2` Note that the client secret for apple is **NOT** a static value as for other providers. The secret sent is a JWT constructed using the additional data keyID, teamID and expirySeconds included with credentials. -The configured client secret is a private key used to sign the JWT. This should be configured with a hex encoded representation of the full PEM block of a private key obtained at https://developer.apple.com. +The configured client secret is a private key used to sign the JWT. This should be configured with a hex encoded representation of the full PEM block of a private key obtained at . ### `FACEBOOK_OAUTH_CREDENTIALS` @@ -284,7 +282,19 @@ The configured client secret is a private key used to sign the JWT. This should | Value | AppID:AppSecret | | Default | nil | -Create a Facebook app at https://developers.facebook.com and enable the Facebook Login product. In the Quickstart, enter [AuthN's OAuth Return](api.md#oauth-return) as the Site URL. Then switch over to Settings and find the App ID and Secret. Join those together with a `:` and provide them to AuthN as a single variable. +Create a Facebook app at and enable the Facebook Login product. In the Quickstart, enter [AuthN's OAuth Return](api.md#oauth-return) as the Site URL. Then switch over to Settings and find the App ID and Secret. Join those together with a `:` and provide them to AuthN as a single variable. + +### `TWITTER_OAUTH_CREDENTIALS` + +| | | +|-----------|-----------------| +| Required? | No | +| Value | AppID:AppSecret | +| Default | nil | + +Log in and see your apps in the [X developer portal](https://developer.x.com/en/portal/projects-and-apps). Go to app settings and set up user authentication. Enter [AuthN's OAuth Return](api.md#oauth-return) as the redirect URL. Save `Client ID` and `Client Secret`. Join those together with a `:` and provide them to AuthN as a single variable. + + ### `GITHUB_OAUTH_CREDENTIALS` @@ -294,7 +304,7 @@ Create a Facebook app at https://developers.facebook.com and enable the Facebook | Value | ClientID:ClientSecret | | Default | nil | -Sign up for GitHub OAuth 2.0 credentials with the instructions here: https://developer.github.com/apps/building-oauth-apps. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for GitHub OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ### `GOOGLE_OAUTH_CREDENTIALS` @@ -304,7 +314,7 @@ Sign up for GitHub OAuth 2.0 credentials with the instructions here: https://dev | Value | ClientID:ClientSecret | | Default | nil | -Sign up for Google OAuth 2.0 credentials with the instructions here: https://developers.google.com/identity/protocols/OpenIDConnect. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for Google OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ### `DISCORD_OAUTH_CREDENTIALS` @@ -314,7 +324,7 @@ Sign up for Google OAuth 2.0 credentials with the instructions here: https://dev | Value | ClientID:ClientSecret | | Default | nil | -Sign up for Discord OAuth 2.0 credentials with the instructions here: https://discordapp.com/developers/docs/topics/oauth2. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for Discord OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ### `MICROSOFT_OAUTH_CREDENTIALS` @@ -324,7 +334,7 @@ Sign up for Discord OAuth 2.0 credentials with the instructions here: https://di | Value | ClientID:ClientSecret | | Default | nil | -Sign up for Microsoft OAuth 2.0 credentials with the instructions here: https://docs.microsoft.com/fr-fr/graph/auth/. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for Microsoft OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ## Username Policy diff --git a/lib/oauth/twitter.go b/lib/oauth/twitter.go new file mode 100644 index 000000000..66af8719f --- /dev/null +++ b/lib/oauth/twitter.go @@ -0,0 +1,40 @@ +package oauth + +import ( + "context" + "encoding/json" + "io" + + "golang.org/x/oauth2" +) + +// NewTwitterProvider returns a AuthN integration for Twitter OAuth +func NewTwitterProvider(credentials *Credentials) *Provider { + config := &oauth2.Config{ + ClientID: credentials.ID, + ClientSecret: credentials.Secret, + Scopes: []string{"email"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://twitter.com/i/oauth2/authorize", + TokenURL: "https://api.twitter.com/2/oauth2/token", + }, + } + + return NewProvider(config, func(t *oauth2.Token) (*UserInfo, error) { + client := config.Client(context.TODO(), t) + resp, err := client.Get("https://api.twitter.com/2/me") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var user UserInfo + err = json.Unmarshal(body, &user) + return &user, err + }) +} From 71934071cb7b0b3c82f9e4f3107a67b0192fb3f9 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Fri, 9 Aug 2024 23:13:52 +0200 Subject: [PATCH 2/2] revert formatting --- docs/config.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/config.md b/docs/config.md index 9847c6ca6..d8e0ce676 100644 --- a/docs/config.md +++ b/docs/config.md @@ -4,7 +4,7 @@ * Databases: [`DATABASE_URL`](#database_url) • [`REDIS_URL`](#redis_url) • [`REDIS_IS_SENTINEL_MODE`](#redis_is_sentinel_mode) • [`REDIS_SENTINEL_MASTER`](#redis_sentinel_master) • [`REDIS_SENTINEL_NODES`](#redis_sentinel_nodes) • [`REDIS_SENTINEL_PASSWORD`](#redis_sentinel_password) * Sessions: [`ACCESS_TOKEN_TTL`](#access_token_ttl) • [`REFRESH_TOKEN_TTL`](#refresh_token_ttl)• [`REFRESH_TOKEN_EXPLICIT_EXPIRY`](#refresh_token_explicit_expiry) • [`SESSION_KEY_SALT`](#session_key_salt) • [`DB_ENCRYPTION_KEY_SALT`](#db_encryption_key_salt) • [`RSA_PRIVATE_KEY`](#rsa_private_key) • [`SAME_SITE`](#same_site) -* OAuth Clients: [`FACEBOOK_OAUTH_CREDENTIALS`](#facebook_oauth_credentials) • [`TWITTER_OAUTH_CREDENTIALS`](#twitter_oauth_credentials) • [`GITHUB_OAUTH_CREDENTIALS`](#github_oauth_credentials) • [`GOOGLE_OAUTH_CREDENTIALS`](#google_oauth_credentials) • [`DISCORD_OAUTH_CREDENTIALS`](#discord_oauth_credentials) • [`MICROSOFT_OAUTH_CREDENTIALS`](#microsoft_oauth_credentials) +* OAuth Clients: [`FACEBOOK_OAUTH_CREDENTIALS`](#facebook_oauth_credentials) • [`TWITTER_OAUTH_CREDENTIALS`](#twitter_oauth_credentials) • [`GITHUB_OAUTH_CREDENTIALS`](#github_oauth_credentials) • [`GOOGLE_OAUTH_CREDENTIALS`](#google_oauth_credentials) • [`DISCORD_OAUTH_CREDENTIALS`](#discord_oauth_credentials) • [`MICROSOFT_OAUTH_CREDENTIALS`](#microsoft_oauth_credentials) * Username Policy: [`USERNAME_IS_EMAIL`](#username_is_email) • [`EMAIL_USERNAME_DOMAINS`](#email_username_domains) * Password Policy: [`PASSWORD_POLICY_SCORE`](#password_policy_score) • [`PASSWORD_CHANGE_LOGOUT`](#password_change_logout) • [`BCRYPT_COST`](#bcrypt_cost) * Password Resets: [`APP_PASSWORD_RESET_URL`](#app_password_reset_url) • [`PASSWORD_RESET_TOKEN_TTL`](#password_reset_token_ttl) • [`APP_PASSWORD_CHANGED_URL`](#app_password_changed_url) @@ -86,6 +86,7 @@ This value is commonly a 64-byte string, and can be generated with [`SecureRando May be set to a falsy value to disable the signup endpoint. If signup is disabled, all users must be created via the private [Import Account endpoint](api.md#import-account). + ## Databases ### `DATABASE_URL` @@ -189,6 +190,7 @@ This setting controls how frequently a refresh token must be used to keep a sess This setting controls cookie expiration behavior for refresh tokens. The cookie will be written without any expiration / max age and treated by browsers as a session cookie by default. If set to true, the cookie will be written as a persistent cookie with explicit expiration based on [`REFRESH_TOKEN_TTL`](#refresh_token_ttl). + ### `SESSION_KEY_SALT` | | | @@ -272,7 +274,7 @@ Additional credentialing data can be passed to the apple provider as key-value p `APPLE_OAUTH_CREDENTIALS=appID:appSecret:key1=val1:key2=val2` Note that the client secret for apple is **NOT** a static value as for other providers. The secret sent is a JWT constructed using the additional data keyID, teamID and expirySeconds included with credentials. -The configured client secret is a private key used to sign the JWT. This should be configured with a hex encoded representation of the full PEM block of a private key obtained at . +The configured client secret is a private key used to sign the JWT. This should be configured with a hex encoded representation of the full PEM block of a private key obtained at https://developer.apple.com. ### `FACEBOOK_OAUTH_CREDENTIALS` @@ -282,7 +284,7 @@ The configured client secret is a private key used to sign the JWT. This should | Value | AppID:AppSecret | | Default | nil | -Create a Facebook app at and enable the Facebook Login product. In the Quickstart, enter [AuthN's OAuth Return](api.md#oauth-return) as the Site URL. Then switch over to Settings and find the App ID and Secret. Join those together with a `:` and provide them to AuthN as a single variable. +Create a Facebook app at https://developers.facebook.com and enable the Facebook Login product. In the Quickstart, enter [AuthN's OAuth Return](api.md#oauth-return) as the Site URL. Then switch over to Settings and find the App ID and Secret. Join those together with a `:` and provide them to AuthN as a single variable. ### `TWITTER_OAUTH_CREDENTIALS` @@ -304,7 +306,7 @@ Log in and see your apps in the [X developer portal](https://developer.x.com/en/ | Value | ClientID:ClientSecret | | Default | nil | -Sign up for GitHub OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for GitHub OAuth 2.0 credentials with the instructions here: https://developer.github.com/apps/building-oauth-apps. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ### `GOOGLE_OAUTH_CREDENTIALS` @@ -314,7 +316,7 @@ Sign up for GitHub OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for Google OAuth 2.0 credentials with the instructions here: https://developers.google.com/identity/protocols/OpenIDConnect. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ### `DISCORD_OAUTH_CREDENTIALS` @@ -324,7 +326,7 @@ Sign up for Google OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for Discord OAuth 2.0 credentials with the instructions here: https://discordapp.com/developers/docs/topics/oauth2. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ### `MICROSOFT_OAUTH_CREDENTIALS` @@ -334,7 +336,7 @@ Sign up for Discord OAuth 2.0 credentials with the instructions here: . Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. +Sign up for Microsoft OAuth 2.0 credentials with the instructions here: https://docs.microsoft.com/fr-fr/graph/auth/. Your client's ID and secret must be joined together with a `:` and provided to AuthN as a single variable. ## Username Policy