Skip to content

Commit 3d92612

Browse files
update: adds raw user info to thirdparty response
- BitBucket - Cleans up primary email logic - Moves `email` key from `from_id_token_payload` to `from_user_info_api` in `raw_user_info_from_provider` - Github - Changes `id` field access to be concrete to be in-line with API Spec - Adds raw user info to output - Makes primary email logic consistent with BitBucket - LinkedIn - Changes `sub` field access to be concrete to be in-line with API Spec - Adds raw user info to output Co-authored-by: Viraj Kanwade <[email protected]>
1 parent f1b9103 commit 3d92612

File tree

7 files changed

+69
-50
lines changed

7 files changed

+69
-50
lines changed

CHANGELOG.md

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [unreleased]
1010

11+
## [0.28.0]
12+
- Adds `raw_user_info_from_provider` to `UserInfo` data in LinkedIn and Github third-party recipes.
13+
- **[Breaking] Bitbucket third-party recipe**
14+
- Moves `email` from `from_id_token_payload` to `from_user_info_api` in `raw_user_info_from_provider`.
15+
- Keeps the API consistent with the Node SDK.
16+
- Migration:
17+
```
18+
- user_info.raw_user_info_from_provider.from_id_token_payload["email"]
19+
+ user_info.raw_user_info_from_provider.from_user_info_api["email"]
20+
```
21+
1122
## [0.27.0] - 2024-12-30
1223
1324
- Added OAuth2Provider recipe
@@ -277,7 +288,7 @@ async def change_email(req: ChangeEmailBody, session: SessionContainer = Depends
277288
# Update the email
278289
await update_email_or_password(
279290
session.get_recipe_user_id(),
280-
email,
291+
email,
281292
)
282293

283294
# ...
@@ -360,7 +371,7 @@ from supertokens_python.types import RecipeUserId
360371

361372
def functions_override(original_implementation: RecipeInterface):
362373
o_create_new_session = original_implementation.create_new_session
363-
374+
364375
async def n_create_new_session(
365376
user_id: str,
366377
recipe_user_id: RecipeUserId,
@@ -377,7 +388,7 @@ def functions_override(original_implementation: RecipeInterface):
377388
return await o_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context)
378389

379390
original_implementation.create_new_session = n_create_new_session
380-
391+
381392
return original_implementation
382393

383394
session.init(override=session.InputOverrideConfig(functions=functions_override))
@@ -395,7 +406,7 @@ from supertokens_python.types import RecipeUserId
395406

396407
def functions_override(original_implementation: RecipeInterface):
397408
o_create_new_session = original_implementation.create_new_session
398-
409+
399410
async def n_create_new_session(
400411
user_id: str,
401412
recipe_user_id: RecipeUserId,
@@ -412,7 +423,7 @@ def functions_override(original_implementation: RecipeInterface):
412423
return await o_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context)
413424

414425
original_implementation.create_new_session = n_create_new_session
415-
426+
416427
return original_implementation
417428

418429
session.init(override=session.InputOverrideConfig(functions=functions_override))
@@ -632,7 +643,7 @@ thirdparty.init(
632643
third_party_id="google",
633644
# rest of the config
634645
),
635-
646+
636647
# Add the following line to make this provider available in non-public tenants by default
637648
include_in_non_public_tenants_by_default=True
638649
),
@@ -641,7 +652,7 @@ thirdparty.init(
641652
third_party_id="github",
642653
# rest of the config
643654
),
644-
655+
645656
# Add the following line to make this provider available in non-public tenants by default
646657
include_in_non_public_tenants_by_default=True
647658
),
@@ -733,7 +744,7 @@ for tenant in tenants_res.tenants:
733744

734745
- The way to get user information has changed:
735746
- If you are using `get_users_by_email` from `thirdpartyemailpassword` recipe:
736-
747+
737748
Before:
738749
```python
739750
from supertokens_python.recipe.thirdpartyemailpassword.syncio import get_users_by_email
@@ -745,20 +756,20 @@ for tenant in tenants_res.tenants:
745756
```python
746757
from supertokens_python.recipe.thirdparty.syncio import get_users_by_email as get_users_by_email_third_party
747758
from supertokens_python.recipe.emailpassword.syncio import get_user_by_email as get_user_by_email_emailpassword
748-
759+
749760
third_party_user_info = get_users_by_email_third_party("public", "[email protected]")
750761

