Skip to content

Commit df5994f

Browse files
Merge pull request #223 from supertokens/test/session-claims
feat: Session claims (test+changelog+example)
2 parents a000d3c + 0283c00 commit df5994f

File tree

16 files changed

+862
-48
lines changed

16 files changed

+862
-48
lines changed

CHANGELOG.md

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

77
## [unreleased]
88

9+
### Changes
10+
11+
- Made the `email` parameter option in `unverify_email`, `revoke_email_verification_tokens`, `is_email_verified`, `verify_email_using_token`, `create_email_verification_token` of the `EmailVerification` recipe.
12+
13+
### Added
14+
15+
- Added support for session claims with related interfaces and classes.
16+
- Added `on_invalid_claim` optional error handler to send InvalidClaim error responses.
17+
- Added `INVALID_CLAIMS` (`InvalidClaimError`) to `SessionErrors`.
18+
- Added `invalid_claim_status_code` optional config to set the status code of InvalidClaim errors.
19+
- Added `override_global_claim_validators` as param of `get_session` and `verify_session`.
20+
- Added `merge_into_access_token_payload` to the Session recipe and session objects which should be preferred to the now deprecated `update_access_token_payload`.
21+
- Added `EmailVerificationClaim`, `UserRoleClaim` and `PermissionClaim`. These claims are now added to the access token payload by default by their respective recipes.
22+
- Added `assert_claims`, `validate_claims_for_session_handle`, `validate_claims_in_jwt_payload` to the Session recipe to support validation of the newly added claims.
23+
- Added `fetch_and_set_claim`, `get_claim_value`, `set_claim_value` and `remove_claim` to the Session recipe to manage claims.
24+
- Added `assert_claims`, `fetch_and_set_claim`, `get_claim_value`, `set_claim_value` and `remove_claim` to session objects to manage claims.
25+
- Added session to the input of `generate_email_verify_token_post`, `verify_email_post`, `is_email_verified_get`.
26+
- Adds default userContext for verifySession calls that contains the request object.
27+
28+
### Breaking Changes
29+
- Changed `sign_in_up` third party recipe function to accept just the email as `str` (removed `email_verified: bool`).
30+
- The frontend SDK should be updated to a version supporting session claims!
31+
- supertokens-auth-react: >= 0.25.0
32+
- supertokens-web-js: >= 0.2.0
33+
- `EmailVerification` recipe is now not initialized as part of auth recipes, it should be added to the `recipe_list` directly instead using `emailverification.init()`.
34+
- Email verification related overrides (`email_verification_feature` attr of `override`) moved from auth recipes into the `EmailVerification` recipe config.
35+
- Email verification related configs (`email_verification_feature` attr) moved from auth recipes into the `EmailVerification` config object root.
36+
- ThirdParty recipe no longer takes `email_delivery` config. use `emailverification` recipe's `email_delivery` instead.
37+
- Moved email verification related configs from the `email_delivery` config of auth recipes into a separate `EmailVerification` email delivery config.
38+
- Updated return type of `get_email_for_user_id` in the `EmailVerification` recipe config. It should now return an object with status.
39+
- Removed `get_reset_password_url`, `get_email_verification_url`, `get_link_domain_and_path`. Changing these urls can be done in the email delivery configs instead.
40+
- Removed `unverify_email`, `revoke_email_verification_tokens`, `is_email_verified`, `verify_email_using_token` and `create_email_verification_token` from auth recipes. These should be called on the `EmailVerification` recipe instead.
41+
- Changed function signature for email verification APIs to accept a session as an input.
42+
- Changed Session API interface functions:
43+
- `refresh_post` now returns a Session container object.
44+
- `sign_out_post` now takes in an optional session object as a parameter.
45+
46+
### Migration
47+
Before:
48+
```python
49+
from supertokens_python import init, SupertokensConfig, InputAppInfo
50+
from supertokens_python.recipe import emailpassword
51+
from supertokens_python.recipe.emailverification.utils import OverrideConfig
52+
53+
init(
54+
supertokens_config=SupertokensConfig("..."),
55+
app_info=InputAppInfo("..."),
56+
framework="...",
57+
recipe_list=[
58+
emailpassword.init(
59+
# these options should be moved into the EmailVerification config:
60+
email_verification_feature=emailpassword.InputEmailVerificationConfig("..."),
61+
override=emailpassword.InputOverrideConfig(
62+
email_verification_feature=OverrideConfig(
63+
# these overrides should be moved into the EmailVerification overrides
64+
"..."
65+
)
66+
),
67+
),
68+
],
69+
)
70+
```
71+
72+
After the update:
73+
74+
```python
75+
from supertokens_python import init, SupertokensConfig, InputAppInfo
76+
from supertokens_python.recipe import emailpassword, emailverification
77+
78+
init(
79+
supertokens_config=SupertokensConfig("..."),
80+
app_info=InputAppInfo("..."),
81+
framework="...",
82+
recipe_list=[
83+
emailverification.init(
84+
"...", # EmailVerification config
85+
override=emailverification.OverrideConfig(
86+
# overrides
87+
"..."
88+
),
89+
),
90+
emailpassword.init(),
91+
],
92+
)
93+
```
94+
95+
#### Passwordless users and email verification
96+
97+
If you turn on email verification your email-based passwordless users may be redirected to an email verification screen in their existing session.
98+
Logging out and logging in again will solve this problem or they could click the link in the email to verify themselves.
99+
100+
You can avoid this by running a script that will:
101+
102+
1. list all users of passwordless
103+
2. create an emailverification token for each of them if they have email addresses
104+
3. user the token to verify their address
105+
106+
Something similar to this script:
107+
108+
```python
109+
from supertokens_python import init, SupertokensConfig, InputAppInfo
110+
from supertokens_python.recipe import passwordless, emailverification, session
111+
from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig
112+
113+
114+
from supertokens_python.syncio import get_users_newest_first
115+
from supertokens_python.recipe.emailverification.syncio import create_email_verification_token, verify_email_using_token
116+
from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult
117+
118+
init(
119+
supertokens_config=SupertokensConfig("http://localhost:3567"),
120+
app_info=InputAppInfo(
121+
app_name="SuperTokens Demo",
122+
api_domain="https://api.supertokens.io",
123+
website_domain="supertokens.io",
124+
),
125+
framework="fastapi",
126+
recipe_list=[
127+
emailverification.init("REQUIRED"),
128+
passwordless.init(
129+
contact_config=ContactEmailOrPhoneConfig(),
130+
flow_type="USER_INPUT_CODE_AND_MAGIC_LINK",
131+
),
132+
session.init(),
133+
],
134+
)
135+
136+
def verify_email_for_passwordless_users():
137+
pagination_token = None
138+
done = False
139+
140+
while not done:
141+
res = get_users_newest_first(
142+
limit=100,
143+
pagination_token=pagination_token,
144+
include_recipe_ids=["passwordless"]
145+
)
146+
147+
for user in res.users:
148+
if user.email is not None:
149+
token_res = create_email_verification_token(user.user_id, user.email)
150+
if isinstance(token_res, CreateEmailVerificationTokenOkResult):
151+
verify_email_using_token(token_res.token)
152+
153+
done = res.next_pagination_token is None
154+
if not done:
155+
pagination_token = res.next_pagination_token
156+
157+
verify_email_for_passwordless_users()
158+
```
159+
160+
#### User roles
161+
162+
The `UserRoles` recipe now adds role and permission information into the access token payload by default. If you are already doing this manually, this will result in duplicate data in the access token.
163+
164+
- You can disable this behaviour by setting `skip_adding_roles_to_access_token` and `skip_adding_permissions_to_access_token` to true in the recipe init.
165+
- Check how to use the new claims in the updated guide: https://supertokens.com/docs/userroles/protecting-routes
166+
167+
9168
## [0.10.4] - 2022-08-30
10169
## Features:
11170
- Add support for User ID Mapping using `create_user_id_mapping`, `get_user_id_mapping`, `delete_user_id_mapping`, `update_or_delete_user_id_mapping` functions

