Skip to content

Commit a25bc40

Browse files
authored
Merge pull request #291 from supertokens/api-logic-implementation
feat: Api logic implementation
2 parents 11a2feb + 63eddae commit a25bc40

File tree

19 files changed

+156
-80
lines changed

19 files changed

+156
-80
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dotenv import load_dotenv
44
from flask import Flask, abort, g, jsonify
55
from flask_cors import CORS
6+
67
from supertokens_python import (
78
InputAppInfo,
89
SupertokensConfig,
@@ -11,9 +12,9 @@
1112
)
1213
from supertokens_python.framework.flask import Middleware
1314
from supertokens_python.recipe import (
15+
emailverification,
1416
session,
1517
thirdpartyemailpassword,
16-
emailverification,
1718
)
1819
from supertokens_python.recipe.session.framework.flask import verify_session
1920
from supertokens_python.recipe.thirdpartyemailpassword import (

supertokens_python/querier.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,18 @@ async def f(url: str) -> Response:
166166

167167
return await self.__send_request_helper(path, "POST", f, len(self.__hosts))
168168

169-
async def send_delete_request(self, path: NormalisedURLPath):
169+
async def send_delete_request(
170+
self, path: NormalisedURLPath, params: Union[Dict[str, Any], None] = None
171+
):
172+
if params is None:
173+
params = {}
174+
170175
async def f(url: str) -> Response:
171176
async with AsyncClient() as client:
172177
return await client.delete( # type:ignore
173-
url, headers=await self.__get_headers_with_api_version(path)
178+
url,
179+
params=params,
180+
headers=await self.__get_headers_with_api_version(path),
174181
)
175182

176183
return await self.__send_request_helper(path, "DELETE", f, len(self.__hosts))

supertokens_python/recipe/dashboard/__init__.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Optional, Callable
17+
from typing import TYPE_CHECKING, Callable, Optional, Union
1818

19-
from supertokens_python import AppInfo, RecipeModule
20-
from supertokens_python.recipe.dashboard.utils import InputOverrideConfig
21-
22-
from .recipe import DashboardRecipe
19+
if TYPE_CHECKING:
20+
from supertokens_python import AppInfo, RecipeModule
21+
from supertokens_python.recipe.dashboard.utils import InputOverrideConfig
2322

2423

2524
def init(
26-
api_key: str,
25+
api_key: Union[str, None] = None,
2726
override: Optional[InputOverrideConfig] = None,
2827
) -> Callable[[AppInfo], RecipeModule]:
28+
# Global import for the following was avoided because of circular import errors
29+
from .recipe import DashboardRecipe
30+
2931
return DashboardRecipe.init(
3032
api_key,
3133
override,

supertokens_python/recipe/dashboard/api/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# under the License.
1414
from .api_key_protector import api_key_protector
1515
from .dashboard import handle_dashboard_api
16-
from .signin import handle_sign_in_api
17-
from .signout import handle_signout
16+
from .signin import handle_emailpassword_signin_api
17+
from .signout import handle_emailpassword_signout_api
1818
from .userdetails.user_delete import handle_user_delete
1919
from .userdetails.user_email_verify_get import handle_user_email_verify_get
2020
from .userdetails.user_email_verify_put import handle_user_email_verify_put
@@ -47,6 +47,6 @@
4747
"handle_user_sessions_post",
4848
"handle_user_password_put",
4949
"handle_email_verify_token_post",
50-
"handle_sign_in_api",
51-
"handle_signout",
50+
"handle_emailpassword_signin_api",
51+
"handle_emailpassword_signout_api",
5252
]

supertokens_python/recipe/dashboard/api/implementation.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717
from textwrap import dedent
1818
from typing import TYPE_CHECKING, Any, Dict
1919

20-
from supertokens_python.normalised_url_domain import NormalisedURLDomain
2120
from supertokens_python import Supertokens
21+
from supertokens_python.normalised_url_domain import NormalisedURLDomain
2222
from supertokens_python.normalised_url_path import NormalisedURLPath
23+
2324
from ..constants import DASHBOARD_API
24-
from ..interfaces import (
25-
APIInterface,
26-
)
25+
from ..interfaces import APIInterface
2726

2827
if TYPE_CHECKING:
2928
from ..interfaces import APIOptions
@@ -48,7 +47,7 @@ async def dashboard_get(
4847

4948
connection_uri = ""
5049
super_tokens_instance = Supertokens.get_instance()
51-
50+
auth_mode = options.config.auth_mode
5251
connection_uri = super_tokens_instance.supertokens_config.connection_uri
5352

5453
dashboard_path = options.app_info.api_base_path.append(
@@ -65,6 +64,7 @@ async def dashboard_get(
6564
window.staticBasePath = "${bundleDomain}/static"
6665
window.dashboardAppPath = "${dashboardPath}"
6766
window.connectionURI = "${connectionURI}"
67+
window.authMode = "${authMode}"
6868
</script>
6969
<script defer src="${bundleDomain}/static/js/bundle.js"></script></head>
7070
<link href="${bundleDomain}/static/css/main.css" rel="stylesheet" type="text/css">
@@ -81,6 +81,7 @@ async def dashboard_get(
8181
bundleDomain=bundle_domain,
8282
dashboardPath=dashboard_path,
8383
connectionURI=connection_uri,
84+
authMode=auth_mode,
8485
)
8586

8687
self.dashboard_get = dashboard_get

supertokens_python/recipe/dashboard/api/signin.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,39 @@
1818
if TYPE_CHECKING:
1919
from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions
2020

21-
# pylint: disable=unused-argument
22-
async def handle_sign_in_api(api_implementation: APIInterface, api_options: APIOptions):
23-
pass
21+
from supertokens_python.exceptions import raise_bad_input_exception
22+
from supertokens_python.normalised_url_path import NormalisedURLPath
23+
from supertokens_python.querier import Querier
24+
from supertokens_python.utils import send_200_response
25+
26+
27+
async def handle_emailpassword_signin_api(_: APIInterface, api_options: APIOptions):
28+
body = await api_options.request.json()
29+
if body is None:
30+
raise_bad_input_exception("Please send body")
31+
email = body.get("email")
32+
password = body.get("password")
33+
34+
if email is None or not isinstance(email, str):
35+
raise_bad_input_exception("Missing required parameter 'email'")
36+
if password is None or not isinstance(password, str):
37+
raise_bad_input_exception("Missing required parameter 'password'")
38+
response = await Querier.get_instance().send_post_request(
39+
NormalisedURLPath("/recipe/dashboard/signin"),
40+
{"email": email, "password": password},
41+
)
42+
43+
if "status" in response and response["status"] == "OK":
44+
return send_200_response(
45+
{"status": "OK", "sessionId": response["sessionId"]}, api_options.response
46+
)
47+
if "status" in response and response["status"] == "INVALID_CREDENTIALS_ERROR":
48+
return send_200_response(
49+
{"status": "INVALID_CREDENTIALS_ERROR"},
50+
api_options.response,
51+
)
52+
if "status" in response and response["status"] == "USER_SUSPENDED_ERROR":
53+
return send_200_response(
54+
{"status": "USER_SUSPENDED_ERROR", "message": response["message"]},
55+
api_options.response,
56+
)

supertokens_python/recipe/dashboard/api/signout.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,26 @@
1818
if TYPE_CHECKING:
1919
from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions
2020

21+
from supertokens_python.exceptions import raise_bad_input_exception
22+
from supertokens_python.normalised_url_path import NormalisedURLPath
23+
from supertokens_python.querier import Querier
24+
2125
from ..interfaces import SignOutOK
2226

2327

24-
# pylint: disable=unused-argument
25-
async def handle_signout(
26-
api_implementation: APIInterface, api_options: APIOptions
28+
async def handle_emailpassword_signout_api(
29+
_: APIInterface, api_options: APIOptions
2730
) -> SignOutOK:
31+
if api_options.config.auth_mode == "api-key":
32+
return SignOutOK()
33+
session_id_form_auth_header = api_options.request.get_header("authorization")
34+
if not session_id_form_auth_header:
35+
return raise_bad_input_exception(
36+
"Neither 'API Key' nor 'Authorization' header was found"
37+
)
38+
session_id_form_auth_header = session_id_form_auth_header.split()[1]
39+
await Querier.get_instance().send_delete_request(
40+
NormalisedURLPath("/recipe/dashboard/session"),
41+
{"sessionId": session_id_form_auth_header},
42+
)
2843
return SignOutOK()

supertokens_python/recipe/dashboard/api/validate_key.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,19 @@
2222
)
2323

2424
from supertokens_python.utils import (
25-
default_user_context,
2625
send_200_response,
2726
send_non_200_response_with_message,
2827
)
2928

29+
from ..utils import validate_api_key
30+
3031

3132
async def handle_validate_key_api(
32-
api_implementation: APIInterface, api_options: APIOptions
33+
_api_implementation: APIInterface, api_options: APIOptions
3334
):
34-
_ = api_implementation
3535

36-
should_allow_accesss = await api_options.recipe_implementation.should_allow_access(
37-
api_options.request,
38-
api_options.config,
39-
default_user_context(api_options.request),
40-
)
41-
if should_allow_accesss is False:
42-
return send_non_200_response_with_message(
43-
"Unauthorized access", 401, api_options.response
44-
)
36+
is_valid_key = validate_api_key(api_options.request, api_options.config)
4537

46-
return send_200_response({"status": "OK"}, api_options.response)
38+
if is_valid_key:
39+
return send_200_response({"status": "OK"}, api_options.response)
40+
return send_non_200_response_with_message("Unauthorised", 401, api_options.response)

supertokens_python/recipe/dashboard/interfaces.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919
from supertokens_python.recipe.session.interfaces import SessionInformationResult
2020
from supertokens_python.types import User
2121

22-
from ...supertokens import AppInfo
2322
from ...types import APIResponse
24-
from .utils import DashboardConfig, UserWithMetadata
2523

2624
if TYPE_CHECKING:
2725
from supertokens_python.framework import BaseRequest, BaseResponse
2826

27+
from ...supertokens import AppInfo
28+
from .utils import DashboardConfig, UserWithMetadata
29+
2930

3031
class SessionInfo:
3132
def __init__(self, info: SessionInformationResult) -> None:
@@ -288,5 +289,7 @@ def to_json(self) -> Dict[str, Any]:
288289

289290

290291
class SignOutOK(APIResponse):
292+
status: str = "OK"
293+
291294
def to_json(self):
292-
return {"status": "OK"}
295+
return {"status": self.status}

supertokens_python/recipe/dashboard/recipe.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
api_key_protector,
2424
handle_dashboard_api,
2525
handle_email_verify_token_post,
26+
handle_emailpassword_signin_api,
27+
handle_emailpassword_signout_api,
2628
handle_metadata_get,
2729
handle_metadata_put,
2830
handle_sessions_get,
29-
handle_sign_in_api,
30-
handle_signout,
3131
handle_user_delete,
3232
handle_user_email_verify_get,
3333
handle_user_email_verify_put,
@@ -141,7 +141,9 @@ async def handle_api_request(
141141
if request_id == VALIDATE_KEY_API:
142142
return await handle_validate_key_api(self.api_implementation, api_options)
143143
if request_id == EMAIL_PASSWORD_SIGN_IN:
144-
return await handle_sign_in_api(self.api_implementation, api_options)
144+
return await handle_emailpassword_signin_api(
145+
self.api_implementation, api_options
146+
)
145147

146148
# Do API key validation for the remaining APIs
147149
api_function: Optional[
@@ -178,7 +180,7 @@ async def handle_api_request(
178180
elif request_id == USER_EMAIL_VERIFY_TOKEN_API:
179181
api_function = handle_email_verify_token_post
180182
elif request_id == EMAIL_PASSSWORD_SIGNOUT:
181-
api_function = handle_signout
183+
api_function = handle_emailpassword_signout_api
182184

183185
if api_function is not None:
184186
return await api_key_protector(

supertokens_python/recipe/dashboard/recipe_implementation.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515

1616
from typing import Any, Dict
1717

18-
from supertokens_python.framework import BaseRequest
19-
from .interfaces import (
20-
RecipeInterface,
21-
)
2218
from supertokens_python.constants import DASHBOARD_VERSION
23-
from .utils import DashboardConfig
19+
from supertokens_python.framework import BaseRequest
20+
from supertokens_python.normalised_url_path import NormalisedURLPath
21+
from supertokens_python.querier import Querier
22+
23+
from .interfaces import RecipeInterface
24+
from .utils import DashboardConfig, validate_api_key
2425

2526

2627
class RecipeImplementation(RecipeInterface):
@@ -33,12 +34,21 @@ async def should_allow_access(
3334
config: DashboardConfig,
3435
user_context: Dict[str, Any],
3536
) -> bool:
36-
api_key_header_value = request.get_header("authorization")
37-
38-
# We receive the api key as `Bearer API_KEY`, this retrieves just the key
39-
api_key = api_key_header_value.split(" ")[1] if api_key_header_value else None
40-
41-
if api_key is None:
42-
return False
43-
44-
return api_key == config.api_key
37+
if not config.api_key:
38+
auth_header_value = request.get_header("authorization")
39+
40+
if not auth_header_value:
41+
return False
42+
43+
auth_header_value = auth_header_value.split()[1]
44+
session_verification_response = (
45+
await Querier.get_instance().send_post_request(
46+
NormalisedURLPath("/recipe/dashboard/session/verify"),
47+
{"sessionId": auth_header_value},
48+
)
49+
)
50+
return (
51+
"status" in session_verification_response
52+
and session_verification_response["status"] == "OK"
53+
)
54+
return validate_api_key(request, config)

supertokens_python/recipe/dashboard/utils.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
if TYPE_CHECKING:
1919
from supertokens_python.framework.request import BaseRequest
20+
from ...supertokens import AppInfo
2021

2122
from supertokens_python.recipe.emailpassword import EmailPasswordRecipe
2223
from supertokens_python.recipe.emailpassword.asyncio import (
@@ -46,7 +47,6 @@
4647
from supertokens_python.utils import Awaitable
4748

4849
from ...normalised_url_path import NormalisedURLPath
49-
from ...supertokens import AppInfo
5050
from .constants import (
5151
DASHBOARD_API,
5252
EMAIL_PASSSWORD_SIGNOUT,
@@ -394,8 +394,10 @@ def is_recipe_initialised(recipeId: str) -> bool:
394394
return isRecipeInitialised
395395

396396

397-
def validate_APIKey(req: BaseRequest, config: DashboardConfig) -> bool:
398-
apiKeyHeaderValue = req.get_header("authorization")
399-
if not apiKeyHeaderValue:
397+
def validate_api_key(req: BaseRequest, config: DashboardConfig) -> bool:
398+
api_key_header_value = req.get_header("authorization")
399+
if not api_key_header_value:
400400
return False
401-
return apiKeyHeaderValue == config.api_key
401+
# We receieve the api key as `Bearer API_KEY`, this retrieves just the key
402+
api_key_header_value = api_key_header_value.split(" ")[1]
403+
return api_key_header_value == config.api_key

supertokens_python/recipe/emailverification/interfaces.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
from __future__ import annotations
1515

1616
from abc import ABC, abstractmethod
17-
from typing import TYPE_CHECKING, Any, Dict, Union, Callable, Awaitable, Optional
17+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Union
1818

1919
from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient
2020
from supertokens_python.types import APIResponse, GeneralErrorResponse
21+
2122
from ..session.interfaces import SessionContainer
22-
from ...supertokens import AppInfo
2323

2424
if TYPE_CHECKING:
2525
from supertokens_python.framework import BaseRequest, BaseResponse
2626

27+
from ...supertokens import AppInfo
2728
from .types import User, VerificationEmailTemplateVars
2829
from .utils import EmailVerificationConfig
2930

0 commit comments

Comments
 (0)