Skip to content

Commit 1642bcb

Browse files
Merge pull request #300 from supertokens/new-providers
New providers
2 parents d62a4b1 + b15723e commit 1642bcb

File tree

11 files changed

+237
-6
lines changed

11 files changed

+237
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## unreleased
99

10+
## [0.12.4] - 2023-03-29
1011
### Changed
11-
1212
- Update all example apps to initialise dashboard recipe
1313

14+
### Added
15+
- Login with gitlab (single tenant only) and bitbucket
16+
1417
## [0.12.3] - 2023-02-27
1518
- Adds APIs and logic to the dashboard recipe to enable email password based login
1619
## [0.12.2] - 2023-02-23

coreDriverInterfaceSupported.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"2.15",
1111
"2.16",
1212
"2.17",
13-
"2.18"
13+
"2.18",
14+
"2.19"
1415
]
15-
}
16+
}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
setup(
7272
name="supertokens_python",
73-
version="0.12.3",
73+
version="0.12.4",
7474
author="SuperTokens",
7575
license="Apache 2.0",
7676
author_email="[email protected]",

src/supertokens-python

Lines changed: 0 additions & 1 deletion
This file was deleted.

supertokens_python/constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
"2.16",
2323
"2.17",
2424
"2.18",
25+
"2.19",
2526
]
26-
VERSION = "0.12.3"
27+
VERSION = "0.12.4"
2728
TELEMETRY = "/telemetry"
2829
USER_COUNT = "/users/count"
2930
USER_DELETE = "/user/remove"

supertokens_python/recipe/thirdparty/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
Github = providers.Github
3030
Google = providers.Google
3131
GoogleWorkspaces = providers.GoogleWorkspaces
32+
Bitbucket = providers.Bitbucket
33+
GitLab = providers.GitLab
3234
exceptions = ex
3335

3436
if TYPE_CHECKING:

