diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee61d051e..936c753da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## unreleased
+## [0.12.3] - 2023-02-27
+- Adds APIs and logic to the dashboard recipe to enable email password based login
## [0.12.2] - 2023-02-23
- Fix expiry time of access token cookie.
diff --git a/coreDriverInterfaceSupported.json b/coreDriverInterfaceSupported.json
index 825609c69..260ce4863 100644
--- a/coreDriverInterfaceSupported.json
+++ b/coreDriverInterfaceSupported.json
@@ -1,12 +1,15 @@
{
- "_comment": "contains a list of core-driver interfaces branch names that this core supports",
- "versions": [
- "2.9",
- "2.10",
- "2.11",
- "2.12",
- "2.13",
- "2.14",
- "2.15"
- ]
-}
\ No newline at end of file
+ "_comment": "contains a list of core-driver interfaces branch names that this core supports",
+ "versions": [
+ "2.9",
+ "2.10",
+ "2.11",
+ "2.12",
+ "2.13",
+ "2.14",
+ "2.15",
+ "2.16",
+ "2.17",
+ "2.18"
+ ]
+}
diff --git a/examples/with-flask/with-thirdpartyemailpassword/app.py b/examples/with-flask/with-thirdpartyemailpassword/app.py
index 56e3163be..e54f4c964 100644
--- a/examples/with-flask/with-thirdpartyemailpassword/app.py
+++ b/examples/with-flask/with-thirdpartyemailpassword/app.py
@@ -3,6 +3,7 @@
from dotenv import load_dotenv
from flask import Flask, abort, g, jsonify
from flask_cors import CORS
+
from supertokens_python import (
InputAppInfo,
SupertokensConfig,
@@ -11,9 +12,9 @@
)
from supertokens_python.framework.flask import Middleware
from supertokens_python.recipe import (
+ emailverification,
session,
thirdpartyemailpassword,
- emailverification,
)
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.thirdpartyemailpassword import (
diff --git a/setup.py b/setup.py
index 41d7812f4..9d5305707 100644
--- a/setup.py
+++ b/setup.py
@@ -70,7 +70,7 @@
setup(
name="supertokens_python",
- version="0.12.2",
+ version="0.12.3",
author="SuperTokens",
license="Apache 2.0",
author_email="team@supertokens.com",
diff --git a/supertokens_python/constants.py b/supertokens_python/constants.py
index abb38620e..5f33750b4 100644
--- a/supertokens_python/constants.py
+++ b/supertokens_python/constants.py
@@ -11,8 +11,19 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-SUPPORTED_CDI_VERSIONS = ["2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15"]
-VERSION = "0.12.2"
+SUPPORTED_CDI_VERSIONS = [
+ "2.9",
+ "2.10",
+ "2.11",
+ "2.12",
+ "2.13",
+ "2.14",
+ "2.15",
+ "2.16",
+ "2.17",
+ "2.18",
+]
+VERSION = "0.12.3"
TELEMETRY = "/telemetry"
USER_COUNT = "/users/count"
USER_DELETE = "/user/remove"
@@ -25,4 +36,4 @@
FDI_KEY_HEADER = "fdi-version"
API_VERSION = "/apiversion"
API_VERSION_HEADER = "cdi-version"
-DASHBOARD_VERSION = "0.3"
+DASHBOARD_VERSION = "0.4"
diff --git a/supertokens_python/querier.py b/supertokens_python/querier.py
index 0c8deff63..b8eb8d827 100644
--- a/supertokens_python/querier.py
+++ b/supertokens_python/querier.py
@@ -166,11 +166,18 @@ async def f(url: str) -> Response:
return await self.__send_request_helper(path, "POST", f, len(self.__hosts))
- async def send_delete_request(self, path: NormalisedURLPath):
+ async def send_delete_request(
+ self, path: NormalisedURLPath, params: Union[Dict[str, Any], None] = None
+ ):
+ if params is None:
+ params = {}
+
async def f(url: str) -> Response:
async with AsyncClient() as client:
return await client.delete( # type:ignore
- url, headers=await self.__get_headers_with_api_version(path)
+ url,
+ params=params,
+ headers=await self.__get_headers_with_api_version(path),
)
return await self.__send_request_helper(path, "DELETE", f, len(self.__hosts))
diff --git a/supertokens_python/recipe/dashboard/__init__.py b/supertokens_python/recipe/dashboard/__init__.py
index 17df015a2..8476d9d7a 100644
--- a/supertokens_python/recipe/dashboard/__init__.py
+++ b/supertokens_python/recipe/dashboard/__init__.py
@@ -14,16 +14,17 @@
from __future__ import annotations
-from typing import Optional, Callable
+from typing import TYPE_CHECKING, Callable, Optional, Union
-from supertokens_python import AppInfo, RecipeModule
-from supertokens_python.recipe.dashboard.utils import InputOverrideConfig
+if TYPE_CHECKING:
+ from supertokens_python import AppInfo, RecipeModule
+ from supertokens_python.recipe.dashboard.utils import InputOverrideConfig
from .recipe import DashboardRecipe
def init(
- api_key: str,
+ api_key: Union[str, None] = None,
override: Optional[InputOverrideConfig] = None,
) -> Callable[[AppInfo], RecipeModule]:
return DashboardRecipe.init(
diff --git a/supertokens_python/recipe/dashboard/api/__init__.py b/supertokens_python/recipe/dashboard/api/__init__.py
index bdef6df16..fb9e8c6ef 100644
--- a/supertokens_python/recipe/dashboard/api/__init__.py
+++ b/supertokens_python/recipe/dashboard/api/__init__.py
@@ -13,6 +13,8 @@
# under the License.
from .api_key_protector import api_key_protector
from .dashboard import handle_dashboard_api
+from .signin import handle_emailpassword_signin_api
+from .signout import handle_emailpassword_signout_api
from .userdetails.user_delete import handle_user_delete
from .userdetails.user_email_verify_get import handle_user_email_verify_get
from .userdetails.user_email_verify_put import handle_user_email_verify_put
@@ -45,4 +47,6 @@
"handle_user_sessions_post",
"handle_user_password_put",
"handle_email_verify_token_post",
+ "handle_emailpassword_signin_api",
+ "handle_emailpassword_signout_api",
]
diff --git a/supertokens_python/recipe/dashboard/api/implementation.py b/supertokens_python/recipe/dashboard/api/implementation.py
index 345fb052b..f17c6a3bf 100644
--- a/supertokens_python/recipe/dashboard/api/implementation.py
+++ b/supertokens_python/recipe/dashboard/api/implementation.py
@@ -17,13 +17,12 @@
from textwrap import dedent
from typing import TYPE_CHECKING, Any, Dict
-from supertokens_python.normalised_url_domain import NormalisedURLDomain
from supertokens_python import Supertokens
+from supertokens_python.normalised_url_domain import NormalisedURLDomain
from supertokens_python.normalised_url_path import NormalisedURLPath
+
from ..constants import DASHBOARD_API
-from ..interfaces import (
- APIInterface,
-)
+from ..interfaces import APIInterface
if TYPE_CHECKING:
from ..interfaces import APIOptions
@@ -48,7 +47,7 @@ async def dashboard_get(
connection_uri = ""
super_tokens_instance = Supertokens.get_instance()
-
+ auth_mode = options.config.auth_mode
connection_uri = super_tokens_instance.supertokens_config.connection_uri
dashboard_path = options.app_info.api_base_path.append(
@@ -65,6 +64,7 @@ async def dashboard_get(
window.staticBasePath = "${bundleDomain}/static"
window.dashboardAppPath = "${dashboardPath}"
window.connectionURI = "${connectionURI}"
+ window.authMode = "${authMode}"
@@ -81,6 +81,7 @@ async def dashboard_get(
bundleDomain=bundle_domain,
dashboardPath=dashboard_path,
connectionURI=connection_uri,
+ authMode=auth_mode,
)
self.dashboard_get = dashboard_get
diff --git a/supertokens_python/recipe/dashboard/api/signin.py b/supertokens_python/recipe/dashboard/api/signin.py
new file mode 100644
index 000000000..f0bc46620
--- /dev/null
+++ b/supertokens_python/recipe/dashboard/api/signin.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
+#
+# This software is licensed under the Apache License, Version 2.0 (the
+# "License") as published by the Apache Software Foundation.
+#
+# You may not use this file except in compliance with the License. You may
+# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions
+
+from supertokens_python.exceptions import raise_bad_input_exception
+from supertokens_python.normalised_url_path import NormalisedURLPath
+from supertokens_python.querier import Querier
+from supertokens_python.utils import send_200_response
+
+
+async def handle_emailpassword_signin_api(_: APIInterface, api_options: APIOptions):
+ body = await api_options.request.json()
+ if body is None:
+ raise_bad_input_exception("Please send body")
+ email = body.get("email")
+ password = body.get("password")
+
+ if email is None or not isinstance(email, str):
+ raise_bad_input_exception("Missing required parameter 'email'")
+ if password is None or not isinstance(password, str):
+ raise_bad_input_exception("Missing required parameter 'password'")
+ response = await Querier.get_instance().send_post_request(
+ NormalisedURLPath("/recipe/dashboard/signin"),
+ {"email": email, "password": password},
+ )
+
+ if "status" in response and response["status"] == "OK":
+ return send_200_response(
+ {"status": "OK", "sessionId": response["sessionId"]}, api_options.response
+ )
+ if "status" in response and response["status"] == "INVALID_CREDENTIALS_ERROR":
+ return send_200_response(
+ {"status": "INVALID_CREDENTIALS_ERROR"},
+ api_options.response,
+ )
+ if "status" in response and response["status"] == "USER_SUSPENDED_ERROR":
+ return send_200_response(
+ {"status": "USER_SUSPENDED_ERROR", "message": response["message"]},
+ api_options.response,
+ )
diff --git a/supertokens_python/recipe/dashboard/api/signout.py b/supertokens_python/recipe/dashboard/api/signout.py
new file mode 100644
index 000000000..cafb3cf98
--- /dev/null
+++ b/supertokens_python/recipe/dashboard/api/signout.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
+#
+# This software is licensed under the Apache License, Version 2.0 (the
+# "License") as published by the Apache Software Foundation.
+#
+# You may not use this file except in compliance with the License. You may
+# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions
+
+from supertokens_python.exceptions import raise_bad_input_exception
+from supertokens_python.normalised_url_path import NormalisedURLPath
+from supertokens_python.querier import Querier
+
+from ..interfaces import SignOutOK
+
+
+async def handle_emailpassword_signout_api(
+ _: APIInterface, api_options: APIOptions
+) -> SignOutOK:
+ if api_options.config.auth_mode == "api-key":
+ return SignOutOK()
+ session_id_form_auth_header = api_options.request.get_header("authorization")
+ if not session_id_form_auth_header:
+ return raise_bad_input_exception(
+ "Neither 'API Key' nor 'Authorization' header was found"
+ )
+ session_id_form_auth_header = session_id_form_auth_header.split()[1]
+ await Querier.get_instance().send_delete_request(
+ NormalisedURLPath("/recipe/dashboard/session"),
+ {"sessionId": session_id_form_auth_header},
+ )
+ return SignOutOK()
diff --git a/supertokens_python/recipe/dashboard/api/validate_key.py b/supertokens_python/recipe/dashboard/api/validate_key.py
index 652c31f09..932a0b76a 100644
--- a/supertokens_python/recipe/dashboard/api/validate_key.py
+++ b/supertokens_python/recipe/dashboard/api/validate_key.py
@@ -22,25 +22,19 @@
)
from supertokens_python.utils import (
- default_user_context,
send_200_response,
send_non_200_response_with_message,
)
+from ..utils import validate_api_key
+
async def handle_validate_key_api(
- api_implementation: APIInterface, api_options: APIOptions
+ _api_implementation: APIInterface, api_options: APIOptions
):
- _ = api_implementation
- should_allow_accesss = await api_options.recipe_implementation.should_allow_access(
- api_options.request,
- api_options.config,
- default_user_context(api_options.request),
- )
- if should_allow_accesss is False:
- return send_non_200_response_with_message(
- "Unauthorized access", 401, api_options.response
- )
+ is_valid_key = validate_api_key(api_options.request, api_options.config)
- return send_200_response({"status": "OK"}, api_options.response)
+ if is_valid_key:
+ return send_200_response({"status": "OK"}, api_options.response)
+ return send_non_200_response_with_message("Unauthorised", 401, api_options.response)
diff --git a/supertokens_python/recipe/dashboard/constants.py b/supertokens_python/recipe/dashboard/constants.py
index 85652d3a2..9505bf658 100644
--- a/supertokens_python/recipe/dashboard/constants.py
+++ b/supertokens_python/recipe/dashboard/constants.py
@@ -8,3 +8,5 @@
USER_SESSION_API = "/api/user/sessions"
USER_PASSWORD_API = "/api/user/password"
USER_EMAIL_VERIFY_TOKEN_API = "/api/user/email/verify/token"
+EMAIL_PASSWORD_SIGN_IN = "/api/signin"
+EMAIL_PASSSWORD_SIGNOUT = "/api/signout"
diff --git a/supertokens_python/recipe/dashboard/interfaces.py b/supertokens_python/recipe/dashboard/interfaces.py
index ecc7f0016..3a5353876 100644
--- a/supertokens_python/recipe/dashboard/interfaces.py
+++ b/supertokens_python/recipe/dashboard/interfaces.py
@@ -19,13 +19,14 @@
from supertokens_python.recipe.session.interfaces import SessionInformationResult
from supertokens_python.types import User
-from ...supertokens import AppInfo
from ...types import APIResponse
-from .utils import DashboardConfig, UserWithMetadata
if TYPE_CHECKING:
from supertokens_python.framework import BaseRequest, BaseResponse
+ from ...supertokens import AppInfo
+ from .utils import DashboardConfig, UserWithMetadata
+
class SessionInfo:
def __init__(self, info: SessionInformationResult) -> None:
@@ -285,3 +286,10 @@ def __init__(self, error: str) -> None:
def to_json(self) -> Dict[str, Any]:
return {"status": self.status, "error": self.error}
+
+
+class SignOutOK(APIResponse):
+ status: str = "OK"
+
+ def to_json(self):
+ return {"status": self.status}
diff --git a/supertokens_python/recipe/dashboard/recipe.py b/supertokens_python/recipe/dashboard/recipe.py
index ad4d98718..76ff3fb5b 100644
--- a/supertokens_python/recipe/dashboard/recipe.py
+++ b/supertokens_python/recipe/dashboard/recipe.py
@@ -23,6 +23,8 @@
api_key_protector,
handle_dashboard_api,
handle_email_verify_token_post,
+ handle_emailpassword_signin_api,
+ handle_emailpassword_signout_api,
handle_metadata_get,
handle_metadata_put,
handle_sessions_get,
@@ -52,6 +54,8 @@
from .constants import (
DASHBOARD_API,
+ EMAIL_PASSSWORD_SIGNOUT,
+ EMAIL_PASSWORD_SIGN_IN,
USER_API,
USER_EMAIL_VERIFY_API,
USER_EMAIL_VERIFY_TOKEN_API,
@@ -78,7 +82,7 @@ def __init__(
self,
recipe_id: str,
app_info: AppInfo,
- api_key: str,
+ api_key: Union[str, None],
override: Union[InputOverrideConfig, None] = None,
):
super().__init__(recipe_id, app_info)
@@ -136,6 +140,10 @@ async def handle_api_request(
return await handle_dashboard_api(self.api_implementation, api_options)
if request_id == VALIDATE_KEY_API:
return await handle_validate_key_api(self.api_implementation, api_options)
+ if request_id == EMAIL_PASSWORD_SIGN_IN:
+ return await handle_emailpassword_signin_api(
+ self.api_implementation, api_options
+ )
# Do API key validation for the remaining APIs
api_function: Optional[
@@ -171,6 +179,8 @@ async def handle_api_request(
api_function = handle_user_password_put
elif request_id == USER_EMAIL_VERIFY_TOKEN_API:
api_function = handle_email_verify_token_post
+ elif request_id == EMAIL_PASSSWORD_SIGNOUT:
+ api_function = handle_emailpassword_signout_api
if api_function is not None:
return await api_key_protector(
@@ -189,7 +199,7 @@ def get_all_cors_headers(self) -> List[str]:
@staticmethod
def init(
- api_key: str,
+ api_key: Union[str, None],
override: Union[InputOverrideConfig, None] = None,
):
def func(app_info: AppInfo):
diff --git a/supertokens_python/recipe/dashboard/recipe_implementation.py b/supertokens_python/recipe/dashboard/recipe_implementation.py
index 1a2934162..9207e8f4d 100644
--- a/supertokens_python/recipe/dashboard/recipe_implementation.py
+++ b/supertokens_python/recipe/dashboard/recipe_implementation.py
@@ -15,12 +15,13 @@
from typing import Any, Dict
-from supertokens_python.framework import BaseRequest
-from .interfaces import (
- RecipeInterface,
-)
from supertokens_python.constants import DASHBOARD_VERSION
-from .utils import DashboardConfig
+from supertokens_python.framework import BaseRequest
+from supertokens_python.normalised_url_path import NormalisedURLPath
+from supertokens_python.querier import Querier
+
+from .interfaces import RecipeInterface
+from .utils import DashboardConfig, validate_api_key
class RecipeImplementation(RecipeInterface):
@@ -33,12 +34,21 @@ async def should_allow_access(
config: DashboardConfig,
user_context: Dict[str, Any],
) -> bool:
- api_key_header_value = request.get_header("authorization")
-
- # We receive the api key as `Bearer API_KEY`, this retrieves just the key
- api_key = api_key_header_value.split(" ")[1] if api_key_header_value else None
-
- if api_key is None:
- return False
-
- return api_key == config.api_key
+ if config.auth_mode == "email-password":
+ auth_header_value = request.get_header("authorization")
+
+ if not auth_header_value:
+ return False
+
+ auth_header_value = auth_header_value.split()[1]
+ session_verification_response = (
+ await Querier.get_instance().send_post_request(
+ NormalisedURLPath("/recipe/dashboard/session/verify"),
+ {"sessionId": auth_header_value},
+ )
+ )
+ return (
+ "status" in session_verification_response
+ and session_verification_response["status"] == "OK"
+ )
+ return validate_api_key(request, config)
diff --git a/supertokens_python/recipe/dashboard/utils.py b/supertokens_python/recipe/dashboard/utils.py
index 8ffeddb6b..2a2cdb523 100644
--- a/supertokens_python/recipe/dashboard/utils.py
+++ b/supertokens_python/recipe/dashboard/utils.py
@@ -15,6 +15,10 @@
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
+if TYPE_CHECKING:
+ from supertokens_python.framework.request import BaseRequest
+ from ...supertokens import AppInfo
+
from supertokens_python.recipe.emailpassword import EmailPasswordRecipe
from supertokens_python.recipe.emailpassword.asyncio import (
get_user_by_id as ep_get_user_by_id,
@@ -43,9 +47,10 @@
from supertokens_python.utils import Awaitable
from ...normalised_url_path import NormalisedURLPath
-from ...supertokens import AppInfo
from .constants import (
DASHBOARD_API,
+ EMAIL_PASSSWORD_SIGNOUT,
+ EMAIL_PASSWORD_SIGN_IN,
USER_API,
USER_EMAIL_VERIFY_API,
USER_EMAIL_VERIFY_TOKEN_API,
@@ -162,21 +167,18 @@ def __init__(
class DashboardConfig:
def __init__(
- self,
- api_key: str,
- override: OverrideConfig,
+ self, api_key: Union[str, None], override: OverrideConfig, auth_mode: str
):
self.api_key = api_key
self.override = override
+ self.auth_mode = auth_mode
def validate_and_normalise_user_input(
# app_info: AppInfo,
- api_key: str,
+ api_key: Union[str, None],
override: Optional[InputOverrideConfig] = None,
) -> DashboardConfig:
- if api_key.strip() == "":
- raise Exception("apiKey provided to Dashboard recipe cannot be empty")
if override is None:
override = InputOverrideConfig()
@@ -187,6 +189,7 @@ def validate_and_normalise_user_input(
functions=override.functions,
apis=override.apis,
),
+ "api-key" if api_key else "email-password",
)
@@ -230,6 +233,10 @@ def get_api_if_matched(path: NormalisedURLPath, method: str) -> Optional[str]:
return USER_PASSWORD_API
if path_str.endswith(USER_EMAIL_VERIFY_TOKEN_API) and method == "post":
return USER_EMAIL_VERIFY_TOKEN_API
+ if path_str.endswith(EMAIL_PASSWORD_SIGN_IN) and method == "post":
+ return EMAIL_PASSWORD_SIGN_IN
+ if path_str.endswith(EMAIL_PASSSWORD_SIGNOUT) and method == "post":
+ return EMAIL_PASSSWORD_SIGNOUT
return None
@@ -385,3 +392,12 @@ def is_recipe_initialised(recipeId: str) -> bool:
pass
return isRecipeInitialised
+
+
+def validate_api_key(req: BaseRequest, config: DashboardConfig) -> bool:
+ api_key_header_value = req.get_header("authorization")
+ if not api_key_header_value:
+ return False
+ # We receieve the api key as `Bearer API_KEY`, this retrieves just the key
+ api_key_header_value = api_key_header_value.split(" ")[1]
+ return api_key_header_value == config.api_key
diff --git a/supertokens_python/recipe/emailverification/interfaces.py b/supertokens_python/recipe/emailverification/interfaces.py
index 1f508548c..666c09ca5 100644
--- a/supertokens_python/recipe/emailverification/interfaces.py
+++ b/supertokens_python/recipe/emailverification/interfaces.py
@@ -14,12 +14,13 @@
from __future__ import annotations
from abc import ABC, abstractmethod
-from typing import TYPE_CHECKING, Any, Dict, Union, Callable, Awaitable, Optional
+from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Union
from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient
from supertokens_python.types import APIResponse, GeneralErrorResponse
-from ..session.interfaces import SessionContainer
+
from ...supertokens import AppInfo
+from ..session.interfaces import SessionContainer
if TYPE_CHECKING:
from supertokens_python.framework import BaseRequest, BaseResponse
diff --git a/supertokens_python/recipe/passwordless/interfaces.py b/supertokens_python/recipe/passwordless/interfaces.py
index bfc8dd844..d861d2747 100644
--- a/supertokens_python/recipe/passwordless/interfaces.py
+++ b/supertokens_python/recipe/passwordless/interfaces.py
@@ -16,22 +16,24 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Union
+from typing_extensions import Literal
+
from supertokens_python.framework import BaseRequest, BaseResponse
from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.types import APIResponse, GeneralErrorResponse
-from typing_extensions import Literal
+
+from ...supertokens import AppInfo
# if TYPE_CHECKING:
from .types import (
DeviceType,
- SMSDeliveryIngredient,
PasswordlessLoginEmailTemplateVars,
PasswordlessLoginSMSTemplateVars,
+ SMSDeliveryIngredient,
User,
)
from .utils import PasswordlessConfig
-from ...supertokens import AppInfo
class CreateCodeOkResult:
diff --git a/tests/auth-react/django3x/mysite/utils.py b/tests/auth-react/django3x/mysite/utils.py
index 32825e903..b08cb9f0f 100644
--- a/tests/auth-react/django3x/mysite/utils.py
+++ b/tests/auth-react/django3x/mysite/utils.py
@@ -2,6 +2,8 @@
from typing import Any, Dict, List, Optional, Union
from dotenv import load_dotenv
+from typing_extensions import Literal
+
from supertokens_python import InputAppInfo, Supertokens, SupertokensConfig, init
from supertokens_python.framework.request import BaseRequest
from supertokens_python.recipe import (
@@ -85,7 +87,6 @@
)
from supertokens_python.recipe.userroles import UserRolesRecipe
from supertokens_python.types import GeneralErrorResponse
-from typing_extensions import Literal
from .store import save_code, save_url_with_token
diff --git a/tests/auth-react/fastapi-server/app.py b/tests/auth-react/fastapi-server/app.py
index 2b2f94a95..eb928bdc2 100644
--- a/tests/auth-react/fastapi-server/app.py
+++ b/tests/auth-react/fastapi-server/app.py
@@ -25,6 +25,8 @@
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import Response
from starlette.types import ASGIApp
+from typing_extensions import Literal
+
from supertokens_python import (
InputAppInfo,
Supertokens,
@@ -131,7 +133,6 @@
create_new_role_or_add_permissions,
)
from supertokens_python.types import GeneralErrorResponse
-from typing_extensions import Literal
load_dotenv()
diff --git a/tests/auth-react/flask-server/app.py b/tests/auth-react/flask-server/app.py
index dc845dd8c..012c0b400 100644
--- a/tests/auth-react/flask-server/app.py
+++ b/tests/auth-react/flask-server/app.py
@@ -17,6 +17,8 @@
from dotenv import load_dotenv
from flask import Flask, g, jsonify, make_response, request
from flask_cors import CORS
+from typing_extensions import Literal
+
from supertokens_python import (
InputAppInfo,
Supertokens,
@@ -124,7 +126,6 @@
create_new_role_or_add_permissions,
)
from supertokens_python.types import GeneralErrorResponse
-from typing_extensions import Literal
load_dotenv()
diff --git a/tests/utils.py b/tests/utils.py
index 3d1e0ec0b..bbcf4f7a4 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -13,7 +13,6 @@
# under the License.
import asyncio
from datetime import datetime, timezone
-from urllib.parse import unquote
from http.cookies import SimpleCookie
from os import environ, kill, remove, scandir
from shutil import rmtree
@@ -21,11 +20,12 @@
from subprocess import DEVNULL, run
from time import sleep
from typing import Any, Dict, List, cast
+from urllib.parse import unquote
+from fastapi.testclient import TestClient
from requests.models import Response
from yaml import FullLoader, dump, load
-from fastapi.testclient import TestClient
from supertokens_python import InputAppInfo, Supertokens, SupertokensConfig
from supertokens_python.process_state import ProcessState
from supertokens_python.recipe.dashboard import DashboardRecipe
@@ -476,7 +476,6 @@ def min_api_version(min_version: str) -> Any:
"""
Skips the test if the local ST core doesn't satisfy
version requirements for the tests.
-
Fetches the core version only once throughout the testing session.
"""
@@ -492,7 +491,6 @@ def wrapper(f: Any) -> Any:
# Import AsyncMock
import sys
-
from unittest.mock import MagicMock
if sys.version_info >= (3, 8):
@@ -529,7 +527,6 @@ def get_st_init_args(recipe_list: List[Any]) -> Dict[str, Any]:
def is_subset(dict1: Any, dict2: Any) -> bool:
"""Check if dict2 is subset of dict1 in a nested manner
-
Iteratively compares list items with recursion if key's value is a list
"""
if isinstance(dict1, list):