Skip to content

Commit 3de6264

Browse files
committed
feat: Introduce session recipe functions without request response
1 parent bc2fc37 commit 3de6264

File tree

16 files changed

+1303
-597
lines changed

16 files changed

+1303
-597
lines changed

supertokens_python/recipe/emailpassword/api/implementation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ async def sign_in_post(
171171
session = await create_new_session(
172172
api_options.request,
173173
user.user_id,
174+
access_token_payload={},
175+
session_data_in_database={},
174176
user_context=user_context,
175177
)
176178
return SignInPostOkResult(user, session)
@@ -208,6 +210,8 @@ async def sign_up_post(
208210
session = await create_new_session(
209211
api_options.request,
210212
user.user_id,
213+
access_token_payload={},
214+
session_data_in_database={},
211215
user_context=user_context,
212216
)
213217
return SignUpPostOkResult(user, session)

supertokens_python/recipe/session/access_token.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def get_info_from_access_token(
4747
verify_jwt(jwt_info, jwt_signing_public_key)
4848
payload = jwt_info.payload
4949

50-
validate_access_token_structure(payload)
50+
validate_access_token_structure(payload, jwt_info.version)
5151

5252
session_handle = sanitize_string(payload.get("sessionHandle"))
5353
user_id = sanitize_string(payload.get("userId"))
@@ -85,7 +85,19 @@ def get_info_from_access_token(
8585
raise_try_refresh_token_exception(e)
8686

8787

88-
def validate_access_token_structure(payload: Dict[str, Any]) -> None:
88+
def validate_access_token_structure(payload: Dict[str, Any], version: int) -> None:
89+
if version >= 3:
90+
if (
91+
not isinstance(payload.get("sub"), str)
92+
or not isinstance(payload.get("exp"), int)
93+
or not isinstance(payload.get("iat"), int)
94+
or not isinstance(payload.get("sessionHandle"), str)
95+
or not isinstance(payload.get("refreshTokenHash1"), str)
96+
):
97+
raise Exception(
98+
"Access token does not contain all the information. Maybe the structure has changed?"
99+
)
100+
89101
if (
90102
not isinstance(payload.get("sessionHandle"), str)
91103
or payload.get("userData") is None

supertokens_python/recipe/session/api/implementation.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,28 @@
2424
from supertokens_python.types import MaybeAwaitable
2525
from supertokens_python.utils import normalise_http_method
2626

27-
from ..utils import get_required_claim_validators
28-
2927
if TYPE_CHECKING:
3028
from supertokens_python.recipe.session.interfaces import APIOptions
3129
from ..interfaces import SessionContainer
3230

3331
from typing import Any, Dict
3432

33+
from supertokens_python.recipe.session.session_request_functions import (
34+
get_session_from_request,
35+
refresh_session_in_request,
36+
)
37+
3538

3639
class APIImplementation(APIInterface):
3740
async def refresh_post(
3841
self, api_options: APIOptions, user_context: Dict[str, Any]
3942
) -> SessionContainer:
40-
return await api_options.recipe_implementation.refresh_session(
41-
api_options.request, user_context
43+
return await refresh_session_in_request(
44+
api_options.request,
45+
# api_options.response,
46+
user_context,
47+
api_options.config,
48+
api_options.recipe_implementation,
4249
)
4350

4451
async def signout_post(
@@ -69,23 +76,20 @@ async def verify_session(
6976
return None
7077
incoming_path = NormalisedURLPath(api_options.request.get_path())
7178
refresh_token_path = api_options.config.refresh_token_path
72-
if incoming_path.equals(refresh_token_path) and method == "post":
73-
return await api_options.recipe_implementation.refresh_session(
74-
api_options.request, user_context
75-
)
76-
session = await api_options.recipe_implementation.get_session(
77-
api_options.request,
78-
anti_csrf_check,
79-
session_required,
80-
user_context,
81-
)
8279

83-
if session is not None:
84-
claim_validators = await get_required_claim_validators(
85-
session,
86-
override_global_claim_validators,
80+
if incoming_path.equals(refresh_token_path) and method == "post":
81+
return await refresh_session_in_request(
82+
api_options.request,
83+
# api_options.response,
8784
user_context,
85+
api_options.config,
86+
api_options.recipe_implementation,
8887
)
89-
await session.assert_claims(claim_validators, user_context)
9088

91-
return session
89+
return await get_session_from_request( # FIXME:
90+
api_options.request,
91+
# api_options.response,
92+
api_options.config,
93+
api_options.recipe_implementation,
94+
user_context=user_context,
95+
)

supertokens_python/recipe/session/api/signout.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@
1515

1616
from typing import TYPE_CHECKING
1717

18+
from supertokens_python.recipe.session.session_request_functions import (
19+
get_session_from_request,
20+
)
21+
1822
if TYPE_CHECKING:
19-
from supertokens_python.recipe.session.interfaces import APIInterface, APIOptions
23+
from supertokens_python.recipe.session.interfaces import (
24+
APIInterface,
25+
APIOptions,
26+
)
2027

2128
from supertokens_python.utils import default_user_context, send_200_response
2229

@@ -27,12 +34,16 @@ async def handle_signout_api(api_implementation: APIInterface, api_options: APIO
2734
or api_implementation.signout_post is None
2835
):
2936
return None
37+
3038
user_context = default_user_context(api_options.request)
3139

32-
session = await api_options.recipe_implementation.get_session(
33-
request=api_options.request,
34-
anti_csrf_check=None,
40+
session = await get_session_from_request(
41+
api_options.request,
42+
# api_options.response,
43+
api_options.config,
44+
api_options.recipe_implementation,
3545
session_required=False,
46+
override_global_claim_validators=lambda _, __, ___: [],
3647
user_context=user_context,
3748
)
3849

supertokens_python/recipe/session/asyncio/__init__.py

Lines changed: 142 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# under the License.
1414
from typing import Any, Dict, List, Union, TypeVar, Callable, Optional
1515

16+
from supertokens_python.exceptions import SuperTokensError
17+
from supertokens_python.framework.request import BaseRequest
1618
from supertokens_python.recipe.openid.interfaces import (
1719
GetOpenIdDiscoveryConfigurationResult,
1820
)
@@ -26,10 +28,24 @@
2628
ClaimsValidationResult,
2729
JSONObject,
2830
GetClaimValueOkResult,
31+
GetSessionUnauthorizedResponse,
32+
GetSessionTryRefreshTokenErrorResponse,
33+
GetSessionClaimValidationErrorResponse,
34+
CreateNewSessionResult,
35+
GetSessionOkResponse,
36+
RefreshSessionOkResponse,
37+
RefreshSessionUnauthorizedResponse,
38+
RefreshSessionTokenTheftErrorResponse,
2939
)
3040
from supertokens_python.recipe.session.recipe import SessionRecipe
41+
from supertokens_python.recipe.session.session_request_functions import (
42+
get_session_from_request,
43+
create_new_session_in_request,
44+
refresh_session_in_request,
45+
)
3146
from supertokens_python.types import MaybeAwaitable
3247
from supertokens_python.utils import FRAMEWORKS, resolve, deprecated_warn
48+
from ..exceptions import InvalidClaimsError
3349
from ..utils import get_required_claim_validators
3450
from ...jwt.interfaces import (
3551
CreateJwtOkResult,
@@ -41,7 +57,8 @@
4157

4258

4359
async def create_new_session(
44-
request: Any,
60+
request: BaseRequest,
61+
# response: BaseResponse,
4562
user_id: str,
4663
access_token_payload: Union[Dict[str, Any], None] = None,
4764
session_data_in_database: Union[Dict[str, Any], None] = None,
@@ -54,6 +71,37 @@ async def create_new_session(
5471
if access_token_payload is None:
5572
access_token_payload = {}
5673

74+
recipe_instance = SessionRecipe.get_instance()
75+
config = recipe_instance.config
76+
app_info = recipe_instance.app_info
77+
78+
return await create_new_session_in_request(
79+
request,
80+
# response,
81+
user_context,
82+
recipe_instance,
83+
access_token_payload,
84+
user_id,
85+
config,
86+
app_info,
87+
session_data_in_database,
88+
)
89+
90+
91+
async def create_new_session_without_request_response(
92+
user_id: str,
93+
access_token_payload: Union[Dict[str, Any], None] = None,
94+
session_data_in_database: Union[Dict[str, Any], None] = None,
95+
disable_anti_csrf: bool = False,
96+
user_context: Union[None, Dict[str, Any]] = None,
97+
) -> CreateNewSessionResult:
98+
if user_context is None:
99+
user_context = {}
100+
if session_data_in_database is None:
101+
session_data_in_database = {}
102+
if access_token_payload is None:
103+
access_token_payload = {}
104+
57105
claims_added_by_other_recipes = (
58106
SessionRecipe.get_instance().get_claims_added_by_other_recipes()
59107
)
@@ -63,16 +111,11 @@ async def create_new_session(
63111
update = await claim.build(user_id, user_context)
64112
final_access_token_payload = {**final_access_token_payload, **update}
65113

66-
if not hasattr(request, "wrapper_used") or not request.wrapper_used:
67-
request = FRAMEWORKS[
68-
SessionRecipe.get_instance().app_info.framework
69-
].wrap_request(request)
70-
71114
return await SessionRecipe.get_instance().recipe_implementation.create_new_session(
72-
request,
73115
user_id,
74116
final_access_token_payload,
75117
session_data_in_database,
118+
disable_anti_csrf,
76119
user_context=user_context,
77120
)
78121

@@ -236,9 +279,11 @@ async def remove_claim(
236279

237280

238281
async def get_session(
239-
request: Any,
240-
anti_csrf_check: Union[bool, None] = None,
241-
session_required: bool = True,
282+
request: BaseRequest,
283+
# response: BaseResponse,
284+
session_required: Optional[bool] = None,
285+
anti_csrf_check: Optional[bool] = None,
286+
check_database: Optional[bool] = None,
242287
override_global_claim_validators: Optional[
243288
Callable[
244289
[List[SessionClaimValidator], SessionContainer, Dict[str, Any]],
@@ -249,40 +294,114 @@ async def get_session(
249294
) -> Union[SessionContainer, None]:
250295
if user_context is None:
251296
user_context = {}
252-
if not hasattr(request, "wrapper_used") or not request.wrapper_used:
253-
request = FRAMEWORKS[
254-
SessionRecipe.get_instance().app_info.framework
255-
].wrap_request(request)
256297

257-
session_recipe_impl = SessionRecipe.get_instance().recipe_implementation
258-
session = await session_recipe_impl.get_session(
298+
recipe_instance = SessionRecipe.get_instance()
299+
recipe_interface_impl = recipe_instance.recipe_implementation
300+
config = recipe_instance.config
301+
302+
return await get_session_from_request(
259303
request,
304+
# response,
305+
config,
306+
recipe_interface_impl,
307+
session_required=session_required,
308+
anti_csrf_check=anti_csrf_check,
309+
check_database=check_database,
310+
override_global_claim_validators=override_global_claim_validators,
311+
user_context=user_context,
312+
)
313+
314+
315+
# TODO: Add comments
316+
async def get_session_without_request_response(
317+
access_token: str,
318+
anti_csrf_token: Optional[str] = None,
319+
anti_csrf_check: Optional[bool] = None,
320+
check_database: Optional[bool] = None,
321+
override_global_claim_validators: Optional[
322+
Callable[
323+
[List[SessionClaimValidator], SessionContainer, Dict[str, Any]],
324+
MaybeAwaitable[List[SessionClaimValidator]],
325+
]
326+
] = None,
327+
user_context: Union[None, Dict[str, Any]] = None,
328+
) -> Union[
329+
GetSessionOkResponse,
330+
GetSessionUnauthorizedResponse,
331+
GetSessionTryRefreshTokenErrorResponse,
332+
GetSessionClaimValidationErrorResponse,
333+
]:
334+
if user_context is None:
335+
user_context = {}
336+
337+
recipe_interface_impl = SessionRecipe.get_instance().recipe_implementation
338+
339+
res = await recipe_interface_impl.get_session(
340+
access_token,
341+
anti_csrf_token,
260342
anti_csrf_check,
261-
session_required,
343+
check_database,
344+
override_global_claim_validators,
262345
user_context,
263346
)
264347

265-
if session is not None:
348+
if isinstance(res, GetSessionOkResponse):
266349
claim_validators = await get_required_claim_validators(
267-
session, override_global_claim_validators, user_context
350+
res.session, override_global_claim_validators, user_context
268351
)
269-
await session.assert_claims(claim_validators, user_context)
352+
try:
353+
await res.session.assert_claims(claim_validators, user_context)
354+
except SuperTokensError as e:
355+
if isinstance(e, InvalidClaimsError):
356+
return GetSessionClaimValidationErrorResponse(e) # FIXME
357+
raise e
270358

271-
return session
359+
return res
272360

273361

274362
async def refresh_session(
275-
request: Any, user_context: Union[None, Dict[str, Any]] = None
363+
request: Any,
364+
# response: BaseResponse,
365+
user_context: Union[None, Dict[str, Any]] = None,
276366
) -> SessionContainer:
277367
if user_context is None:
278368
user_context = {}
369+
279370
if not hasattr(request, "wrapper_used") or not request.wrapper_used:
280371
request = FRAMEWORKS[
281372
SessionRecipe.get_instance().app_info.framework
282373
].wrap_request(request)
283374

375+
# TODO: wrap response if required
376+
377+
recipe_instance = SessionRecipe.get_instance()
378+
config = recipe_instance.config
379+
recipe_interface_impl = recipe_instance.recipe_implementation
380+
381+
return await refresh_session_in_request(
382+
request,
383+
# response,
384+
user_context,
385+
config,
386+
recipe_interface_impl,
387+
)
388+
389+
390+
async def refresh_session_without_request_response(
391+
refresh_token: str,
392+
disable_anti_csrf: bool = False,
393+
anti_csrf_token: Optional[str] = None,
394+
user_context: Optional[Dict[str, Any]] = None,
395+
) -> Union[
396+
RefreshSessionOkResponse,
397+
RefreshSessionUnauthorizedResponse,
398+
RefreshSessionTokenTheftErrorResponse,
399+
]:
400+
if user_context is None:
401+
user_context = {}
402+
284403
return await SessionRecipe.get_instance().recipe_implementation.refresh_session(
285-
request, user_context
404+
refresh_token, anti_csrf_token, disable_anti_csrf, user_context
286405
)
287406

288407

0 commit comments

Comments
 (0)