supertokens_python/recipe/thirdparty/providers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@
1717
from .github import Github # type: ignore
1818
from .google import Google # type: ignore
1919
from .google_workspaces import GoogleWorkspaces # type: ignore
20+
from .bitbucket import Bitbucket # type: ignore
21+
from .gitlab import GitLab # type: ignore
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright (c) 2023, 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+
from __future__ import annotations
16+
17+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union
18+
19+
from httpx import AsyncClient
20+
from supertokens_python.recipe.thirdparty.provider import Provider
21+
from supertokens_python.recipe.thirdparty.types import (
22+
AccessTokenAPI,
23+
AuthorisationRedirectAPI,
24+
UserInfo,
25+
UserInfoEmail,
26+
)
27+
28+
if TYPE_CHECKING:
29+
from supertokens_python.framework.request import BaseRequest
30+
31+
32+
class Bitbucket(Provider):
33+
def __init__(
34+
self,
35+
client_id: str,
36+
client_secret: str,
37+
scope: Union[None, List[str]] = None,
38+
authorisation_redirect: Union[
39+
None, Dict[str, Union[str, Callable[[BaseRequest], str]]]
40+
] = None,
41+
is_default: bool = False,
42+
):
43+
super().__init__("bitbucket", is_default)
44+
self.client_id = client_id
45+
self.client_secret = client_secret
46+
self.scopes = ["account", "email"] if scope is None else list(set(scope))
47+
self.access_token_api_url = "https://bitbucket.org/site/oauth2/access_token"
48+
self.authorisation_redirect_url = "https://bitbucket.org/site/oauth2/authorize"
49+
self.authorisation_redirect_params = {}
50+
if authorisation_redirect is not None:
51+
self.authorisation_redirect_params = authorisation_redirect
52+
53+
async def get_profile_info(
54+
self, auth_code_response: Dict[str, Any], user_context: Dict[str, Any]
55+
) -> UserInfo:
56+
access_token: str = auth_code_response["access_token"]
57+
headers = {"Authorization": f"Bearer {access_token}"}
58+
async with AsyncClient() as client:
59+
response = await client.get( # type: ignore
60+
url="https://api.bitbucket.org/2.0/user",
61+
headers=headers,
62+
)
63+
user_info = response.json()
64+
user_id = user_info["uuid"]
65+
email_res = await client.get( # type: ignore
66+
url="https://api.bitbucket.org/2.0/user/emails",
67+
headers=headers,
68+
)
69+
email_data = email_res.json()
70+
email = None
71+
is_verified = False
72+
for email_info in email_data["values"]:
73+
if email_info.get("is_primary"):
74+
email = email_info["email"]
75+
is_verified = email_info["is_confirmed"]
76+
break
77+
78+
if email is None:
79+
return UserInfo(user_id)
80+
return UserInfo(user_id, UserInfoEmail(email, is_verified))
81+
82+
def get_authorisation_redirect_api_info(
83+
self, user_context: Dict[str, Any]
84+
) -> AuthorisationRedirectAPI:
85+
params = {
86+
"scope": " ".join(self.scopes),
87+
"response_type": "code",
88+
"client_id": self.client_id,
89+
"access_type": "offline",
90+
**self.authorisation_redirect_params,
91+
}
92+
return AuthorisationRedirectAPI(self.authorisation_redirect_url, params)
93+
94+
def get_access_token_api_info(
95+
self,
96+
redirect_uri: str,
97+
auth_code_from_request: str,
98+
user_context: Dict[str, Any],
99+
) -> AccessTokenAPI:
100+
params = {
101+
"client_id": self.client_id,
102+
"client_secret": self.client_secret,
103+
"grant_type": "authorization_code",
104+
"code": auth_code_from_request,
105+
"redirect_uri": redirect_uri,
106+
}
107+
return AccessTokenAPI(self.access_token_api_url, params)
108+
109+
def get_redirect_uri(self, user_context: Dict[str, Any]) -> Union[None, str]:
110+
return None
111+
112+
def get_client_id(self, user_context: Dict[str, Any]) -> str:
113+
return self.client_id
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright (c) 2023, 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+
from __future__ import annotations
16+
17+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union
18+
19+
from supertokens_python.normalised_url_domain import NormalisedURLDomain
20+
21+
from httpx import AsyncClient
22+
from supertokens_python.recipe.thirdparty.provider import Provider
23+
from supertokens_python.recipe.thirdparty.types import (
24+
AccessTokenAPI,
25+
AuthorisationRedirectAPI,
26+
UserInfo,
27+
UserInfoEmail,
28+
)
29+
30+
if TYPE_CHECKING:
31+
from supertokens_python.framework.request import BaseRequest
32+
33+
34+
class GitLab(Provider):
35+
def __init__(
36+
self,
37+
client_id: str,
38+
client_secret: str,
39+
scope: Union[None, List[str]] = None,
40+
authorisation_redirect: Union[
41+
None, Dict[str, Union[str, Callable[[BaseRequest], str]]]
42+
] = None,
43+
gitlab_base_url: str = "https://gitlab.com",
44+
is_default: bool = False,
45+
):
46+
super().__init__("gitlab", is_default)
47+
default_scopes = ["read_user"]
48+
if scope is None:
49+
scope = default_scopes
50+
self.client_id = client_id
51+
self.client_secret = client_secret
52+
self.scopes = list(set(scope))
53+
gitlab_base_url = NormalisedURLDomain(gitlab_base_url).get_as_string_dangerous()
54+
self.gitlab_base_url = gitlab_base_url
55+
self.access_token_api_url = f"{gitlab_base_url}/oauth/token"
56+
self.authorisation_redirect_url = f"{gitlab_base_url}/oauth/authorize"
57+
self.authorisation_redirect_params = {}
58+
if authorisation_redirect is not None:
59+
self.authorisation_redirect_params = authorisation_redirect
60+
61+
async def get_profile_info(
62+
self, auth_code_response: Dict[str, Any], user_context: Dict[str, Any]
63+
) -> UserInfo:
64+
access_token: str = auth_code_response["access_token"]
65+
headers = {"Authorization": f"Bearer {access_token}"}
66+
async with AsyncClient() as client:
67+
response = await client.get(f"{self.gitlab_base_url}/api/v4/user", headers=headers) # type: ignore
68+
user_info = response.json()
69+
user_id = str(user_info["id"])
70+
email = user_info.get("email")
71+
if email is None:
72+
return UserInfo(user_id)
73+
is_email_verified = user_info.get("confirmed_at") is not None
74+
return UserInfo(user_id, UserInfoEmail(email, is_email_verified))
75+
76+
def get_authorisation_redirect_api_info(
77+
self, user_context: Dict[str, Any]
78+
) -> AuthorisationRedirectAPI:
79+
params = {
80+
"scope": " ".join(self.scopes),
81+
"response_type": "code",
82+
"client_id": self.client_id,
83+
**self.authorisation_redirect_params,
84+
}
85+
return AuthorisationRedirectAPI(self.authorisation_redirect_url, params)
86+
87+
def get_access_token_api_info(
88+
self,
89+
redirect_uri: str,
90+
auth_code_from_request: str,
91+
user_context: Dict[str, Any],
92+
) -> AccessTokenAPI:
93+
params = {
94+
"client_id": self.client_id,
95+
"client_secret": self.client_secret,
96+
"grant_type": "authorization_code",
97+
"code": auth_code_from_request,
98+
"redirect_uri": redirect_uri,
99+
}
100+
return AccessTokenAPI(self.access_token_api_url, params)
101+
102+
def get_redirect_uri(self, user_context: Dict[str, Any]) -> Union[None, str]:
103+
return None
104+
105+
def get_client_id(self, user_context: Dict[str, Any]) -> str:
106+
return self.client_id

supertokens_python/recipe/thirdpartyemailpassword/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
Github = thirdparty.Github
3636
Google = thirdparty.Google
3737
GoogleWorkspaces = thirdparty.GoogleWorkspaces
38+
Bitbucket = thirdparty.Bitbucket
39+
GitLab = thirdparty.GitLab
3840
SMTPService = emaildelivery_services.SMTPService
3941

4042
if TYPE_CHECKING:

0 commit comments

Comments
 (0)