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):