751762
email_password_user_info = get_user_by_email_emailpassword("public", "[email protected]")
752763

753764
if email_password_user_info is not None:
754765
print(email_password_user_info)
755-
766+
756767
if len(third_party_user_info) > 0:
757768
print(third_party_user_info)
758769
```
759770

760771
- If you are using `get_user_id` from `thirdpartyemailpassword` recipe:
761-
772+
762773
Before:
763774
```python
764775
from supertokens_python.recipe.thirdpartyemailpassword.syncio import get_user_by_id
@@ -783,9 +794,9 @@ for tenant in tenants_res.tenants:
783794
else:
784795
print(thirdparty_user)
785796
```
786-
797+
787798
- If you are using `get_users_by_email` from `thirdpartypasswordless` recipe:
788-
799+
789800
Before:
790801
```python
791802
from supertokens_python.recipe.thirdpartypasswordless.syncio import get_users_by_email
@@ -797,20 +808,20 @@ for tenant in tenants_res.tenants:
797808
```python
798809
from supertokens_python.recipe.thirdparty.syncio import get_users_by_email as get_users_by_email_third_party
799810
from supertokens_python.recipe.passwordless.syncio import get_user_by_email as get_user_by_email_passwordless
800-
811+
801812
third_party_user_info = get_users_by_email_third_party("public", "[email protected]")
802813

803814
passwordless_user_info = get_user_by_email_passwordless("public", "[email protected]")
804815

805816
if passwordless_user_info is not None:
806817
print(passwordless_user_info)
807-
818+
808819
if len(third_party_user_info) > 0:
809820
print(third_party_user_info)
810821
```
811822

812823
- If you are using `get_user_id` from `thirdpartypasswordless` recipe:
813-
824+
814825
Before:
815826
```python
816827
from supertokens_python.recipe.thirdpartypasswordless.syncio import get_user_by_id
@@ -1022,7 +1033,7 @@ With this update, verify_session will return a 401 error if it detects multiple
10221033
)
10231034
```
10241035

1025-
- In the session recipe, if there is an `UNAUTHORISED` or `TOKEN_THEFT_DETECTED` error, the session tokens are cleared in the response regardless of if you have provided your own `error_handlers` in `session.init`
1036+
- In the session recipe, if there is an `UNAUTHORISED` or `TOKEN_THEFT_DETECTED` error, the session tokens are cleared in the response regardless of if you have provided your own `error_handlers` in `session.init`
10261037

10271038
## [0.17.0] - 2023-11-14
10281039
- Fixes `create_reset_password_link` in the emailpassword recipe wherein we passed the `rid` instead of the token in the link

setup.py

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

8484
setup(
8585
name="supertokens_python",
86-
version="0.27.0",
86+
version="0.28.0",
8787
author="SuperTokens",
8888
license="Apache 2.0",
8989
author_email="[email protected]",

supertokens_python/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from __future__ import annotations
1616

1717
SUPPORTED_CDI_VERSIONS = ["5.2"]
18-
VERSION = "0.27.0"
18+
VERSION = "0.28.0"
1919
TELEMETRY = "/telemetry"
2020
USER_COUNT = "/users/count"
2121
USER_DELETE = "/user/remove"

supertokens_python/recipe/thirdparty/providers/bitbucket.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,13 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Dict, Any, Optional
18-
1917
from supertokens_python.recipe.thirdparty.provider import (
2018
ProviderConfigForClient,
2119
ProviderInput,
2220
Provider,
2321
)
22+
from typing import Dict, Any, Optional
2423
from .custom import GenericProvider, NewProvider
25-
2624
from .utils import do_get_request
2725
from ..types import RawUserInfoFromProvider, UserInfo, UserInfoEmail
2826

@@ -66,25 +64,22 @@ async def get_user_info(
6664
headers=headers,
6765
)
6866

69-
if raw_user_info_from_provider.from_id_token_payload is None:
70-
# Actually this should never happen but python type
71-
# checker is not agreeing so doing this:
72-
raw_user_info_from_provider.from_id_token_payload = {}
73-
74-
raw_user_info_from_provider.from_id_token_payload["email"] = (
75-
user_info_from_email
76-
)
67+
raw_user_info_from_provider.from_user_info_api["email"] = user_info_from_email
7768

78-
email = None
79-
is_verified = False
69+
# Get the primary email from the Email response
70+
# Create an object if primary email found
71+
primary_email_info: UserInfoEmail | None = None
8072
for email_info in user_info_from_email["values"]:
8173
if email_info["is_primary"]:
82-
email = email_info["email"]
83-
is_verified = email_info["is_confirmed"]
74+
primary_email_info = UserInfoEmail(
75+
email=email_info["email"],
76+
is_verified=email_info["is_confirmed"],
77+
)
78+
break
8479

8580
return UserInfo(
8681
third_party_user_id=raw_user_info_from_provider.from_user_info_api["uuid"],
87-
email=None if email is None else UserInfoEmail(email, is_verified),
82+
email=primary_email_info,
8883
raw_user_info_from_provider=raw_user_info_from_provider,
8984
)
9085

supertokens_python/recipe/thirdparty/providers/github.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
)
2323
from supertokens_python.recipe.thirdparty.types import UserInfo, UserInfoEmail
2424

