2021-07-17 16:29:50 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
2021-12-03 17:25:27 +00:00
|
|
|
"context"
|
2021-07-17 16:29:50 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2021-09-04 22:27:29 +00:00
|
|
|
"log"
|
2021-07-17 16:29:50 +00:00
|
|
|
"net/http"
|
2022-03-08 07:06:26 +00:00
|
|
|
"strconv"
|
2021-07-17 16:29:50 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2021-07-23 16:27:44 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/constants"
|
2022-01-22 19:54:41 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/cookie"
|
2021-07-23 16:27:44 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/db"
|
2022-01-21 08:04:04 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/db/models"
|
2022-01-17 06:02:13 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/envstore"
|
2021-07-23 16:27:44 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/oauth"
|
2022-01-22 19:54:41 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
|
|
|
"github.com/authorizerdev/authorizer/server/token"
|
2021-07-23 16:27:44 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/utils"
|
2021-12-03 17:25:27 +00:00
|
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
2021-07-17 16:29:50 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
2022-01-17 06:02:13 +00:00
|
|
|
// OAuthCallbackHandler handles the OAuth callback for various oauth providers
|
|
|
|
func OAuthCallbackHandler() gin.HandlerFunc {
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
provider := c.Param("oauth_provider")
|
|
|
|
state := c.Request.FormValue("state")
|
|
|
|
|
2022-02-28 15:56:49 +00:00
|
|
|
sessionState := sessionstore.GetState(state)
|
2022-01-17 06:02:13 +00:00
|
|
|
if sessionState == "" {
|
|
|
|
c.JSON(400, gin.H{"error": "invalid oauth state"})
|
|
|
|
}
|
2022-02-28 15:56:49 +00:00
|
|
|
sessionstore.GetState(state)
|
2022-01-17 06:02:13 +00:00
|
|
|
// contains random token, redirect url, role
|
2022-03-08 13:43:45 +00:00
|
|
|
sessionSplit := strings.Split(state, "___")
|
2022-01-17 06:02:13 +00:00
|
|
|
|
2022-03-08 07:06:26 +00:00
|
|
|
if len(sessionSplit) < 3 {
|
2022-01-17 06:02:13 +00:00
|
|
|
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-08 07:06:26 +00:00
|
|
|
stateValue := sessionSplit[0]
|
2022-01-17 06:02:13 +00:00
|
|
|
redirectURL := sessionSplit[1]
|
2022-03-08 07:06:26 +00:00
|
|
|
inputRoles := strings.Split(sessionSplit[2], ",")
|
|
|
|
scopes := strings.Split(sessionSplit[3], ",")
|
2022-01-17 06:02:13 +00:00
|
|
|
|
|
|
|
var err error
|
2022-01-21 08:04:04 +00:00
|
|
|
user := models.User{}
|
2022-01-17 06:02:13 +00:00
|
|
|
code := c.Request.FormValue("code")
|
|
|
|
switch provider {
|
|
|
|
case constants.SignupMethodGoogle:
|
|
|
|
user, err = processGoogleUserInfo(code)
|
|
|
|
case constants.SignupMethodGithub:
|
|
|
|
user, err = processGithubUserInfo(code)
|
|
|
|
case constants.SignupMethodFacebook:
|
|
|
|
user, err = processFacebookUserInfo(code)
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf(`invalid oauth provider`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, gin.H{"error": err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
existingUser, err := db.Provider.GetUserByEmail(user.Email)
|
2022-01-17 06:02:13 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2022-03-16 17:19:18 +00:00
|
|
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp) {
|
|
|
|
c.JSON(400, gin.H{"error": "signup is disabled for this instance"})
|
|
|
|
return
|
|
|
|
}
|
2022-01-17 06:02:13 +00:00
|
|
|
// user not registered, register user and generate session token
|
|
|
|
user.SignupMethods = provider
|
|
|
|
// make sure inputRoles don't include protected roles
|
|
|
|
hasProtectedRole := false
|
|
|
|
for _, ir := range inputRoles {
|
2022-02-28 02:25:01 +00:00
|
|
|
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
|
2022-01-17 06:02:13 +00:00
|
|
|
hasProtectedRole = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if hasProtectedRole {
|
|
|
|
c.JSON(400, gin.H{"error": "invalid role"})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
user.Roles = strings.Join(inputRoles, ",")
|
|
|
|
now := time.Now().Unix()
|
|
|
|
user.EmailVerifiedAt = &now
|
2022-01-21 08:04:04 +00:00
|
|
|
user, _ = db.Provider.AddUser(user)
|
2022-01-17 06:02:13 +00:00
|
|
|
} else {
|
2022-03-24 08:43:55 +00:00
|
|
|
if user.RevokedTimestamp != nil {
|
|
|
|
c.JSON(400, gin.H{"error": "user access has been revoked"})
|
|
|
|
}
|
|
|
|
|
2022-01-17 06:02:13 +00:00
|
|
|
// user exists in db, check if method was google
|
|
|
|
// if not append google to existing signup method and save it
|
|
|
|
signupMethod := existingUser.SignupMethods
|
|
|
|
if !strings.Contains(signupMethod, provider) {
|
|
|
|
signupMethod = signupMethod + "," + provider
|
|
|
|
}
|
2022-02-02 07:00:11 +00:00
|
|
|
user = existingUser
|
2022-01-17 06:02:13 +00:00
|
|
|
user.SignupMethods = signupMethod
|
|
|
|
|
2022-01-31 06:05:24 +00:00
|
|
|
if user.EmailVerifiedAt == nil {
|
|
|
|
now := time.Now().Unix()
|
|
|
|
user.EmailVerifiedAt = &now
|
|
|
|
}
|
|
|
|
|
2022-01-17 06:02:13 +00:00
|
|
|
// There multiple scenarios with roles here in social login
|
|
|
|
// 1. user has access to protected roles + roles and trying to login
|
|
|
|
// 2. user has not signed up for one of the available role but trying to signup.
|
|
|
|
// Need to modify roles in this case
|
|
|
|
|
|
|
|
// find the unassigned roles
|
|
|
|
existingRoles := strings.Split(existingUser.Roles, ",")
|
|
|
|
unasignedRoles := []string{}
|
|
|
|
for _, ir := range inputRoles {
|
|
|
|
if !utils.StringSliceContains(existingRoles, ir) {
|
|
|
|
unasignedRoles = append(unasignedRoles, ir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(unasignedRoles) > 0 {
|
|
|
|
// check if it contains protected unassigned role
|
|
|
|
hasProtectedRole := false
|
|
|
|
for _, ur := range unasignedRoles {
|
2022-02-28 02:25:01 +00:00
|
|
|
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
|
2022-01-17 06:02:13 +00:00
|
|
|
hasProtectedRole = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if hasProtectedRole {
|
|
|
|
c.JSON(400, gin.H{"error": "invalid role"})
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
user.Roles = existingUser.Roles
|
|
|
|
}
|
2022-02-02 07:00:11 +00:00
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
user, err = db.Provider.UpdateUser(user)
|
2022-02-02 07:00:11 +00:00
|
|
|
if err != nil {
|
|
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
|
|
return
|
|
|
|
}
|
2022-01-17 06:02:13 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 07:06:26 +00:00
|
|
|
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
|
2022-03-07 03:01:39 +00:00
|
|
|
if err != nil {
|
|
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
|
|
}
|
2022-03-25 12:21:20 +00:00
|
|
|
|
|
|
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
|
|
|
if expiresIn <= 0 {
|
|
|
|
expiresIn = 1
|
|
|
|
}
|
|
|
|
|
2022-03-08 07:06:26 +00:00
|
|
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
|
|
|
|
|
|
|
|
cookie.SetSession(c, authToken.FingerPrintHash)
|
|
|
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
|
|
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
|
|
|
|
|
|
|
if authToken.RefreshToken != nil {
|
2022-03-08 14:01:19 +00:00
|
|
|
params = params + `&refresh_token=` + authToken.RefreshToken.Token
|
2022-03-08 15:43:23 +00:00
|
|
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
2022-03-08 07:06:26 +00:00
|
|
|
}
|
2022-01-17 06:02:13 +00:00
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
go utils.SaveSessionInDB(c, user.ID)
|
2022-03-08 07:06:26 +00:00
|
|
|
if strings.Contains(redirectURL, "?") {
|
|
|
|
redirectURL = redirectURL + "&" + params
|
|
|
|
} else {
|
|
|
|
redirectURL = redirectURL + "?" + params
|
|
|
|
}
|
|
|
|
|
2022-01-17 06:02:13 +00:00
|
|
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
func processGoogleUserInfo(code string) (models.User, error) {
|
|
|
|
user := models.User{}
|
2021-12-03 17:25:27 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code)
|
2021-07-17 16:29:50 +00:00
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("invalid google exchange code: %s", err.Error())
|
2021-07-17 16:29:50 +00:00
|
|
|
}
|
2021-12-03 17:25:27 +00:00
|
|
|
|
|
|
|
verifier := oauth.OIDCProviders.GoogleOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.GoogleConfig.ClientID})
|
|
|
|
|
|
|
|
// Extract the ID Token from OAuth2 token.
|
|
|
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
|
|
|
if !ok {
|
|
|
|
return user, fmt.Errorf("unable to extract id_token")
|
2021-07-17 16:29:50 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 17:25:27 +00:00
|
|
|
// Parse and verify ID Token payload.
|
|
|
|
idToken, err := verifier.Verify(ctx, rawIDToken)
|
2021-07-17 16:29:50 +00:00
|
|
|
if err != nil {
|
2021-12-11 01:11:35 +00:00
|
|
|
return user, fmt.Errorf("unable to verify id_token: %s", err.Error())
|
2021-07-17 16:29:50 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 05:21:12 +00:00
|
|
|
if err := idToken.Claims(&user); err != nil {
|
2021-12-03 17:25:27 +00:00
|
|
|
return user, fmt.Errorf("unable to extract claims")
|
|
|
|
}
|
2021-07-17 16:29:50 +00:00
|
|
|
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, nil
|
2021-07-17 23:18:42 +00:00
|
|
|
}
|
2021-07-17 16:29:50 +00:00
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
func processGithubUserInfo(code string) (models.User, error) {
|
|
|
|
user := models.User{}
|
2021-12-03 17:25:27 +00:00
|
|
|
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
|
2021-07-17 23:18:42 +00:00
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
|
2021-07-17 23:18:42 +00:00
|
|
|
}
|
|
|
|
client := http.Client{}
|
|
|
|
req, err := http.NewRequest("GET", constants.GithubUserInfoURL, nil)
|
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
|
2021-07-17 23:18:42 +00:00
|
|
|
}
|
|
|
|
req.Header = http.Header{
|
|
|
|
"Authorization": []string{fmt.Sprintf("token %s", token.AccessToken)},
|
2021-07-17 16:29:50 +00:00
|
|
|
}
|
|
|
|
|
2021-07-17 23:18:42 +00:00
|
|
|
response, err := client.Do(req)
|
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, err
|
2021-07-17 23:18:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer response.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
|
2021-07-17 23:18:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
userRawData := make(map[string]string)
|
|
|
|
json.Unmarshal(body, &userRawData)
|
|
|
|
|
|
|
|
name := strings.Split(userRawData["name"], " ")
|
|
|
|
firstName := ""
|
|
|
|
lastName := ""
|
|
|
|
if len(name) >= 1 && strings.TrimSpace(name[0]) != "" {
|
|
|
|
firstName = name[0]
|
|
|
|
}
|
|
|
|
if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
|
|
|
|
lastName = name[0]
|
|
|
|
}
|
2021-12-22 05:21:12 +00:00
|
|
|
|
2021-12-22 10:01:45 +00:00
|
|
|
picture := userRawData["avatar_url"]
|
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
user = models.User{
|
2021-12-22 10:01:45 +00:00
|
|
|
GivenName: &firstName,
|
|
|
|
FamilyName: &lastName,
|
|
|
|
Picture: &picture,
|
2022-04-22 14:26:55 +00:00
|
|
|
Email: userRawData["email"],
|
2021-07-17 23:18:42 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, nil
|
2021-07-17 16:29:50 +00:00
|
|
|
}
|
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
func processFacebookUserInfo(code string) (models.User, error) {
|
|
|
|
user := models.User{}
|
2021-12-03 17:25:27 +00:00
|
|
|
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
|
2021-09-04 22:27:29 +00:00
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
|
2021-09-04 22:27:29 +00:00
|
|
|
}
|
|
|
|
client := http.Client{}
|
|
|
|
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+token.AccessToken, nil)
|
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("error creating facebook user info request: %s", err.Error())
|
2021-09-04 22:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
response, err := client.Do(req)
|
|
|
|
if err != nil {
|
2021-12-17 15:55:07 +00:00
|
|
|
log.Println("error processing facebook user info:", err)
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, err
|
2021-09-04 22:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer response.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
|
2021-09-04 22:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
userRawData := make(map[string]interface{})
|
|
|
|
json.Unmarshal(body, &userRawData)
|
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
email := fmt.Sprintf("%v", userRawData["sub"])
|
2021-09-04 22:27:29 +00:00
|
|
|
|
|
|
|
picObject := userRawData["picture"].(map[string]interface{})["data"]
|
|
|
|
picDataObject := picObject.(map[string]interface{})
|
2021-12-22 10:01:45 +00:00
|
|
|
firstName := fmt.Sprintf("%v", userRawData["first_name"])
|
|
|
|
lastName := fmt.Sprintf("%v", userRawData["last_name"])
|
|
|
|
picture := fmt.Sprintf("%v", picDataObject["url"])
|
|
|
|
|
2022-01-21 08:04:04 +00:00
|
|
|
user = models.User{
|
2021-12-22 10:01:45 +00:00
|
|
|
GivenName: &firstName,
|
|
|
|
FamilyName: &lastName,
|
|
|
|
Picture: &picture,
|
2021-12-22 05:21:12 +00:00
|
|
|
Email: email,
|
2021-09-04 22:27:29 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 16:41:41 +00:00
|
|
|
return user, nil
|
2021-09-04 22:27:29 +00:00
|
|
|
}
|