156 lines
4.3 KiB
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
|
|
}
|