25-
from .custom import GenericProvider, NewProvider
2625
from ..provider import Provider, ProviderConfigForClient, ProviderInput
26+
from ..types import RawUserInfoFromProvider
27+
from .custom import GenericProvider, NewProvider
2728

2829

2930
class GithubImpl(GenericProvider):
@@ -45,24 +46,31 @@ async def get_user_info(
4546
"Accept": "application/vnd.github.v3+json",
4647
}
4748

48-
raw_response = {}
49-
50-
email_info: List[Any] = await do_get_request("https://api.github.com/user/emails", headers=headers) # type: ignore
49+
# https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28
5150
user_info = await do_get_request("https://api.github.com/user", headers=headers)
52-
53-
raw_response["emails"] = email_info
54-
raw_response["user"] = user_info
51+
user_email_info: List[Any] = await do_get_request("https://api.github.com/user/emails", headers=headers) # type: ignore
52+
53+
raw_user_info_from_provider = RawUserInfoFromProvider({}, {})
54+
raw_user_info_from_provider.from_user_info_api = user_info
55+
raw_user_info_from_provider.from_user_info_api["emails"] = user_email_info
56+
57+
# Get the primary email from the Email response
58+
# Create an object if primary email found
59+
primary_email_info: UserInfoEmail | None = None
60+
for email_detail in user_email_info:
61+
if email_detail["primary"]:
62+
primary_email_info = UserInfoEmail(
63+
email=email_detail["email"],
64+
is_verified=email_detail["verified"],
65+
)
66+
break
5567

5668
result = UserInfo(
57-
third_party_user_id=str(user_info.get("id")),
69+
third_party_user_id=str(user_info["id"]),
70+
email=primary_email_info,
71+
raw_user_info_from_provider=raw_user_info_from_provider,
5872
)
5973

60-
for info in email_info:
61-
if info.get("primary"):
62-
result.email = UserInfoEmail(
63-
email=info.get("email"), is_verified=info.get("verified")
64-
)
65-
6674
return result
6775

6876

supertokens_python/recipe/thirdparty/providers/linkedin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ async def get_user_info(
6767
raw_user_info_from_provider.from_user_info_api = user_info
6868

6969
return UserInfo(
70-
third_party_user_id=raw_user_info_from_provider.from_user_info_api.get("sub"), # type: ignore
70+
third_party_user_id=raw_user_info_from_provider.from_user_info_api["sub"], # type: ignore
7171
email=UserInfoEmail(
7272
email=raw_user_info_from_provider.from_user_info_api.get("email"), # type: ignore
7373
is_verified=raw_user_info_from_provider.from_user_info_api.get("email_verified"), # type: ignore
7474
),
75+
raw_user_info_from_provider=raw_user_info_from_provider,
7576
)
7677

7778

supertokens_python/recipe/thirdparty/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ def to_json(self) -> Dict[str, Any]:
5858

5959

6060
class UserInfoEmail:
61+
"""
62+
Details about the user's - generally primary - email.
63+
"""
64+
6165
def __init__(self, email: str, is_verified: bool):
6266
self.id: str = email
6367
self.is_verified: bool = is_verified

0 commit comments

Comments
 (0)