diff --git a/recipe/thirdparty/providers/bitbucket.go b/recipe/thirdparty/providers/bitbucket.go index cd565674..52bc6ae5 100644 --- a/recipe/thirdparty/providers/bitbucket.go +++ b/recipe/thirdparty/providers/bitbucket.go @@ -16,135 +16,113 @@ package providers import ( + "errors" + "fmt" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" + "github.com/supertokens/supertokens-golang/supertokens" ) const bitbucketID = "bitbucket" -func Bitbucket(config tpmodels.ProviderInput) tpmodels.TypeProvider { - return tpmodels.TypeProvider{ - // FIXME - // ID: bitbucketID, - // Get: func(redirectURI, authCodeFromRequest *string, userContext supertokens.UserContext) tpmodels.TypeProviderGetResponse { - // accessTokenAPIURL := "https://bitbucket.org/site/oauth2/access_token" - // accessTokenAPIParams := map[string]string{ - // "client_id": config.ClientID, - // "client_secret": config.ClientSecret, - // "grant_type": "authorization_code", - // } - // if authCodeFromRequest != nil { - // accessTokenAPIParams["code"] = *authCodeFromRequest - // } - // if redirectURI != nil { - // accessTokenAPIParams["redirect_uri"] = *redirectURI - // } - - // authorisationRedirectURL := "https://bitbucket.org/site/oauth2/authorize" - // scopes := []string{"account", "email"} - // if config.Scope != nil { - // scopes = config.Scope - // } - - // var additionalParams map[string]interface{} = nil - // if config.AuthorisationRedirect != nil && config.AuthorisationRedirect.Params != nil { - // additionalParams = config.AuthorisationRedirect.Params - // } - - // authorizationRedirectParams := map[string]interface{}{ - // "scope": strings.Join(scopes, " "), - // "access_type": "offline", - // "response_type": "code", - // "client_id": config.ClientID, - // } - // for key, value := range additionalParams { - // authorizationRedirectParams[key] = value - // } - - // return tpmodels.TypeProviderGetResponse{ - // AccessTokenAPI: tpmodels.AccessTokenAPI{ - // URL: accessTokenAPIURL, - // Params: accessTokenAPIParams, - // }, - // AuthorisationRedirect: tpmodels.AuthorisationRedirect{ - // URL: authorisationRedirectURL, - // Params: authorizationRedirectParams, - // }, - // GetProfileInfo: func(authCodeResponse interface{}, userContext supertokens.UserContext) (tpmodels.UserInfo, error) { - // authCodeResponseJson, err := json.Marshal(authCodeResponse) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // var accessTokenAPIResponse bitbucketGetProfileInfoInput - // err = json.Unmarshal(authCodeResponseJson, &accessTokenAPIResponse) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // accessToken := accessTokenAPIResponse.AccessToken - // authHeader := "Bearer " + accessToken - // response, err := getBitbucketAuthRequest(authHeader) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // userInfo := response.(map[string]interface{}) - // ID := userInfo["uuid"].(string) - - // emailResponse, err := getBitbucketEmailRequest(authHeader) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // var email string - // var isVerified bool = false - // emailResponseInfo := emailResponse.(map[string]interface{}) - // for _, emailInfo := range emailResponseInfo["values"].([]interface{}) { - // emailInfoMap := emailInfo.(map[string]interface{}) - // if emailInfoMap["is_primary"].(bool) { - // email = emailInfoMap["email"].(string) - // isVerified = emailInfoMap["is_confirmed"].(bool) - // } - // } - // if email == "" { - // return tpmodels.UserInfo{ - // ID: ID, - // }, nil - // } - - // return tpmodels.UserInfo{ - // ID: ID, - // Email: &tpmodels.EmailStruct{ - // ID: email, - // IsVerified: isVerified, - // }, - // }, nil - // }, - // GetClientId: func(userContext supertokens.UserContext) string { - // return config.ClientID - // }, - // } - // }, - // IsDefault: config.IsDefault, +func Bitbucket(input tpmodels.ProviderInput) *tpmodels.TypeProvider { + if input.Config.Name == "" { + input.Config.Name = "Bitbucket" } -} -// func getBitbucketAuthRequest(authHeader string) (interface{}, error) { -// url := "https://api.bitbucket.org/2.0/user" -// req, err := http.NewRequest("GET", url, nil) -// if err != nil { -// return nil, err -// } -// req.Header.Add("Authorization", authHeader) -// return doGetRequest(req) -// } - -// func getBitbucketEmailRequest(authHeader string) (interface{}, error) { -// url := "https://api.bitbucket.org/2.0/user/emails" -// req, err := http.NewRequest("GET", url, nil) -// if err != nil { -// return nil, err -// } -// req.Header.Add("Authorization", authHeader) -// return doGetRequest(req) -// } - -// type bitbucketGetProfileInfoInput struct { -// AccessToken string `json:"access_token"` -// } + if input.Config.AuthorizationEndpoint == "" { + input.Config.AuthorizationEndpoint = "https://bitbucket.org/site/oauth2/authorize" + } + + if input.Config.TokenEndpoint == "" { + input.Config.TokenEndpoint = "https://bitbucket.org/site/oauth2/access_token" + } + + if input.Config.AuthorizationEndpointQueryParams == nil { + input.Config.AuthorizationEndpointQueryParams = map[string]interface{}{ + "audience": "api.atlassian.com", + } + } + + oOverride := input.Override + + input.Override = func(originalImplementation *tpmodels.TypeProvider) *tpmodels.TypeProvider { + oGetConfig := originalImplementation.GetConfigForClientType + + originalImplementation.GetConfigForClientType = func(clientType *string, userContext supertokens.UserContext) (tpmodels.ProviderConfigForClientType, error) { + config, err := oGetConfig(clientType, userContext) + if err != nil { + return tpmodels.ProviderConfigForClientType{}, err + } + + if len(config.Scope) == 0 { + config.Scope = []string{"account", "email"} + } + + return config, nil + } + + originalImplementation.GetUserInfo = func(oAuthTokens tpmodels.TypeOAuthTokens, userContext supertokens.UserContext) (tpmodels.TypeUserInfo, error) { + accessToken, ok := oAuthTokens["access_token"].(string) + if !ok { + return tpmodels.TypeUserInfo{}, errors.New("access token not found") + } + + headers := map[string]string{ + "Authorization": "Bearer " + accessToken, + } + rawUserInfoFromProvider := tpmodels.TypeRawUserInfoFromProvider{} + userInfoFromAccessToken, err := doGetRequest( + "https://api.bitbucket.org/2.0/user", + nil, + headers, + ) + if err != nil { + return tpmodels.TypeUserInfo{}, err + } + rawUserInfoFromProvider.FromUserInfoAPI = userInfoFromAccessToken.(map[string]interface{}) + + userInfoFromEmail, err := doGetRequest( + "https://api.bitbucket.org/2.0/user/emails", + nil, + headers, + ) + rawUserInfoFromProvider.FromUserInfoAPI["email"] = userInfoFromEmail + + email := "" + isVerified := false + + for _, emailInfo := range userInfoFromEmail.(map[string]interface{})["values"].([]interface{}) { + emailInfoMap := emailInfo.(map[string]interface{}) + if emailInfoMap["is_primary"].(bool) { + email = emailInfoMap["email"].(string) + isVerified = emailInfoMap["is_confirmed"].(bool) + break + } + } + + if email == "" { + return tpmodels.TypeUserInfo{ + ThirdPartyUserId: fmt.Sprint(rawUserInfoFromProvider.FromUserInfoAPI["uuid"]), + RawUserInfoFromProvider: rawUserInfoFromProvider, + }, nil + } else { + return tpmodels.TypeUserInfo{ + ThirdPartyUserId: fmt.Sprint(rawUserInfoFromProvider.FromUserInfoAPI["uuid"]), + Email: &tpmodels.EmailStruct{ + ID: email, + IsVerified: isVerified, + }, + RawUserInfoFromProvider: rawUserInfoFromProvider, + }, nil + } + } + + if oOverride != nil { + originalImplementation = oOverride(originalImplementation) + } + return originalImplementation + } + + return NewProvider(input) +} diff --git a/recipe/thirdparty/providers/config_utils.go b/recipe/thirdparty/providers/config_utils.go index 94a00151..0be068c1 100644 --- a/recipe/thirdparty/providers/config_utils.go +++ b/recipe/thirdparty/providers/config_utils.go @@ -55,12 +55,16 @@ func createProvider(input tpmodels.ProviderInput) *tpmodels.TypeProvider { return ActiveDirectory(input) } else if strings.HasPrefix(input.Config.ThirdPartyId, "apple") { return Apple(input) + } else if strings.HasPrefix(input.Config.ThirdPartyId, "bitbucket") { + return Bitbucket(input) } else if strings.HasPrefix(input.Config.ThirdPartyId, "discord") { return Discord(input) } else if strings.HasPrefix(input.Config.ThirdPartyId, "facebook") { return Facebook(input) } else if strings.HasPrefix(input.Config.ThirdPartyId, "github") { return Github(input) + } else if strings.HasPrefix(input.Config.ThirdPartyId, "gitlab") { + return Gitlab(input) } else if strings.HasPrefix(input.Config.ThirdPartyId, "google-workspaces") { return GoogleWorkspaces(input) } else if strings.HasPrefix(input.Config.ThirdPartyId, "google") { diff --git a/recipe/thirdparty/providers/gitlab.go b/recipe/thirdparty/providers/gitlab.go index 43b130d9..47885067 100644 --- a/recipe/thirdparty/providers/gitlab.go +++ b/recipe/thirdparty/providers/gitlab.go @@ -17,124 +17,48 @@ package providers import ( "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" + "github.com/supertokens/supertokens-golang/supertokens" ) const gitlabID = "gitlab" -func GitLab(config tpmodels.ProviderInput) tpmodels.TypeProvider { - // FIXME - // gitLabURL := "https://gitlab.com" - // if config.GitLabBaseURL != nil { - // url, err := supertokens.NewNormalisedURLDomain(*config.GitLabBaseURL) - // if err != nil { - // panic(err) - // } - // gitLabURL = url.GetAsStringDangerous() - // } - return tpmodels.TypeProvider{ - // ID: gitlabID, - // Get: func(redirectURI, authCodeFromRequest *string, userContext supertokens.UserContext) tpmodels.TypeProviderGetResponse { - // accessTokenAPIURL := gitLabURL + "/oauth/token" - // accessTokenAPIParams := map[string]string{ - // "client_id": config.ClientID, - // "client_secret": config.ClientSecret, - // "grant_type": "authorization_code", - // } - // if authCodeFromRequest != nil { - // accessTokenAPIParams["code"] = *authCodeFromRequest - // } - // if redirectURI != nil { - // accessTokenAPIParams["redirect_uri"] = *redirectURI - // } +func Gitlab(input tpmodels.ProviderInput) *tpmodels.TypeProvider { + if input.Config.Name == "" { + input.Config.Name = "Gitlab" + } - // authorisationRedirectURL := gitLabURL + "/oauth/authorize" - // scopes := []string{"read_user"} - // if config.Scope != nil { - // scopes = config.Scope - // } + oOverride := input.Override - // var additionalParams map[string]interface{} = nil - // if config.AuthorisationRedirect != nil && config.AuthorisationRedirect.Params != nil { - // additionalParams = config.AuthorisationRedirect.Params - // } + input.Override = func(originalImplementation *tpmodels.TypeProvider) *tpmodels.TypeProvider { + oGetConfig := originalImplementation.GetConfigForClientType + originalImplementation.GetConfigForClientType = func(clientType *string, userContext supertokens.UserContext) (tpmodels.ProviderConfigForClientType, error) { + config, err := oGetConfig(clientType, userContext) + if err != nil { + return tpmodels.ProviderConfigForClientType{}, err + } - // authorizationRedirectParams := map[string]interface{}{ - // "scope": strings.Join(scopes, " "), - // "response_type": "code", - // "client_id": config.ClientID, - // } - // for key, value := range additionalParams { - // authorizationRedirectParams[key] = value - // } + if len(config.Scope) == 0 { + config.Scope = []string{"openid", "email"} + } - // return tpmodels.TypeProviderGetResponse{ - // AccessTokenAPI: tpmodels.AccessTokenAPI{ - // URL: accessTokenAPIURL, - // Params: accessTokenAPIParams, - // }, - // AuthorisationRedirect: tpmodels.AuthorisationRedirect{ - // URL: authorisationRedirectURL, - // Params: authorizationRedirectParams, - // }, - // GetProfileInfo: func(authCodeResponse interface{}, userContext supertokens.UserContext) (tpmodels.UserInfo, error) { - // authCodeResponseJson, err := json.Marshal(authCodeResponse) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // var accessTokenAPIResponse gitlabGetProfileInfoInput - // err = json.Unmarshal(authCodeResponseJson, &accessTokenAPIResponse) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // accessToken := accessTokenAPIResponse.AccessToken - // authHeader := "Bearer " + accessToken - // response, err := getGitLabAuthRequest(gitLabURL, authHeader) - // if err != nil { - // return tpmodels.UserInfo{}, err - // } - // userInfo := response.(map[string]interface{}) - // ID := fmt.Sprint(userInfo["id"]) // the id returned by gitlab is a number, so we convert to a string - // _, emailExists := userInfo["email"] - // if !emailExists { - // return tpmodels.UserInfo{ - // ID: ID, - // }, nil - // } - // email := userInfo["email"].(string) - // var isVerified bool - // _, ok := userInfo["confirmed_at"] - // if ok && userInfo["confirmed_at"] != nil && userInfo["confirmed_at"].(string) != "" { - // isVerified = true - // } else { - // isVerified = false - // } - // return tpmodels.UserInfo{ - // ID: ID, - // Email: &tpmodels.EmailStruct{ - // ID: email, - // IsVerified: isVerified, - // }, - // }, nil - // }, - // GetClientId: func(userContext supertokens.UserContext) string { - // return config.ClientID - // }, - // } - // }, - // IsDefault: config.IsDefault, - } -} + if config.OIDCDiscoveryEndpoint == "" { + config.OIDCDiscoveryEndpoint = "https://gitlab.com" + if config.AdditionalConfig != nil { + if gitlabBaseUrl, ok := config.AdditionalConfig["gitlabBaseUrl"].(string); ok { + config.OIDCDiscoveryEndpoint = gitlabBaseUrl + } + } + } + + return config, nil + } -// func getGitLabAuthRequest(gitLabUrl string, authHeader string) (interface{}, error) { -// url := gitLabUrl + "/api/v4/user" -// req, err := http.NewRequest("GET", url, nil) -// if err != nil { -// return nil, err -// } -// req.Header.Add("Authorization", authHeader) -// return doGetRequest(req) -// } + if oOverride != nil { + originalImplementation = oOverride(originalImplementation) + } -// type gitlabGetProfileInfoInput struct { -// AccessToken string `json:"access_token"` -// } + return originalImplementation + } + + return NewProvider(input) +}