feat: Adds login via twitter
This commit is contained in:
parent
aa232de426
commit
f73d1fc588
|
@ -14,4 +14,6 @@ const (
|
|||
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
|
||||
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
|
||||
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
|
||||
|
||||
TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username"
|
||||
)
|
||||
|
|
|
@ -68,7 +68,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||
case constants.AuthRecipeMethodApple:
|
||||
user, err = processAppleUserInfo(code)
|
||||
case constants.AuthRecipeMethodTwitter:
|
||||
user, err = processTwitterUserInfo(code)
|
||||
user, err = processTwitterUserInfo(code, sessionState)
|
||||
default:
|
||||
log.Info("Invalid oauth provider")
|
||||
err = fmt.Errorf(`invalid oauth provider`)
|
||||
|
@ -567,8 +567,69 @@ func processAppleUserInfo(code string) (models.User, error) {
|
|||
return user, err
|
||||
}
|
||||
|
||||
func processTwitterUserInfo(code string) (models.User, error) {
|
||||
func processTwitterUserInfo(code, verifier string) (models.User, error) {
|
||||
user := models.User{}
|
||||
// TODO exchange code and get user information
|
||||
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(oauth2.NoContext, code, oauth2.SetAuthURLParam("code_verifier", verifier))
|
||||
if err != nil {
|
||||
log.Debug("Failed to exchange code for token: ", err)
|
||||
return user, fmt.Errorf("invalid twitter exchange code: %s", err.Error())
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest("GET", constants.TwitterUserInfoURL, nil)
|
||||
if err != nil {
|
||||
log.Debug("Failed to create Twitter user info request: ", err)
|
||||
return user, fmt.Errorf("error creating Twitter user info request: %s", err.Error())
|
||||
}
|
||||
req.Header = http.Header{
|
||||
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
|
||||
}
|
||||
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Debug("Failed to request Twitter user info: ", err)
|
||||
return user, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Debug("Failed to read Twitter user info response body: ", err)
|
||||
return user, fmt.Errorf("failed to read Twitter response body: %s", err.Error())
|
||||
}
|
||||
|
||||
if response.StatusCode >= 400 {
|
||||
log.Debug("Failed to request Twitter user info: ", string(body))
|
||||
return user, fmt.Errorf("failed to request Twitter user info: %s", string(body))
|
||||
}
|
||||
|
||||
responseRawData := make(map[string]interface{})
|
||||
json.Unmarshal(body, &responseRawData)
|
||||
|
||||
userRawData := responseRawData["data"].(map[string]interface{})
|
||||
|
||||
log.Info(userRawData)
|
||||
// Twitter API does not return E-Mail adresses by default. For that case special privileges have
|
||||
// to be granted on a per-App basis. See https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials
|
||||
|
||||
// Currently Twitter API only provides the full name of a user. To fill givenName and familyName
|
||||
// the full name will be split at the first whitespace. This approach will not be valid for all name combinations
|
||||
nameArr := strings.SplitAfterN(userRawData["name"].(string), " ", 2)
|
||||
|
||||
firstName := nameArr[0]
|
||||
lastName := ""
|
||||
if len(nameArr) == 2 {
|
||||
lastName = nameArr[1]
|
||||
}
|
||||
nickname := userRawData["username"].(string)
|
||||
profilePicture := userRawData["profile_image_url"].(string)
|
||||
|
||||
user = models.User{
|
||||
GivenName: &firstName,
|
||||
FamilyName: &lastName,
|
||||
Picture: &profilePicture,
|
||||
Nickname: &nickname,
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/oauth"
|
||||
"github.com/authorizerdev/authorizer/server/parsers"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/authorizerdev/authorizer/server/validators"
|
||||
)
|
||||
|
||||
|
@ -175,7 +176,10 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||
isProviderConfigured = false
|
||||
break
|
||||
}
|
||||
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodTwitter)
|
||||
|
||||
verifier, challenge := utils.GenerateCodeChallenge()
|
||||
|
||||
err := memorystore.Provider.SetState(oauthStateString, verifier)
|
||||
if err != nil {
|
||||
log.Debug("Error setting state: ", err)
|
||||
c.JSON(500, gin.H{
|
||||
|
@ -184,7 +188,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
oauth.OAuthProviders.TwitterConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitter
|
||||
url := oauth.OAuthProviders.TwitterConfig.AuthCodeURL(oauthStateString)
|
||||
url := oauth.OAuthProviders.TwitterConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("code_challenge", challenge), oauth2.SetAuthURLParam("code_challenge_method", "S256"))
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case constants.AuthRecipeMethodApple:
|
||||
if oauth.OAuthProviders.AppleConfig == nil {
|
||||
|
|
|
@ -134,7 +134,28 @@ func InitOAuth() error {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO add support for twitter provider and update OAuthProviders.TwitterConfig
|
||||
twitterClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientID)
|
||||
if err != nil {
|
||||
twitterClientID = ""
|
||||
}
|
||||
twitterClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientSecret)
|
||||
if err != nil {
|
||||
twitterClientSecret = ""
|
||||
}
|
||||
if twitterClientID != "" && twitterClientSecret != "" {
|
||||
OAuthProviders.TwitterConfig = &oauth2.Config{
|
||||
ClientID: twitterClientID,
|
||||
ClientSecret: twitterClientSecret,
|
||||
RedirectURL: "/oauth_callback/twitter",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
// Endpoint is currently not yet part of oauth2-package. See https://go-review.googlesource.com/c/oauth2/+/350889 for status
|
||||
AuthURL: "https://twitter.com/i/oauth2/authorize",
|
||||
TokenURL: "https://api.twitter.com/2/oauth2/token",
|
||||
AuthStyle: oauth2.AuthStyleInHeader,
|
||||
},
|
||||
Scopes: []string{"tweet.read", "users.read"},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
32
server/utils/pkce.go
Normal file
32
server/utils/pkce.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
b64 "encoding/base64"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
length = 32
|
||||
)
|
||||
|
||||
// GenerateCodeChallenge creates PKCE-Code-Challenge
|
||||
// and returns the verifier and challenge
|
||||
func GenerateCodeChallenge() (string, string) {
|
||||
// Generate Verifier
|
||||
randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomBytes := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
randomBytes[i] = byte(randGenerator.Intn(255))
|
||||
}
|
||||
verifier := strings.Trim(b64.URLEncoding.EncodeToString(randomBytes), "=")
|
||||
|
||||
// Generate Challenge
|
||||
rawChallenge := sha256.New()
|
||||
rawChallenge.Write([]byte(verifier))
|
||||
challenge := strings.Trim(b64.URLEncoding.EncodeToString(rawChallenge.Sum(nil)), "=")
|
||||
|
||||
return verifier, challenge
|
||||
}
|
Loading…
Reference in New Issue
Block a user