Skip to content

Commit 968c756

Browse files
namsnathmavwolverinesattvikc
authored
update: adds raw user info to thirdparty response (#558)
- 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]> Co-authored-by: Sattvik Chakravarthy <[email protected]>
1 parent 251dae1 commit 968c756

File tree

5 files changed

+65
-48
lines changed

5 files changed

+65
-48
lines changed

CHANGELOG.md

+26-17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
## [unreleased]
1010

1111
## [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+
```
1221
- Updates timestamps to use UTC instead of GMT as the timezone
1322
1423
## [0.27.0] - 2024-12-30
@@ -280,7 +289,7 @@ async def change_email(req: ChangeEmailBody, session: SessionContainer = Depends
280289
# Update the email
281290
await update_email_or_password(
282291
session.get_recipe_user_id(),
283-
email,
292+
email,
284293
)
285294

286295
# ...
@@ -363,7 +372,7 @@ from supertokens_python.types import RecipeUserId
363372

364373
def functions_override(original_implementation: RecipeInterface):
365374
o_create_new_session = original_implementation.create_new_session
366-
375+
367376
async def n_create_new_session(
368377
user_id: str,
369378
recipe_user_id: RecipeUserId,
@@ -380,7 +389,7 @@ def functions_override(original_implementation: RecipeInterface):
380389
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)
381390

382391
original_implementation.create_new_session = n_create_new_session
383-
392+
384393
return original_implementation
385394

386395
session.init(override=session.InputOverrideConfig(functions=functions_override))
@@ -398,7 +407,7 @@ from supertokens_python.types import RecipeUserId
398407

399408
def functions_override(original_implementation: RecipeInterface):
400409
o_create_new_session = original_implementation.create_new_session
401-
410+
402411
async def n_create_new_session(
403412
user_id: str,
404413
recipe_user_id: RecipeUserId,
@@ -415,7 +424,7 @@ def functions_override(original_implementation: RecipeInterface):
415424
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)
416425

417426
original_implementation.create_new_session = n_create_new_session
418-
427+
419428
return original_implementation
420429

421430
session.init(override=session.InputOverrideConfig(functions=functions_override))
@@ -635,7 +644,7 @@ thirdparty.init(
635644
third_party_id="google",
636645
# rest of the config
637646
),
638-
647+
639648
# Add the following line to make this provider available in non-public tenants by default
640649
include_in_non_public_tenants_by_default=True
641650
),
@@ -644,7 +653,7 @@ thirdparty.init(
644653
third_party_id="github",
645654
# rest of the config
646655
),
647-
656+
648657
# Add the following line to make this provider available in non-public tenants by default
649658
include_in_non_public_tenants_by_default=True
650659
),
@@ -736,7 +745,7 @@ for tenant in tenants_res.tenants:
736745

737746
- The way to get user information has changed:
738747
- If you are using `get_users_by_email` from `thirdpartyemailpassword` recipe:
739-
748+
740749
Before:
741750
```python
742751
from supertokens_python.recipe.thirdpartyemailpassword.syncio import get_users_by_email
@@ -748,20 +757,20 @@ for tenant in tenants_res.tenants:
748757
```python
749758
from supertokens_python.recipe.thirdparty.syncio import get_users_by_email as get_users_by_email_third_party
750759
from supertokens_python.recipe.emailpassword.syncio import get_user_by_email as get_user_by_email_emailpassword
751-
760+
752761
third_party_user_info = get_users_by_email_third_party("public", "[email protected]")
753762

754763
email_password_user_info = get_user_by_email_emailpassword("public", "[email protected]")
755764

756765
if email_password_user_info is not None:
757766
print(email_password_user_info)
758-
767+
759768
if len(third_party_user_info) > 0:
760769
print(third_party_user_info)
761770
```
762771

763772
- If you are using `get_user_id` from `thirdpartyemailpassword` recipe:
764-
773+
765774
Before:
766775
```python
767776
from supertokens_python.recipe.thirdpartyemailpassword.syncio import get_user_by_id
@@ -786,9 +795,9 @@ for tenant in tenants_res.tenants:
786795
else:
787796
print(thirdparty_user)
788797
```
789-
798+
790799
- If you are using `get_users_by_email` from `thirdpartypasswordless` recipe:
791-
800+
792801
Before:
793802
```python
794803
from supertokens_python.recipe.thirdpartypasswordless.syncio import get_users_by_email
@@ -800,20 +809,20 @@ for tenant in tenants_res.tenants:
800809
```python
801810
from supertokens_python.recipe.thirdparty.syncio import get_users_by_email as get_users_by_email_third_party
802811
from supertokens_python.recipe.passwordless.syncio import get_user_by_email as get_user_by_email_passwordless
803-
812+
804813
third_party_user_info = get_users_by_email_third_party("public", "[email protected]")
805814

806815
passwordless_user_info = get_user_by_email_passwordless("public", "[email protected]")
807816

808817
if passwordless_user_info is not None:
809818
print(passwordless_user_info)
810-
819+
811820
if len(third_party_user_info) > 0:
812821
print(third_party_user_info)
813822
```
814823

815824
- If you are using `get_user_id` from `thirdpartypasswordless` recipe:
816-
825+
817826
Before:
818827
```python
819828
from supertokens_python.recipe.thirdpartypasswordless.syncio import get_user_by_id
@@ -1025,7 +1034,7 @@ With this update, verify_session will return a 401 error if it detects multiple
10251034
)
10261035
```
10271036

1028-
- 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`
1037+
- 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`
10291038

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

supertokens_python/recipe/thirdparty/providers/bitbucket.py

+11-16
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

+22-14
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

+2-1
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

+4
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)