Skip to content

Commit 6d297b0

Browse files
committed
feat: Wrap up features and tests related to basic session claims
1 parent ed3a1cb commit 6d297b0

File tree

22 files changed

+1026
-100
lines changed

22 files changed

+1026
-100
lines changed

supertokens_python/recipe/session/api/implementation.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
# under the License.
1414
from __future__ import annotations
1515

16-
from typing import TYPE_CHECKING, Union
16+
from typing import TYPE_CHECKING, Union, List, Callable, Optional
1717

1818
from supertokens_python.normalised_url_path import NormalisedURLPath
1919
from supertokens_python.recipe.session.interfaces import (
2020
APIInterface,
2121
SignOutOkayResponse,
22+
SessionClaimValidator,
2223
)
24+
from supertokens_python.types import MaybeAwaitable
2325
from supertokens_python.utils import normalise_http_method
2426

2527
if TYPE_CHECKING:
@@ -48,6 +50,7 @@ async def signout_post(
4850
user_context=user_context,
4951
anti_csrf_check=None,
5052
session_required=True,
53+
override_global_claim_validators=lambda _, __, ___: [],
5154
)
5255
except UnauthorisedError:
5356
return SignOutOkayResponse()
@@ -62,6 +65,12 @@ async def verify_session(
6265
api_options: APIOptions,
6366
anti_csrf_check: Union[bool, None],
6467
session_required: bool,
68+
override_global_claim_validators: Optional[
69+
Callable[
70+
[SessionContainer, List[SessionClaimValidator], Dict[str, Any]],
71+
MaybeAwaitable[List[SessionClaimValidator]],
72+
]
73+
],
6574
user_context: Dict[str, Any],
6675
) -> Union[SessionContainer, None]:
6776
method = normalise_http_method(api_options.request.method())
@@ -73,6 +82,19 @@ async def verify_session(
7382
return await api_options.recipe_implementation.refresh_session(
7483
api_options.request, user_context
7584
)
76-
return await api_options.recipe_implementation.get_session(
77-
api_options.request, anti_csrf_check, session_required, user_context
85+
session = await api_options.recipe_implementation.get_session(
86+
api_options.request,
87+
anti_csrf_check,
88+
session_required,
89+
override_global_claim_validators,
90+
user_context,
7891
)
92+
93+
if session is not None:
94+
await api_options.recipe_implementation.assert_claims(
95+
session,
96+
override_global_claim_validators,
97+
user_context,
98+
)
99+
100+
return session

supertokens_python/recipe/session/asyncio/__init__.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
from typing import Any, Dict, List, Union, TypeVar
14+
from typing import Any, Dict, List, Union, TypeVar, Callable, Optional
1515

1616
from supertokens_python.recipe.openid.interfaces import (
1717
GetOpenIdDiscoveryConfigurationResult,
@@ -21,8 +21,10 @@
2121
SessionContainer,
2222
SessionInformationResult,
2323
SessionClaim,
24+
SessionClaimValidator,
2425
)
2526
from supertokens_python.recipe.session.recipe import SessionRecipe
27+
from supertokens_python.types import MaybeAwaitable
2628
from supertokens_python.utils import FRAMEWORKS
2729
from ...jwt.interfaces import (
2830
CreateJwtOkResult,
@@ -92,6 +94,12 @@ async def get_session(
9294
request: Any,
9395
anti_csrf_check: Union[bool, None] = None,
9496
session_required: bool = True,
97+
override_global_claim_validators: Optional[
98+
Callable[
99+
[SessionContainer, List[SessionClaimValidator], Dict[str, Any]],
100+
MaybeAwaitable[List[SessionClaimValidator]],
101+
]
102+
] = None,
95103
user_context: Union[None, Dict[str, Any]] = None,
96104
) -> Union[SessionContainer, None]:
97105
if user_context is None:
@@ -100,10 +108,23 @@ async def get_session(
100108
request = FRAMEWORKS[
101109
SessionRecipe.get_instance().app_info.framework
102110
].wrap_request(request)
103-
return await SessionRecipe.get_instance().recipe_implementation.get_session(
104-
request, anti_csrf_check, session_required, user_context
111+
112+
session_recipe_impl = SessionRecipe.get_instance().recipe_implementation
113+
session = await session_recipe_impl.get_session(
114+
request,
115+
anti_csrf_check,
116+
session_required,
117+
override_global_claim_validators,
118+
user_context,
105119
)
106120

121+
if session is not None:
122+
await session_recipe_impl.assert_claims(
123+
session, override_global_claim_validators, user_context
124+
)
125+
126+
return session
127+
107128

108129
async def refresh_session(
109130
request: Any, user_context: Union[None, Dict[str, Any]] = None

supertokens_python/recipe/session/claim_base_classes/primitive_claim.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,28 @@
1313
# under the License.
1414

1515
import time
16-
from typing import Any, Dict, TypeVar, Union
16+
from typing import Any, Dict, TypeVar, Union, Callable, Optional
1717

18+
from supertokens_python.types import MaybeAwaitable
1819
from ..interfaces import JSONObject, JSONPrimitive, SessionClaim, SessionClaimValidator
1920

2021
_T = TypeVar("_T")
2122

2223

2324
class PrimitiveClaim(SessionClaim[JSONPrimitive]):
24-
def __init__(self, key: str, fetch_value: Any) -> None:
25+
def __init__(
26+
self,
27+
key: str,
28+
fetch_value: Optional[
29+
Callable[
30+
[str, Optional[Dict[str, Any]]],
31+
MaybeAwaitable[Optional[JSONPrimitive]],
32+
]
33+
] = None,
34+
) -> None:
2535
super().__init__(key)
26-
self.fetch_value = fetch_value
36+
if fetch_value is not None:
37+
self.fetch_value = fetch_value # type: ignore
2738

2839
claim = self
2940

@@ -32,8 +43,8 @@ def has_value(
3243
) -> SessionClaimValidator:
3344
class HasValueSCV(SessionClaimValidator):
3445
def __init__(self):
46+
super().__init__(id_ or claim.key)
3547
self.claim = claim
36-
self.id_ = id_ or claim.key
3748

3849
def should_refetch(
3950
self,
@@ -69,8 +80,8 @@ def has_fresh_value(
6980
) -> SessionClaimValidator:
7081
class HasFreshValueSCV(SessionClaimValidator):
7182
def __init__(self):
83+
super().__init__(id_ or (claim.key + "-fresh-val"))
7284
self.claim = claim
73-
self.id_ = id_ or (claim.key + "-fresh-val")
7485

7586
def should_refetch(
7687
self,
@@ -119,6 +130,9 @@ def __init__(self) -> None:
119130
self.has_value = has_value
120131
self.has_fresh_value = has_fresh_value
121132

133+
# def __setattr__(self, k: str, v: Any):
134+
# super().__setattr__(k, v)
135+
122136
self.validators = Validators()
123137

124138
def add_to_payload_(

supertokens_python/recipe/session/exceptions.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# under the License.
1414
from __future__ import annotations
1515

16-
from typing import Union
16+
from typing import Union, Any, List
1717

1818
from supertokens_python.exceptions import SuperTokensError
1919

@@ -51,3 +51,21 @@ def __init__(self, msg: str, clear_cookies: bool = True):
5151

5252
class TryRefreshTokenError(SuperTokensSessionError):
5353
pass
54+
55+
56+
class InvalidClaimsError(SuperTokensSessionError):
57+
def __init__(self, msg: str, payload: List[ClaimValidationError]):
58+
super().__init__(msg)
59+
self.payload = [
60+
p.__dict__ for p in payload
61+
] # Must be JSON serializable as it will be used in response
62+
63+
64+
class ClaimValidationError:
65+
def __init__(self, id_: str, reason: Any):
66+
self.id = id_
67+
self.reason = reason
68+
69+
70+
def raise_invalid_claims_exception(msg: str, payload: List[ClaimValidationError]):
71+
raise InvalidClaimsError(msg, payload)

supertokens_python/recipe/session/framework/django/asyncio/__init__.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
from functools import wraps
15-
from typing import Any, Callable, Dict, TypeVar, Union, cast
15+
from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional
1616

1717
from supertokens_python import Supertokens
1818
from supertokens_python.exceptions import SuperTokensError
1919
from supertokens_python.framework.django.django_request import DjangoRequest
2020
from supertokens_python.framework.django.django_response import DjangoResponse
21-
from supertokens_python.recipe.session import SessionRecipe
21+
from supertokens_python.recipe.session import SessionRecipe, SessionContainer
22+
from supertokens_python.recipe.session.interfaces import SessionClaimValidator
23+
from supertokens_python.types import MaybeAwaitable
2224

2325
_T = TypeVar("_T", bound=Callable[..., Any])
2426

@@ -27,6 +29,12 @@ def verify_session(
2729
anti_csrf_check: Union[bool, None] = None,
2830
session_required: bool = True,
2931
user_context: Union[None, Dict[str, Any]] = None,
32+
override_global_claim_validators: Optional[
33+
Callable[
34+
[SessionContainer, List[SessionClaimValidator], Dict[str, Any]],
35+
MaybeAwaitable[List[SessionClaimValidator]],
36+
]
37+
] = None,
3038
) -> Callable[[_T], _T]:
3139
if user_context is None:
3240
user_context = {}
@@ -42,7 +50,11 @@ async def wrapped_function(request: HttpRequest, *args: Any, **kwargs: Any):
4250
baseRequest = DjangoRequest(request)
4351
recipe = SessionRecipe.get_instance()
4452
session = await recipe.verify_session(
45-
baseRequest, anti_csrf_check, session_required, user_context
53+
baseRequest,
54+
anti_csrf_check,
55+
session_required,
56+
override_global_claim_validators,
57+
user_context,
4658
)
4759
if session is None:
4860
if session_required:

supertokens_python/recipe/session/framework/django/syncio/__init__.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
from functools import wraps
15-
from typing import Any, Callable, Dict, TypeVar, Union, cast
15+
from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional
1616

1717
from supertokens_python import Supertokens
1818
from supertokens_python.async_to_sync_wrapper import sync
1919
from supertokens_python.exceptions import SuperTokensError
2020
from supertokens_python.framework.django.django_request import DjangoRequest
2121
from supertokens_python.framework.django.django_response import DjangoResponse
22-
from supertokens_python.recipe.session import SessionRecipe
22+
from supertokens_python.recipe.session import SessionRecipe, SessionContainer
23+
from supertokens_python.recipe.session.interfaces import SessionClaimValidator
24+
from supertokens_python.types import MaybeAwaitable
2325

2426
_T = TypeVar("_T", bound=Callable[..., Any])
2527

@@ -28,6 +30,12 @@ def verify_session(
2830
anti_csrf_check: Union[bool, None] = None,
2931
session_required: bool = True,
3032
user_context: Union[None, Dict[str, Any]] = None,
33+
override_global_claim_validators: Optional[
34+
Callable[
35+
[SessionContainer, List[SessionClaimValidator], Dict[str, Any]],
36+
MaybeAwaitable[List[SessionClaimValidator]],
37+
]
38+
] = None,
3139
) -> Callable[[_T], _T]:
3240
if user_context is None:
3341
user_context = {}
@@ -44,7 +52,11 @@ def wrapped_function(request: HttpRequest, *args: Any, **kwargs: Any):
4452
recipe = SessionRecipe.get_instance()
4553
session = sync(
4654
recipe.verify_session(
47-
baseRequest, anti_csrf_check, session_required, user_context
55+
baseRequest,
56+
anti_csrf_check,
57+
session_required,
58+
override_global_claim_validators,
59+
user_context,
4860
)
4961
)
5062
if session is None:

supertokens_python/recipe/session/framework/fastapi/__init__.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,25 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
from typing import Any, Callable, Coroutine, Dict, Union
14+
from typing import Any, Callable, Coroutine, Dict, Union, List, Optional
1515

1616
from supertokens_python.framework.fastapi.fastapi_request import FastApiRequest
1717
from supertokens_python.recipe.session import SessionRecipe
18+
from supertokens_python.types import MaybeAwaitable
1819

19-
from ...interfaces import SessionContainer
20+
from ...interfaces import SessionContainer, SessionClaimValidator
2021

2122

2223
def verify_session(
2324
anti_csrf_check: Union[bool, None] = None,
2425
session_required: bool = True,
2526
user_context: Union[None, Dict[str, Any]] = None,
27+
override_global_claim_validators: Optional[
28+
Callable[
29+
[SessionContainer, List[SessionClaimValidator], Dict[str, Any]],
30+
MaybeAwaitable[List[SessionClaimValidator]],
31+
]
32+
] = None,
2633
) -> Callable[..., Coroutine[Any, Any, Union[SessionContainer, None]]]:
2734
if user_context is None:
2835
user_context = {}
@@ -32,7 +39,11 @@ async def func(request: Request) -> Union[SessionContainer, None]:
3239
baseRequest = FastApiRequest(request)
3340
recipe = SessionRecipe.get_instance()
3441
session = await recipe.verify_session(
35-
baseRequest, anti_csrf_check, session_required, user_context
42+
baseRequest,
43+
anti_csrf_check,
44+
session_required,
45+
override_global_claim_validators,
46+
user_context,
3647
)
3748
if session is None:
3849
if session_required:

supertokens_python/recipe/session/framework/flask/__init__.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
from functools import wraps
15-
from typing import Any, Callable, Dict, TypeVar, Union, cast
15+
from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional
1616

1717
from supertokens_python.async_to_sync_wrapper import sync
1818
from supertokens_python.framework.flask.flask_request import FlaskRequest
19-
from supertokens_python.recipe.session import SessionRecipe
19+
from supertokens_python.recipe.session import SessionRecipe, SessionContainer
20+
from supertokens_python.recipe.session.interfaces import SessionClaimValidator
21+
from supertokens_python.types import MaybeAwaitable
2022

2123
_T = TypeVar("_T", bound=Callable[..., Any])
2224

@@ -25,6 +27,12 @@ def verify_session(
2527
anti_csrf_check: Union[bool, None] = None,
2628
session_required: bool = True,
2729
user_context: Union[None, Dict[str, Any]] = None,
30+
override_global_claim_validators: Optional[
31+
Callable[
32+
[SessionContainer, List[SessionClaimValidator], Dict[str, Any]],
33+
MaybeAwaitable[List[SessionClaimValidator]],
34+
]
35+
] = None,
2836
) -> Callable[[_T], _T]:
2937
if user_context is None:
3038
user_context = {}
@@ -38,7 +46,11 @@ def wrapped_function(*args: Any, **kwargs: Any):
3846
recipe = SessionRecipe.get_instance()
3947
session = sync(
4048
recipe.verify_session(
41-
baseRequest, anti_csrf_check, session_required, user_context
49+
baseRequest,
50+
anti_csrf_check,
51+
session_required,
52+
override_global_claim_validators,
53+
user_context,
4254
)
4355
)
4456
if session is None:

0 commit comments

Comments
 (0)