examples/with-django/with-thirdpartyemailpassword/project/settings.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
get_all_cors_headers,
2323
init,
2424
)
25-
from supertokens_python.recipe import session, thirdpartyemailpassword
25+
from supertokens_python.recipe import (
26+
session,
27+
thirdpartyemailpassword,
28+
emailverification,
29+
)
2630
from supertokens_python.recipe.thirdpartyemailpassword import (
2731
Apple,
2832
Discord,
@@ -62,6 +66,7 @@ def get_website_domain():
6266
mode="wsgi",
6367
recipe_list=[
6468
session.init(),
69+
emailverification.init("REQUIRED"),
6570
thirdpartyemailpassword.init(
6671
providers=[
6772
Google(

examples/with-fastapi/with-thirdpartyemailpassword/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
init,
1414
)
1515
from supertokens_python.framework.fastapi import get_middleware
16-
from supertokens_python.recipe import session, thirdpartyemailpassword
16+
from supertokens_python.recipe import (
17+
session,
18+
thirdpartyemailpassword,
19+
emailverification,
20+
)
1721
from supertokens_python.recipe.session import SessionContainer
1822
from supertokens_python.recipe.session.framework.fastapi import verify_session
1923
from supertokens_python.recipe.thirdpartyemailpassword import (
@@ -52,6 +56,7 @@ def get_website_domain():
5256
framework="fastapi",
5357
recipe_list=[
5458
session.init(),
59+
emailverification.init("REQUIRED"),
5560
thirdpartyemailpassword.init(
5661
providers=[
5762
Google(

examples/with-flask/with-thirdpartyemailpassword/app.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
init,
1111
)
1212
from supertokens_python.framework.flask import Middleware
13-
from supertokens_python.recipe import session, thirdpartyemailpassword
13+
from supertokens_python.recipe import (
14+
session,
15+
thirdpartyemailpassword,
16+
emailverification,
17+
)
1418
from supertokens_python.recipe.session.framework.flask import verify_session
1519
from supertokens_python.recipe.thirdpartyemailpassword import (
1620
Apple,
@@ -45,6 +49,7 @@ def get_website_domain():
4549
framework="flask",
4650
recipe_list=[
4751
session.init(),
52+
emailverification.init("REQUIRED"),
4853
thirdpartyemailpassword.init(
4954
providers=[
5055
Google(

frontendDriverInterfaceSupported.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_comment": "contains a list of frontend-driver interfaces branch names that this core supports",
33
"versions": [
4-
"1.14"
4+
"1.14", "1.15"
55
]
66
}

tests/sessions/claims/test_assert_claims.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from unittest.mock import patch
44

55
from pytest import mark
6+
from supertokens_python.recipe import session
7+
8+
from supertokens_python.recipe.session import SessionRecipe
69
from supertokens_python.recipe.session.claims import PrimitiveClaim
710
from supertokens_python.recipe.session.interfaces import (
811
JSONObject,
@@ -11,17 +14,26 @@
1114
SessionClaim,
1215
)
1316
from supertokens_python.recipe.session.session_class import Session
14-
from tests.utils import AsyncMock
17+
from supertokens_python import init
18+
from tests.utils import setup_function, teardown_function, start_st, st_init_common_args
19+
20+
_ = setup_function # type:ignore
21+
_ = teardown_function # type:ignore
1522

1623
_T = TypeVar("_T")
1724

1825
pytestmark = mark.asyncio
1926

2027

2128
async def test_should_not_throw_for_empty_array():
22-
recipe_implementation_mock = AsyncMock()
23-
session = Session(
24-
recipe_implementation_mock,
29+
st_args = {**st_init_common_args, "recipe_list": [session.init()]}
30+
init(**st_args) # type:ignore
31+
start_st()
32+
33+
s = SessionRecipe.get_instance()
34+
35+
user_session = Session(
36+
s.recipe_implementation,
2537
"test_access_token",
2638
"test_session_handle",
2739
"test_user_id",
@@ -30,17 +42,22 @@ async def test_should_not_throw_for_empty_array():
3042
with patch.object(
3143
Session,
3244
"update_access_token_payload",
33-
wraps=session.update_access_token_payload,
45+
wraps=user_session.update_access_token_payload,
3446
) as mock:
35-
await session.assert_claims([])
47+
await user_session.assert_claims([])
3648
mock.assert_not_called()
3749

3850

3951
async def test_should_call_validate_with_the_same_payload_object():
40-
recipe_implementation_mock = AsyncMock()
52+
st_args = {**st_init_common_args, "recipe_list": [session.init()]}
53+
init(**st_args) # type:ignore
54+
start_st()
55+
56+
s = SessionRecipe.get_instance()
57+
4158
payload = {"custom-key": "custom-value"}
42-
session = Session(
43-
recipe_implementation_mock,
59+
user_session = Session(
60+
s.recipe_implementation,
4461
"test_access_token",
4562
"test_session_handle",
4663
"test_user_id",
@@ -74,9 +91,9 @@ def should_refetch(self, payload: JSONObject, user_context: Dict[str, Any]):
7491
with patch.object(
7592
Session,
7693
"update_access_token_payload",
77-
wraps=session.update_access_token_payload,
94+
wraps=user_session.update_access_token_payload,
7895
) as mock:
79-
await session.assert_claims([dummy_claim.validators.dummy_claim_validator]) # type: ignore
96+
await user_session.assert_claims([dummy_claim.validators.dummy_claim_validator]) # type: ignore
8097

8198
assert dummy_claim_validator.validate_calls == {json.dumps(payload): 1}
8299
mock.assert_not_called()

tests/sessions/claims/test_create_new_session.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
from supertokens_python.framework import BaseRequest
55
from supertokens_python.recipe import session
66
from supertokens_python.recipe.session.asyncio import create_new_session
7-
from tests.utils import setup_function, teardown_function, start_st, min_api_version
8-
from .utils import (
7+
from tests.utils import (
8+
setup_function,
9+
teardown_function,
10+
start_st,
11+
min_api_version,
912
st_init_common_args,
13+
)
14+
from .utils import (
1015
NoneClaim,
1116
get_st_init_args,
1217
session_functions_override_with_claim,

tests/sessions/claims/test_get_claim_value.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
GetClaimValueOkResult,
1515
SessionDoesNotExistError,
1616
)
17-
from tests.utils import setup_function, teardown_function, start_st
18-
from .utils import TrueClaim, get_st_init_args, st_init_common_args
17+
from tests.utils import setup_function, teardown_function, start_st, st_init_common_args
18+
from .utils import TrueClaim, get_st_init_args
1919

2020
_ = setup_function # type:ignore
2121
_ = teardown_function # type:ignore

0 commit comments

Comments
 (0)