Skip to content

Commit d37809c

Browse files
Merge pull request #363 from supertokens/test/verify-session-if-no-middleware
test: Verify session should return 401 if access token isn't sent and middleware isn't added
2 parents cfabd11 + c52b7ab commit d37809c

File tree

5 files changed

+209
-27
lines changed

5 files changed

+209
-27
lines changed

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,18 @@
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+
import json
15+
1416
from typing import Any, Callable, Coroutine, Dict, Union, List, Optional
1517

18+
from supertokens_python import Supertokens
1619
from supertokens_python.framework.fastapi.fastapi_request import FastApiRequest
20+
from supertokens_python.framework.fastapi.fastapi_response import FastApiResponse
1721
from supertokens_python.recipe.session import SessionRecipe
22+
from supertokens_python.exceptions import SuperTokensError
1823
from supertokens_python.types import MaybeAwaitable
24+
from fastapi.responses import JSONResponse
25+
from fastapi import Request
1926

2027
from ...interfaces import SessionContainer, SessionClaimValidator
2128

@@ -34,13 +41,12 @@ def verify_session(
3441
) -> Callable[..., Coroutine[Any, Any, Union[SessionContainer, None]]]:
3542
if user_context is None:
3643
user_context = {}
37-
from fastapi import Request
3844

