Skip to content

Commit b8298eb

Browse files
Merge pull request #250 from supertokens/new-providers
New providers
2 parents 39b700c + 29513ec commit b8298eb

File tree

7 files changed

+335
-3
lines changed

7 files changed

+335
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [unreleased]
9+
10+
## [0.10.3] - 2023-03-29
911
- Adds unit test for Apple callback form post
1012
- Updates all example apps to also initialise dashboard recipe
13+
- Adds login with gitlab (for single tenant only) and bitbucket
1114

1215
## [0.10.2] - 2023-02-24
1316
- Adds APIs and logic to the dashboard recipe to enable email password based login

coreDriverInterfaceSupported.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"2.15",
1212
"2.16",
1313
"2.17",
14-
"2.18"
14+
"2.18",
15+
"2.19"
1516
]
1617
}

recipe/thirdparty/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ func GoogleWorkspaces(config tpmodels.GoogleWorkspacesConfig) tpmodels.TypeProvi
9898
return providers.GoogleWorkspaces(config)
9999
}
100100

101+
func Bitbucket(config tpmodels.BitbucketConfig) tpmodels.TypeProvider {
102+
return providers.Bitbucket(config)
103+
}
104+
105+
func GitLab(config tpmodels.GitLabConfig) tpmodels.TypeProvider {
106+
return providers.GitLab(config)
107+
}
108+
101109
func Google(config tpmodels.GoogleConfig) tpmodels.TypeProvider {
102110
return providers.Google(config)
103111
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
2+
*
3+
* This software is licensed under the Apache License, Version 2.0 (the
4+
* "License") as published by the Apache Software Foundation.
5+
*
6+
* You may not use this file except in compliance with the License. You may
7+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
* License for the specific language governing permissions and limitations
13+
* under the License.
14+
*/
15+
16+
package providers
17+
18+
import (
19+
"encoding/json"
20+
"net/http"
21+
"strings"
22+
23+
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
24+
"github.com/supertokens/supertokens-golang/supertokens"
25+
)
26+
27+
const bitbucketID = "bitbucket"
28+
29+
func Bitbucket(config tpmodels.BitbucketConfig) tpmodels.TypeProvider {
30+
return tpmodels.TypeProvider{
31+
ID: bitbucketID,
32+
Get: func(redirectURI, authCodeFromRequest *string, userContext supertokens.UserContext) tpmodels.TypeProviderGetResponse {
33+
accessTokenAPIURL := "https://bitbucket.org/site/oauth2/access_token"
34+
accessTokenAPIParams := map[string]string{
35+
"client_id": config.ClientID,
36+
"client_secret": config.ClientSecret,
37+
"grant_type": "authorization_code",
38+
}
39+
if authCodeFromRequest != nil {
40+
accessTokenAPIParams["code"] = *authCodeFromRequest
41+
}
42+
if redirectURI != nil {
43+
accessTokenAPIParams["redirect_uri"] = *redirectURI
44+
}
45+
46+
authorisationRedirectURL := "https://bitbucket.org/site/oauth2/authorize"
47+
scopes := []string{"account", "email"}
48+
if config.Scope != nil {
49+
scopes = config.Scope
50+
}
51+
52+
var additionalParams map[string]interface{} = nil
53+
if config.AuthorisationRedirect != nil && config.AuthorisationRedirect.Params != nil {
54+
additionalParams = config.AuthorisationRedirect.Params
55+
}
56+
57+
authorizationRedirectParams := map[string]interface{}{
58+
"scope": strings.Join(scopes, " "),
59+
"access_type": "offline",
60+
"response_type": "code",
61+
"client_id": config.ClientID,
62+
}
63+
for key, value := range additionalParams {
64+
authorizationRedirectParams[key] = value
65+
}
66+
67+
return tpmodels.TypeProviderGetResponse{
68+
AccessTokenAPI: tpmodels.AccessTokenAPI{
69+
URL: accessTokenAPIURL,
70+
Params: accessTokenAPIParams,
71+
},
72+
AuthorisationRedirect: tpmodels.AuthorisationRedirect{
73+
URL: authorisationRedirectURL,
74+
Params: authorizationRedirectParams,
75+
},
76+
GetProfileInfo: func(authCodeResponse interface{}, userContext supertokens.UserContext) (tpmodels.UserInfo, error) {
77+
authCodeResponseJson, err := json.Marshal(authCodeResponse)
78+
if err != nil {
79+
return tpmodels.UserInfo{}, err
80+
}
81+
var accessTokenAPIResponse bitbucketGetProfileInfoInput
82+
err = json.Unmarshal(authCodeResponseJson, &accessTokenAPIResponse)
83+
if err != nil {
84+
return tpmodels.UserInfo{}, err
85+
}
86+
accessToken := accessTokenAPIResponse.AccessToken
87+
authHeader := "Bearer " + accessToken
88+
response, err := getBitbucketAuthRequest(authHeader)
89+
if err != nil {
90+
return tpmodels.UserInfo{}, err
91+
}
92+
userInfo := response.(map[string]interface{})
93+
ID := userInfo["uuid"].(string)
94+
95+
emailResponse, err := getBitbucketEmailRequest(authHeader)
96+
if err != nil {
97+
return tpmodels.UserInfo{}, err
98+
}
99+
var email string
100+
var isVerified bool = false
101+
emailResponseInfo := emailResponse.(map[string]interface{})
102+
for _, emailInfo := range emailResponseInfo["values"].([]interface{}) {
103+
emailInfoMap := emailInfo.(map[string]interface{})
104+
if emailInfoMap["is_primary"].(bool) {
105+
email = emailInfoMap["email"].(string)
106+
isVerified = emailInfoMap["is_confirmed"].(bool)
107+
}
108+
}
109+
if email == "" {
110+
return tpmodels.UserInfo{
111+
ID: ID,
112+
}, nil
113+
}
114+
115+
return tpmodels.UserInfo{
116+
ID: ID,
117+
Email: &tpmodels.EmailStruct{
118+
ID: email,
119+
IsVerified: isVerified,
120+
},
121+
}, nil
122+
},
123+
GetClientId: func(userContext supertokens.UserContext) string {
124+
return config.ClientID
125+
},
126+
}
127+
},
128+
IsDefault: config.IsDefault,
129+
}
130+
}
131+
132+
func getBitbucketAuthRequest(authHeader string) (interface{}, error) {
133+
url := "https://api.bitbucket.org/2.0/user"
134+
req, err := http.NewRequest("GET", url, nil)
135+
if err != nil {
136+
return nil, err
137+
}
138+
req.Header.Add("Authorization", authHeader)
139+
return doGetRequest(req)
140+
}
141+
142+
func getBitbucketEmailRequest(authHeader string) (interface{}, error) {
143+
url := "https://api.bitbucket.org/2.0/user/emails"
144+
req, err := http.NewRequest("GET", url, nil)
145+
if err != nil {
146+
return nil, err
147+
}
148+
req.Header.Add("Authorization", authHeader)
149+
return doGetRequest(req)
150+
}
151+
152+
type bitbucketGetProfileInfoInput struct {
153+
AccessToken string `json:"access_token"`
154+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
2+
*
3+
* This software is licensed under the Apache License, Version 2.0 (the
4+
* "License") as published by the Apache Software Foundation.
5+
*
6+
* You may not use this file except in compliance with the License. You may
7+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
* License for the specific language governing permissions and limitations
13+
* under the License.
14+
*/
15+
16+
package providers
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
"net/http"
22+
"strings"
23+
24+
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
25+
"github.com/supertokens/supertokens-golang/supertokens"
26+
)
27+
28+
const gitlabID = "gitlab"
29+
30+
func GitLab(config tpmodels.GitLabConfig) tpmodels.TypeProvider {
31+
gitLabURL := "https://gitlab.com"
32+
if config.GitLabBaseURL != nil {
33+
url, err := supertokens.NewNormalisedURLDomain(*config.GitLabBaseURL)
34+
if err != nil {
35+
panic(err)
36+
}
37+
gitLabURL = url.GetAsStringDangerous()
38+
}
39+
return tpmodels.TypeProvider{
40+
ID: gitlabID,
41+
Get: func(redirectURI, authCodeFromRequest *string, userContext supertokens.UserContext) tpmodels.TypeProviderGetResponse {
42+
accessTokenAPIURL := gitLabURL + "/oauth/token"
43+
accessTokenAPIParams := map[string]string{
44+
"client_id": config.ClientID,
45+
"client_secret": config.ClientSecret,
46+
"grant_type": "authorization_code",
47+
}
48+
if authCodeFromRequest != nil {
49+
accessTokenAPIParams["code"] = *authCodeFromRequest
50+
}
51+
if redirectURI != nil {
52+
accessTokenAPIParams["redirect_uri"] = *redirectURI
53+
}
54+
55+
authorisationRedirectURL := gitLabURL + "/oauth/authorize"
56+
scopes := []string{"read_user"}
57+
if config.Scope != nil {
58+
scopes = config.Scope
59+
}
60+
61+
var additionalParams map[string]interface{} = nil
62+
if config.AuthorisationRedirect != nil && config.AuthorisationRedirect.Params != nil {
63+
additionalParams = config.AuthorisationRedirect.Params
64+
}
65+
66+
authorizationRedirectParams := map[string]interface{}{
67+
"scope": strings.Join(scopes, " "),
68+
"response_type": "code",
69+
"client_id": config.ClientID,
70+
}
71+
for key, value := range additionalParams {
72+
authorizationRedirectParams[key] = value
73+
}
74+
75+
return tpmodels.TypeProviderGetResponse{
76+
AccessTokenAPI: tpmodels.AccessTokenAPI{
77+
URL: accessTokenAPIURL,
78+
Params: accessTokenAPIParams,
79+
},
80+
AuthorisationRedirect: tpmodels.AuthorisationRedirect{
81+
URL: authorisationRedirectURL,
82+
Params: authorizationRedirectParams,
83+
},
84+
GetProfileInfo: func(authCodeResponse interface{}, userContext supertokens.UserContext) (tpmodels.UserInfo, error) {
85+
authCodeResponseJson, err := json.Marshal(authCodeResponse)
86+
if err != nil {
87+
return tpmodels.UserInfo{}, err
88+
}
89+
var accessTokenAPIResponse gitlabGetProfileInfoInput
90+
err = json.Unmarshal(authCodeResponseJson, &accessTokenAPIResponse)
91+
if err != nil {
92+
return tpmodels.UserInfo{}, err
93+
}
94+
accessToken := accessTokenAPIResponse.AccessToken
95+
authHeader := "Bearer " + accessToken
96+
response, err := getGitLabAuthRequest(gitLabURL, authHeader)
97+
if err != nil {
98+
return tpmodels.UserInfo{}, err
99+
}
100+
userInfo := response.(map[string]interface{})
101+
ID := fmt.Sprint(userInfo["id"]) // the id returned by gitlab is a number, so we convert to a string
102+
_, emailExists := userInfo["email"]
103+
if !emailExists {
104+
return tpmodels.UserInfo{
105+
ID: ID,
106+
}, nil
107+
}
108+
email := userInfo["email"].(string)
109+
var isVerified bool
110+
_, ok := userInfo["confirmed_at"]
111+
if ok && userInfo["confirmed_at"] != nil && userInfo["confirmed_at"].(string) != "" {
112+
isVerified = true
113+
} else {
114+
isVerified = false
115+
}
116+
return tpmodels.UserInfo{
117+
ID: ID,
118+
Email: &tpmodels.EmailStruct{
119+
ID: email,
120+
IsVerified: isVerified,
121+
},
122+
}, nil
123+
},
124+
GetClientId: func(userContext supertokens.UserContext) string {
125+
return config.ClientID
126+
},
127+
}
128+
},
129+
IsDefault: config.IsDefault,
130+
}
131+
}
132+
133+
func getGitLabAuthRequest(gitLabUrl string, authHeader string) (interface{}, error) {
134+
url := gitLabUrl + "/api/v4/user"
135+
req, err := http.NewRequest("GET", url, nil)
136+
if err != nil {
137+
return nil, err
138+
}
139+
req.Header.Add("Authorization", authHeader)
140+
return doGetRequest(req)
141+
}
142+
143+
type gitlabGetProfileInfoInput struct {
144+
AccessToken string `json:"access_token"`
145+
}

recipe/thirdparty/tpmodels/providers.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,27 @@ type GoogleConfig struct {
2525
IsDefault bool
2626
}
2727

28+
type BitbucketConfig struct {
29+
ClientID string
30+
ClientSecret string
31+
Scope []string
32+
AuthorisationRedirect *struct {
33+
Params map[string]interface{}
34+
}
35+
IsDefault bool
36+
}
37+
38+
type GitLabConfig struct {
39+
ClientID string
40+
ClientSecret string
41+
Scope []string
42+
AuthorisationRedirect *struct {
43+
Params map[string]interface{}
44+
}
45+
GitLabBaseURL *string
46+
IsDefault bool
47+
}
48+
2849
type GoogleWorkspacesConfig struct {
2950
ClientID string
3051
ClientSecret string

supertokens/constants.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ const (
2121
)
2222

2323
// VERSION current version of the lib
24-
const VERSION = "0.10.2"
24+
const VERSION = "0.10.3"
2525

2626
var (
27-
cdiSupported = []string{"2.8", "2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15", "2.16", "2.17", "2.18"}
27+
cdiSupported = []string{"2.8", "2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15", "2.16", "2.17", "2.18", "2.19"}
2828
)
2929

3030
const DashboardVersion = "0.4"

0 commit comments

Comments
 (0)