chatdesk-ui/auth_v2.169.0/internal/api/provider/twitter.go

156 lines
4.3 KiB
Go

package provider
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/mrjones/oauth"
"github.com/supabase/auth/internal/conf"
"github.com/supabase/auth/internal/utilities"
"golang.org/x/oauth2"
)
const (
defaultTwitterAPIBase = "api.twitter.com"
requestURL = "/oauth/request_token"
authenticateURL = "/oauth/authenticate"
tokenURL = "/oauth/access_token" //#nosec G101 -- Not a secret value.
endpointProfile = "/1.1/account/verify_credentials.json"
)
// TwitterProvider stores the custom config for twitter provider
type TwitterProvider struct {
ClientKey string
Secret string
CallbackURL string
AuthURL string
RequestToken *oauth.RequestToken
OauthVerifier string
Consumer *oauth.Consumer
UserInfoURL string
}
type twitterUser struct {
UserName string `json:"screen_name"`
Name string `json:"name"`
AvatarURL string `json:"profile_image_url_https"`
Email string `json:"email"`
ID string `json:"id_str"`
}
// NewTwitterProvider creates a Twitter account provider.
func NewTwitterProvider(ext conf.OAuthProviderConfiguration, scopes string) (OAuthProvider, error) {
if err := ext.ValidateOAuth(); err != nil {
return nil, err
}
authHost := chooseHost(ext.URL, defaultTwitterAPIBase)
p := &TwitterProvider{
ClientKey: ext.ClientID[0],
Secret: ext.Secret,
CallbackURL: ext.RedirectURI,
UserInfoURL: authHost + endpointProfile,
}
p.Consumer = newConsumer(p, authHost)
return p, nil
}
// GetOAuthToken is a stub method for OAuthProvider interface, unused in OAuth1.0 protocol
func (t TwitterProvider) GetOAuthToken(_ string) (*oauth2.Token, error) {
return &oauth2.Token{}, nil
}
// GetUserData is a stub method for OAuthProvider interface, unused in OAuth1.0 protocol
func (t TwitterProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
return &UserProvidedData{}, nil
}
// FetchUserData retrieves the user's data from the twitter provider
func (t TwitterProvider) FetchUserData(ctx context.Context, tok *oauth.AccessToken) (*UserProvidedData, error) {
var u twitterUser
resp, err := t.Consumer.Get(
t.UserInfoURL,
map[string]string{"include_entities": "false", "skip_status": "true", "include_email": "true"},
tok,
)
if err != nil {
return nil, err
}
defer utilities.SafeClose(resp.Body)
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return &UserProvidedData{}, fmt.Errorf("a %v error occurred with retrieving user from twitter", resp.StatusCode)
}
bits, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
_ = json.NewDecoder(bytes.NewReader(bits)).Decode(&u)
data := &UserProvidedData{}
if u.Email != "" {
data.Emails = []Email{{
Email: u.Email,
Verified: true,
Primary: true,
}}
}
data.Metadata = &Claims{
Issuer: t.UserInfoURL,
Subject: u.ID,
Name: u.Name,
Picture: u.AvatarURL,
PreferredUsername: u.UserName,
// To be deprecated
UserNameKey: u.UserName,
FullName: u.Name,
AvatarURL: u.AvatarURL,
ProviderId: u.ID,
}
return data, nil
}
// AuthCodeURL fetches the request token from the twitter provider
func (t *TwitterProvider) AuthCodeURL(state string, args ...oauth2.AuthCodeOption) string {
// we do nothing with the state here as the state is passed in the requestURL step
requestToken, url, err := t.Consumer.GetRequestTokenAndUrl(t.CallbackURL + "?state=" + state)
if err != nil {
return ""
}
t.RequestToken = requestToken
t.AuthURL = url
return t.AuthURL
}
func newConsumer(provider *TwitterProvider, authHost string) *oauth.Consumer {
c := oauth.NewConsumer(
provider.ClientKey,
provider.Secret,
oauth.ServiceProvider{
RequestTokenUrl: authHost + requestURL,
AuthorizeTokenUrl: authHost + authenticateURL,
AccessTokenUrl: authHost + tokenURL,
},
)
return c
}
// Marshal encodes the twitter request token
func (t TwitterProvider) Marshal() string {
b, _ := json.Marshal(t.RequestToken)
return string(b)
}
// Unmarshal decodes the twitter request token
func (t TwitterProvider) Unmarshal(data string) (*oauth.RequestToken, error) {
requestToken := &oauth.RequestToken{}
err := json.NewDecoder(strings.NewReader(data)).Decode(requestToken)
return requestToken, err
}