3945
async def func(request: Request) -> Union[SessionContainer, None]:
40-
baseRequest = FastApiRequest(request)
46+
base_req = FastApiRequest(request)
4147
recipe = SessionRecipe.get_instance()
4248
session = await recipe.verify_session(
43-
baseRequest,
49+
base_req,
4450
anti_csrf_check,
4551
session_required,
4652
check_database,
@@ -50,9 +56,28 @@ async def func(request: Request) -> Union[SessionContainer, None]:
5056
if session is None:
5157
if session_required:
5258
raise Exception("Should never come here")
53-
baseRequest.set_session_as_none()
59+
base_req.set_session_as_none()
5460
else:
55-
baseRequest.set_session(session)
56-
return baseRequest.get_session()
61+
base_req.set_session(session)
62+
return base_req.get_session()
5763

5864
return func
65+
66+
67+
async def session_exception_handler(
68+
request: Request, exc: SuperTokensError
69+
) -> JSONResponse:
70+
"""FastAPI exceptional handler for errors raised by Supertokens SDK when not using middleware
71+
72+
Usage: `app.add_exception_handler(SuperTokensError, st_exception_handler)`
73+
"""
74+
base_req = FastApiRequest(request)
75+
base_res = FastApiResponse(JSONResponse())
76+
result = await Supertokens.get_instance().handle_supertokens_error(
77+
base_req, exc, base_res
78+
)
79+
if isinstance(result, FastApiResponse):
80+
body = json.loads(result.response.body)
81+
return JSONResponse(body, status_code=result.response.status_code)
82+
83+
raise Exception("Should never come here")

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

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
from functools import wraps
1515
from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional
1616

17+
from supertokens_python import Supertokens
1718
from supertokens_python.async_to_sync_wrapper import sync
1819
from supertokens_python.framework.flask.flask_request import FlaskRequest
20+
from supertokens_python.framework.flask.flask_response import FlaskResponse
1921
from supertokens_python.recipe.session import SessionRecipe, SessionContainer
22+
from supertokens_python.exceptions import SuperTokensError
2023
from supertokens_python.recipe.session.interfaces import SessionClaimValidator
2124
from supertokens_python.types import MaybeAwaitable
2225

@@ -43,27 +46,39 @@ def session_verify(f: _T) -> _T:
4346
def wrapped_function(*args: Any, **kwargs: Any):
4447
from flask import make_response, request
4548

46-
baseRequest = FlaskRequest(request)
47-
recipe = SessionRecipe.get_instance()
48-
session = sync(
49-
recipe.verify_session(
50-
baseRequest,
51-
anti_csrf_check,
52-
session_required,
53-
check_database,
54-
override_global_claim_validators,
55-
user_context,
49+
base_req = FlaskRequest(request)
50+
51+
try:
52+
recipe = SessionRecipe.get_instance()
53+
session = sync(
54+
recipe.verify_session(
55+
base_req,
56+
anti_csrf_check,
57+
session_required,
58+
check_database,
59+
override_global_claim_validators,
60+
user_context,
61+
)
5662
)
57-
)
58-
if session is None:
59-
if session_required:
60-
raise Exception("Should never come here")
61-
baseRequest.set_session_as_none()
62-
else:
63-
baseRequest.set_session(session)
63+
if session is None:
64+
if session_required:
65+
raise Exception("Should never come here")
66+
base_req.set_session_as_none()
67+
else:
68+
base_req.set_session(session)
6469

65-
response = f(*args, **kwargs)
66-
return make_response(response) if response is not None else None
70+
response = f(*args, **kwargs)
71+
return make_response(response) if response is not None else None
72+
except SuperTokensError as e:
73+
response = FlaskResponse(make_response())
74+
result = sync(
75+
Supertokens.get_instance().handle_supertokens_error(
76+
base_req, e, response
77+
)
78+
)
79+
if isinstance(result, FlaskResponse):
80+
return result.response
81+
raise Exception("Should never come here")
6782

6883
return cast(_T, wrapped_function)
6984

tests/Django/test_django.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,19 @@
3333
create_new_session,
3434
get_session,
3535
refresh_session,
36+
create_new_session_without_request_response,
3637
)
3738
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
3839

3940
import pytest
40-
from tests.utils import clean_st, reset, setup_st, start_st, create_users
41+
from tests.utils import (
42+
clean_st,
43+
reset,
44+
setup_st,
45+
start_st,
46+
create_users,
47+
get_st_init_args,
48+
)
4149
from supertokens_python.recipe.dashboard import DashboardRecipe, InputOverrideConfig
4250
from supertokens_python.recipe.dashboard.interfaces import RecipeInterface
4351
from supertokens_python.framework import BaseRequest
@@ -111,6 +119,12 @@ async def optional_session(request: HttpRequest):
111119
return JsonResponse({"s": session.get_handle()})
112120

113121

122+
@verify_session()
123+
async def verify_view(request: HttpRequest):
124+
session: SessionContainer = request.supertokens # type: ignore
125+
return JsonResponse({"handle": session.get_handle()}) # type: ignore
126+
127+
114128
class SupertokensTest(TestCase):
115129
def setUp(self):
116130
self.factory = RequestFactory()
@@ -874,6 +888,43 @@ async def test_search_with_provider_google_and_phone_one(self):
874888
data_json = json.loads(response.content)
875889
self.assertEqual(len(data_json["users"]), 0)
876890

891+
async def test_that_verify_session_return_401_if_access_token_is_not_sent_and_middleware_is_not_added(
892+
self,
893+
):
894+
args = get_st_init_args([session.init(get_token_transfer_method=lambda *_: "header")]) # type: ignore
895+
args.update({"framework": "django"})
896+
init(**args) # type: ignore
897+
start_st()
898+
899+
# Try with middleware
900+
request = self.factory.get("/verify")
901+
response: HttpResponse = await middleware(verify_view)(request) # type: ignore
902+
assert response.status_code == 401
903+
assert json.loads(response.content) == {"message": "unauthorised"}
904+
905+
# Try without middleware
906+
request = self.factory.get("/verify")
907+
response: HttpResponse = await verify_view(request) # type: ignore
908+
assert response.status_code == 401
909+
assert json.loads(response.content) == {"message": "unauthorised"}
910+
911+
# Create a session and get access token
912+
s = await create_new_session_without_request_response("userId", {}, {})
913+
access_token = s.get_access_token()
914+
headers = {"HTTP_AUTHORIZATION": "Bearer " + access_token}
915+
916+
# Now try with middleware:
917+
request = self.factory.get("/verify", {}, **headers)
918+
response: JsonResponse = await middleware(verify_view)(request) # type: ignore
919+
assert response.status_code == 200
920+
assert list(json.loads(response.content)) == ["handle"]
921+
922+
# Now try without middleware:
923+
request = self.factory.get("/verify", **headers)
924+
response: JsonResponse = await verify_view(request) # type: ignore
925+
assert response.status_code == 200
926+
assert list(json.loads(response.content)) == ["handle"]
927+
877928

878929
def test_remove_header_works():
879930
response = HttpResponse()

tests/Flask/test_flask.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from supertokens_python.recipe.session.framework.flask import verify_session
2727
from supertokens_python.recipe.session.syncio import (
2828
create_new_session,
29+
create_new_session_without_request_response,
2930
get_session,
3031
refresh_session,
3132
revoke_session,
@@ -734,6 +735,11 @@ def ping(): # type: ignore
734735
def options_api(): # type: ignore
735736
return jsonify({"msg": "Shouldn't come here"})
736737

738+
@app.get("/verify") # type: ignore
739+
@verify_session()
740+
def verify_api(): # type: ignore
741+
return {"handle": g.supertokens.get_handle()}
742+
737743
return app
738744

739745

@@ -767,3 +773,48 @@ def test_verify_session_with_before_request_with_no_response(flask_app: Any):
767773
assert client.get("/ping").status_code == 200
768774

769775
assert client.get("/stats").json == {"unknown": 3, "userId": 2}
776+
777+
778+
@fixture(scope="function")
779+
def flask_app_without_middleware():
780+
app = Flask(__name__)
781+
782+
app.testing = True
783+
784+
@app.get("/verify") # type: ignore
785+
@verify_session()
786+
def verify_api(): # type: ignore
787+
return {"handle": g.supertokens.get_handle()}
788+
789+
return app
790+
791+
792+
def test_that_verify_session_return_401_if_access_token_is_not_sent_and_middleware_is_not_added(
793+
flask_app: Any, flask_app_without_middleware: Any
794+
):
795+
init(**{**get_st_init_args([session.init(get_token_transfer_method=lambda *_: "header")]), "framework": "flask"}) # type: ignore
796+
start_st()
797+
798+
client = flask_app.test_client()
799+
client_without_middleware = flask_app_without_middleware.test_client()
800+
801+
res = client.get("/verify")
802+
assert res.status_code == 401
803+
assert res.json == {"message": "unauthorised"}
804+
805+
s = create_new_session_without_request_response("userId", {}, {})
806+
res = client.get(
807+
"/verify", headers={"Authorization": "Bearer " + s.get_access_token()}
808+
)
809+
assert res.status_code == 200
810+
assert list(res.json) == ["handle"]
811+
812+
# Client without middleware
813+
res = client_without_middleware.get("/verify")
814+
assert res.status_code == 401
815+
assert res.json == {"message": "unauthorised"}
816+
817+
res = client_without_middleware.get(
818+
"/verify", headers={"Authorization": "Bearer " + s.get_access_token()}
819+
)
820+
assert res.status_code == 200

tests/sessions/claims/test_verify_session.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
raise_invalid_claims_exception,
1515
ClaimValidationError,
1616
)
17-
from supertokens_python.recipe.session.framework.fastapi import verify_session
17+
from supertokens_python.recipe.session.framework.fastapi import (
18+
verify_session,
19+
session_exception_handler,
20+
)
21+
from supertokens_python.exceptions import SuperTokensError
1822
from supertokens_python.recipe.session.interfaces import (
1923
RecipeInterface,
2024
SessionClaimValidator,
@@ -221,6 +225,10 @@ async def refetched_claim4( # type: ignore
221225
):
222226
return {"handle": s.get_handle()}
223227

228+
@app.post("/verify")
229+
async def _verify(s: SessionContainer = Depends(verify_session())): # type: ignore
230+
return {"handle": s.get_handle()}
231+
224232
return TestClient(app)
225233

226234

@@ -532,3 +540,35 @@ async def test_should_allow_with_custom_claim_returning_true(
532540
res = fastapi_client.get("/refetched-claim-isvalid-true")
533541
assert res.status_code == 200
534542
assert "-" in res.json()["handle"]
543+
544+
545+
@fixture(scope="function")
546+
async def client_without_middleware():
547+
app = FastAPI()
548+
549+
@app.post("/verify")
550+
async def _verify(s: Session = Depends(verify_session())): # type: ignore
551+
return {"handle": s.get_handle()}
552+
553+
app.add_exception_handler(SuperTokensError, session_exception_handler) # type: ignore
554+
555+
return TestClient(app)
556+
557+
558+
async def test_that_verify_session_return_401_if_access_token_is_not_sent_and_middleware_is_not_added(
559+
client_without_middleware: TestClient, fastapi_client: TestClient
560+
):
561+
init(**{**st_init_common_args, "recipe_list": [session.init(get_token_transfer_method=lambda *_: "cookie")]}) # type: ignore
562+
start_st()
563+
564+
res = fastapi_client.post("/verify")
565+
assert res.status_code == 401
566+
assert res.json() == {"message": "unauthorised"}
567+
568+
create_session(fastapi_client)
569+
res = fastapi_client.post("/verify")
570+
assert res.status_code == 200
571+
572+
res = client_without_middleware.post("/verify")
573+
assert res.status_code == 401
574+
assert res.json() == {"message": "unauthorised"}

0 commit comments

Comments
 (0)