From a773e8f28f9e8b5dd8d21cdcfebb0ae5aedfcac7 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 10:04:10 +0530 Subject: [PATCH 01/14] fix: Add support for any type in value field instead of only string --- CHANGELOG.md | 5 +++ .../recipe/emailpassword/api/utils.py | 4 +- .../recipe/emailpassword/types.py | 7 ++-- .../recipe/emailpassword/utils.py | 19 +++++---- tests/emailpassword/test_signin.py | 42 +++++++++++++++++++ 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3c6d9a8..b4a7610f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.24.3] - 2024-09-24 + +- Adds support for form field related improvements by making fields accept any type of values +- Adds support for optional fields to properly optional + ## [0.24.2] - 2024-09-03 - Makes optional input form fields truly optional instead of just being able to accept `""`. diff --git a/supertokens_python/recipe/emailpassword/api/utils.py b/supertokens_python/recipe/emailpassword/api/utils.py index 989b017a9..536fec7d4 100644 --- a/supertokens_python/recipe/emailpassword/api/utils.py +++ b/supertokens_python/recipe/emailpassword/api/utils.py @@ -41,7 +41,9 @@ async def validate_form_or_throw_error( input_field: Union[None, FormField] = find_first_occurrence_in_list( lambda x: x.id == field.id, inputs ) - is_invalid_value = input_field is None or input_field.value == "" + is_invalid_value = input_field is None or ( + isinstance(input_field.value, str) and input_field.value == "" + ) if not field.optional and is_invalid_value: validation_errors.append(ErrorFormField(field.id, "Field is not optional")) continue diff --git a/supertokens_python/recipe/emailpassword/types.py b/supertokens_python/recipe/emailpassword/types.py index 41752c2cd..ef3d4b6f9 100644 --- a/supertokens_python/recipe/emailpassword/types.py +++ b/supertokens_python/recipe/emailpassword/types.py @@ -12,7 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. from __future__ import annotations -from typing import Awaitable, Callable, List, TypeVar, Union + +from typing import Any, Awaitable, Callable, List, TypeVar, Union from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.ingredients.emaildelivery.types import ( @@ -53,9 +54,9 @@ def __init__(self, id: str, error: str): # pylint: disable=redefined-builtin class FormField: - def __init__(self, id: str, value: str): # pylint: disable=redefined-builtin + def __init__(self, id: str, value: Any): # pylint: disable=redefined-builtin self.id: str = id - self.value: str = value + self.value: Any = value class InputFormField: diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index 390fc49f4..8d349c1d1 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -14,9 +14,9 @@ from __future__ import annotations from re import fullmatch -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union, Dict -from supertokens_python.framework import BaseRequest +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union +from supertokens_python.framework import BaseRequest from supertokens_python.ingredients.emaildelivery.types import ( EmailDeliveryConfig, EmailDeliveryConfigWithService, @@ -26,17 +26,14 @@ ) from .interfaces import APIInterface, RecipeInterface -from .types import InputFormField, NormalisedFormField, EmailTemplateVars +from .types import EmailTemplateVars, InputFormField, NormalisedFormField if TYPE_CHECKING: from supertokens_python.supertokens import AppInfo from supertokens_python.utils import get_filtered_list -from .constants import ( - FORM_FIELD_EMAIL_ID, - FORM_FIELD_PASSWORD_ID, -) +from .constants import FORM_FIELD_EMAIL_ID, FORM_FIELD_PASSWORD_ID async def default_validator(_: str, __: str) -> Union[str, None]: @@ -261,10 +258,14 @@ def validate_and_normalise_user_input( email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> EmailPasswordConfig: - if sign_up_feature is not None and not isinstance(sign_up_feature, InputSignUpFeature): # type: ignore + # type: ignore + if sign_up_feature is not None and not isinstance( + sign_up_feature, InputSignUpFeature + ): raise ValueError("sign_up_feature must be of type InputSignUpFeature or None") - if override is not None and not isinstance(override, InputOverrideConfig): # type: ignore + # type: ignore + if override is not None and not isinstance(override, InputOverrideConfig): raise ValueError("override must be of type InputOverrideConfig or None") if override is None: diff --git a/tests/emailpassword/test_signin.py b/tests/emailpassword/test_signin.py index c27174dd2..e2e5b3575 100644 --- a/tests/emailpassword/test_signin.py +++ b/tests/emailpassword/test_signin.py @@ -719,6 +719,48 @@ async def test_optional_custom_field_without_input(driver_config_client: TestCli assert dict_response["status"] == "OK" +@mark.asyncio +async def test_non_optional_custom_field_with_boolean_value( + driver_config_client: TestClient, +): + init( + supertokens_config=SupertokensConfig("http://localhost:3567"), + app_info=InputAppInfo( + app_name="SuperTokens Demo", + api_domain="http://api.supertokens.io", + website_domain="http://supertokens.io", + api_base_path="/auth", + ), + framework="fastapi", + recipe_list=[ + emailpassword.init( + sign_up_feature=emailpassword.InputSignUpFeature( + form_fields=[ + emailpassword.InputFormField("autoVerify", optional=False) + ] + ) + ), + session.init(get_token_transfer_method=lambda _, __, ___: "cookie"), + ], + ) + start_st() + + response_1 = driver_config_client.post( + url="/auth/signup", + headers={"Content-Type": "application/json"}, + json={ + "formFields": [ + {"id": "email", "value": "random@gmail.com"}, + {"id": "password", "value": "validpassword123"}, + {"id": "autoVerify", "value": False}, + ] + }, + ) + assert response_1.status_code == 200 + dict_response = json.loads(response_1.text) + assert dict_response["status"] == "OK" + + @mark.asyncio async def test_too_many_fields(driver_config_client: TestClient): init( From 2002dc0bb06a0dcd4501bf9db39534631c91ccb0 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 13:59:10 +0530 Subject: [PATCH 02/14] Add support for validating type of password and email in form validation --- .../recipe/emailpassword/api/utils.py | 16 +++- .../recipe/emailpassword/utils.py | 11 +-- tests/emailpassword/test_signin.py | 79 +++++++++++++++++++ tests/emailpassword/test_signup.py | 8 +- 4 files changed, 101 insertions(+), 13 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/api/utils.py b/supertokens_python/recipe/emailpassword/api/utils.py index 536fec7d4..06af69e9c 100644 --- a/supertokens_python/recipe/emailpassword/api/utils.py +++ b/supertokens_python/recipe/emailpassword/api/utils.py @@ -16,7 +16,10 @@ from typing import Any, Dict, List, Union from supertokens_python.exceptions import raise_bad_input_exception -from supertokens_python.recipe.emailpassword.constants import FORM_FIELD_EMAIL_ID +from supertokens_python.recipe.emailpassword.constants import ( + FORM_FIELD_EMAIL_ID, + FORM_FIELD_PASSWORD_ID, +) from supertokens_python.recipe.emailpassword.exceptions import ( raise_form_field_exception, ) @@ -85,7 +88,18 @@ async def validate_form_fields_or_throw_error( raise_bad_input_exception( "All elements of formFields must contain an 'id' and 'value' field" ) + value = current_form_field["value"] + if current_form_field["id"] in [ + FORM_FIELD_EMAIL_ID, + FORM_FIELD_PASSWORD_ID, + ] and not isinstance(value, str): + # Ensure that the type is string else we will throw a bad input + # error. + raise_bad_input_exception( + f"{current_form_field['id']} value must be a string" + ) + if current_form_field["id"] == FORM_FIELD_EMAIL_ID and isinstance(value, str): value = value.strip() form_fields.append(FormField(current_form_field["id"], value)) diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index 8d349c1d1..84ad8a803 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -258,15 +258,8 @@ def validate_and_normalise_user_input( email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> EmailPasswordConfig: - # type: ignore - if sign_up_feature is not None and not isinstance( - sign_up_feature, InputSignUpFeature - ): - raise ValueError("sign_up_feature must be of type InputSignUpFeature or None") - - # type: ignore - if override is not None and not isinstance(override, InputOverrideConfig): - raise ValueError("override must be of type InputOverrideConfig or None") + # NOTE: We don't need to check the instance of sign_up_feature and override + # as they will always be either None or the specified type. if override is None: override = InputOverrideConfig() diff --git a/tests/emailpassword/test_signin.py b/tests/emailpassword/test_signin.py index e2e5b3575..1bc192262 100644 --- a/tests/emailpassword/test_signin.py +++ b/tests/emailpassword/test_signin.py @@ -761,6 +761,85 @@ async def test_non_optional_custom_field_with_boolean_value( assert dict_response["status"] == "OK" +@mark.asyncio +async def test_invalid_type_for_email_and_password( + driver_config_client: TestClient, +): + init( + supertokens_config=SupertokensConfig("http://localhost:3567"), + app_info=InputAppInfo( + app_name="SuperTokens Demo", + api_domain="http://api.supertokens.io", + website_domain="http://supertokens.io", + api_base_path="/auth", + ), + framework="fastapi", + recipe_list=[ + emailpassword.init( + sign_up_feature=emailpassword.InputSignUpFeature(form_fields=[]) + ), + session.init(get_token_transfer_method=lambda _, __, ___: "cookie"), + ], + ) + start_st() + + response_1 = driver_config_client.post( + url="/auth/signup", + headers={"Content-Type": "application/json"}, + json={ + "formFields": [ + {"id": "email", "value": 123}, + {"id": "password", "value": "validpassword123"}, + ] + }, + ) + assert response_1.status_code == 400 + dict_response = json.loads(response_1.text) + assert dict_response["message"] == "email value must be a string" + + response_1_signin = driver_config_client.post( + url="/auth/signin", + headers={"Content-Type": "application/json"}, + json={ + "formFields": [ + {"id": "email", "value": 123}, + {"id": "password", "value": "validpassword123"}, + ] + }, + ) + assert response_1_signin.status_code == 400 + dict_response_signin = json.loads(response_1_signin.text) + assert dict_response_signin["message"] == "email value must be a string" + + response_2 = driver_config_client.post( + url="/auth/signup", + headers={"Content-Type": "application/json"}, + json={ + "formFields": [ + {"id": "email", "value": "random@gmail.com"}, + {"id": "password", "value": 12345}, + ] + }, + ) + assert response_2.status_code == 400 + dict_response = json.loads(response_2.text) + assert dict_response["message"] == "password value must be a string" + + response_2_signin = driver_config_client.post( + url="/auth/signin", + headers={"Content-Type": "application/json"}, + json={ + "formFields": [ + {"id": "email", "value": "random@gmail.com"}, + {"id": "password", "value": 12345}, + ] + }, + ) + assert response_2_signin.status_code == 400 + dict_response_signin = json.loads(response_2_signin.text) + assert dict_response_signin["message"] == "password value must be a string" + + @mark.asyncio async def test_too_many_fields(driver_config_client: TestClient): init( diff --git a/tests/emailpassword/test_signup.py b/tests/emailpassword/test_signup.py index 740d729d3..a1acb6030 100644 --- a/tests/emailpassword/test_signup.py +++ b/tests/emailpassword/test_signup.py @@ -11,19 +11,21 @@ # 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 fastapi import FastAPI import json -from tests.testclient import TestClientWithNoCookieJar as TestClient + +from fastapi import FastAPI from pytest import fixture, mark + from supertokens_python import init from supertokens_python.framework.fastapi import get_middleware from supertokens_python.recipe import emailpassword, session +from tests.testclient import TestClientWithNoCookieJar as TestClient from tests.utils import ( get_st_init_args, setup_function, + sign_up_request, start_st, teardown_function, - sign_up_request, ) _ = setup_function # type: ignore From 59161ef9f6ee0ea987815d4a08e07e27fc697c55 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:10:37 +0530 Subject: [PATCH 03/14] Add tests for password reset --- tests/emailpassword/test_passwordreset.py | 47 +++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/emailpassword/test_passwordreset.py b/tests/emailpassword/test_passwordreset.py index 3b76544c0..e618b0bbf 100644 --- a/tests/emailpassword/test_passwordreset.py +++ b/tests/emailpassword/test_passwordreset.py @@ -18,8 +18,8 @@ from fastapi import FastAPI from fastapi.requests import Request -from tests.testclient import TestClientWithNoCookieJar as TestClient from pytest import fixture, mark, raises + from supertokens_python import InputAppInfo, SupertokensConfig, init from supertokens_python.framework import BaseRequest from supertokens_python.framework.fastapi import get_middleware @@ -34,6 +34,7 @@ get_session, refresh_session, ) +from tests.testclient import TestClientWithNoCookieJar as TestClient from tests.utils import clean_st, reset, setup_st, sign_up_request, start_st @@ -178,9 +179,11 @@ async def send_email( assert response_1.status_code == 200 assert reset_url == "http://supertokens.io/auth/reset-password" - assert token_info is not None and "token=" in token_info # type: ignore pylint: disable=unsupported-membership-test + # type: ignore pylint: disable=unsupported-membership-test + assert token_info is not None and "token=" in token_info assert query_length == 2 - assert tenant_info is not None and "tenantId=public" in tenant_info # type: ignore pylint: disable=unsupported-membership-test + # type: ignore pylint: disable=unsupported-membership-test + assert tenant_info is not None and "tenantId=public" in tenant_info @mark.asyncio @@ -223,6 +226,44 @@ async def test_password_validation(driver_config_client: TestClient): assert dict_response["status"] != "FIELD_ERROR" +@mark.asyncio +async def test_invalid_type_for_password_and_email(driver_config_client: TestClient): + init( + supertokens_config=SupertokensConfig("http://localhost:3567"), + app_info=InputAppInfo( + app_name="SuperTokens Demo", + api_domain="http://api.supertokens.io", + website_domain="http://supertokens.io", + api_base_path="/auth", + ), + framework="fastapi", + recipe_list=[emailpassword.init()], + ) + start_st() + + response_1 = driver_config_client.post( + url="/auth/user/password/reset", + json={ + "formFields": [{"id": "password", "value": 12345}], + "token": "random", + }, + ) + + assert response_1.status_code == 400 + assert json.loads(response_1.text)["message"] == "password value must be a string" + + response_2 = driver_config_client.post( + url="/auth/user/password/reset/token", + json={ + "formFields": [{"id": "email", "value": 123456}], + "token": "randomToken", + }, + ) + + assert response_2.status_code == 400 + assert json.loads(response_2.text)["message"] == "email value must be a string" + + @mark.asyncio async def test_token_missing_from_input(driver_config_client: TestClient): init( From 60d89a9054a806d620d74dbdcacc3f3c05e95b82 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:13:12 +0530 Subject: [PATCH 04/14] Bump version --- setup.py | 2 +- supertokens_python/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b779e2733..b8c913000 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ setup( name="supertokens_python", - version="0.24.2", + version="0.24.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 55cefdf95..b921e29af 100644 --- a/supertokens_python/constants.py +++ b/supertokens_python/constants.py @@ -14,7 +14,7 @@ from __future__ import annotations SUPPORTED_CDI_VERSIONS = ["3.0"] -VERSION = "0.24.2" +VERSION = "0.24.3" TELEMETRY = "/telemetry" USER_COUNT = "/users/count" USER_DELETE = "/user/remove" From fc5759a6084bdc5f60d1eef837cc288b39cc100e Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:21:34 +0530 Subject: [PATCH 05/14] Bump node version in pre-commit workflow --- .github/workflows/pre-commit-hook-run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index e8d607157..c29f839d2 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -18,7 +18,7 @@ jobs: - name: Set up node uses: actions/setup-node@v1 with: - node-version: '12' + node-version: '16' - run: git init && git add --all && git -c user.name='test' -c user.email='test@example.com' commit -m 'init for pr action' - run: make dev-install && rm -rf src - run: ./hooks/pre-commit.sh From b7ef159815aaf747b6bc7a7981f7a5cfd27528fe Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:27:32 +0530 Subject: [PATCH 06/14] Add support for installing glibc before checkout for pre-commit hook workflow --- .github/workflows/pre-commit-hook-run.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index c29f839d2..1b612fab9 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -14,11 +14,15 @@ jobs: runs-on: ubuntu-latest container: rishabhpoddar/supertokens_python_driver_testing steps: + - name: Install required GLIBC + run: | + apt-get update && \ + apt-get install -y --no-install-recommends g++ make zlib1g-dev libc6 - uses: actions/checkout@v2 - name: Set up node uses: actions/setup-node@v1 with: - node-version: '16' + node-version: '12' - run: git init && git add --all && git -c user.name='test' -c user.email='test@example.com' commit -m 'init for pr action' - run: make dev-install && rm -rf src - run: ./hooks/pre-commit.sh From 5c5bfcf8ebc6d94d1d13a2a8ae7b4bfe028197bd Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:31:01 +0530 Subject: [PATCH 07/14] Run pre-commit hooks on runner directly --- .github/workflows/pre-commit-hook-run.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index 1b612fab9..12cfa1963 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -12,12 +12,8 @@ jobs: pr-title: name: Pre commit hook check runs-on: ubuntu-latest - container: rishabhpoddar/supertokens_python_driver_testing + # container: rishabhpoddar/supertokens_python_driver_testing steps: - - name: Install required GLIBC - run: | - apt-get update && \ - apt-get install -y --no-install-recommends g++ make zlib1g-dev libc6 - uses: actions/checkout@v2 - name: Set up node uses: actions/setup-node@v1 From 1f1bc5312a2697bfe587b3249b97e58f005ea3f8 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:33:48 +0530 Subject: [PATCH 08/14] Add step to make dummy change to commit and run pre-commit hook --- .github/workflows/pre-commit-hook-run.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index 12cfa1963..8f94e7953 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -19,6 +19,9 @@ jobs: uses: actions/setup-node@v1 with: node-version: '12' + - name: Make a dummy change to README.md + run: | + echo "# Dummy change for PR check" >> README.md - run: git init && git add --all && git -c user.name='test' -c user.email='test@example.com' commit -m 'init for pr action' - run: make dev-install && rm -rf src - run: ./hooks/pre-commit.sh From 8b5ea66c8ba4caa29c86ebec53c7878cfd1d37a7 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:39:56 +0530 Subject: [PATCH 09/14] Add steps to setup virtual env and run pre-commit hook there --- .github/workflows/pre-commit-hook-run.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index 8f94e7953..ce8bf698a 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -19,10 +19,16 @@ jobs: uses: actions/setup-node@v1 with: node-version: '12' + - name: Create virtual environment and install dependencies + run: | + python3 -m venv venv + source venv/bin/activate + make dev-install && rm -rf src - name: Make a dummy change to README.md run: | echo "# Dummy change for PR check" >> README.md - run: git init && git add --all && git -c user.name='test' -c user.email='test@example.com' commit -m 'init for pr action' - - run: make dev-install && rm -rf src - - run: ./hooks/pre-commit.sh + - run: | + source venv/bin/activate + ./hooks/pre-commit.sh \ No newline at end of file From e1749e8c46b623019c5053834c01f2c7e96cf236 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:48:50 +0530 Subject: [PATCH 10/14] Add fix for building pyyaml in pre-commit hook workflow --- .github/workflows/pre-commit-hook-run.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index ce8bf698a..115b7f9a6 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -23,6 +23,8 @@ jobs: run: | python3 -m venv venv source venv/bin/activate + pip install "cython<3.0.0" wheel + pip install "PyYAML==5.4.1" --no-build-isolation make dev-install && rm -rf src - name: Make a dummy change to README.md run: | From fff32b0c475ba84d369124ca7572c849473a9518 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 14:51:36 +0530 Subject: [PATCH 11/14] Remove a commented out line in the pre-commit workflow --- .github/workflows/pre-commit-hook-run.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index 115b7f9a6..c0065b50c 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -12,7 +12,6 @@ jobs: pr-title: name: Pre commit hook check runs-on: ubuntu-latest - # container: rishabhpoddar/supertokens_python_driver_testing steps: - uses: actions/checkout@v2 - name: Set up node From 89b7be283d4453a560b69523144a273aab1d1381 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 24 Sep 2024 17:59:55 +0530 Subject: [PATCH 12/14] adding dev-v0.24.3 tag to this commit to ensure building --- html/supertokens_python/constants.html | 2 +- .../recipe/emailpassword/api/utils.html | 35 +++++++++++++++++-- .../recipe/emailpassword/types.html | 13 +++---- .../recipe/emailpassword/utils.html | 25 +++++-------- 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/html/supertokens_python/constants.html b/html/supertokens_python/constants.html index c38dee44e..102b464af 100644 --- a/html/supertokens_python/constants.html +++ b/html/supertokens_python/constants.html @@ -42,7 +42,7 @@

Module supertokens_python.constants

from __future__ import annotations SUPPORTED_CDI_VERSIONS = ["3.0"] -VERSION = "0.24.2" +VERSION = "0.24.3" TELEMETRY = "/telemetry" USER_COUNT = "/users/count" USER_DELETE = "/user/remove" diff --git a/html/supertokens_python/recipe/emailpassword/api/utils.html b/html/supertokens_python/recipe/emailpassword/api/utils.html index b6c8df83a..d3e575a12 100644 --- a/html/supertokens_python/recipe/emailpassword/api/utils.html +++ b/html/supertokens_python/recipe/emailpassword/api/utils.html @@ -44,7 +44,10 @@

Module supertokens_python.recipe.emailpassword.api.utils from typing import Any, Dict, List, Union from supertokens_python.exceptions import raise_bad_input_exception -from supertokens_python.recipe.emailpassword.constants import FORM_FIELD_EMAIL_ID +from supertokens_python.recipe.emailpassword.constants import ( + FORM_FIELD_EMAIL_ID, + FORM_FIELD_PASSWORD_ID, +) from supertokens_python.recipe.emailpassword.exceptions import ( raise_form_field_exception, ) @@ -69,7 +72,9 @@

Module supertokens_python.recipe.emailpassword.api.utils input_field: Union[None, FormField] = find_first_occurrence_in_list( lambda x: x.id == field.id, inputs ) - is_invalid_value = input_field is None or input_field.value == "" + is_invalid_value = input_field is None or ( + isinstance(input_field.value, str) and input_field.value == "" + ) if not field.optional and is_invalid_value: validation_errors.append(ErrorFormField(field.id, "Field is not optional")) continue @@ -111,7 +116,18 @@

Module supertokens_python.recipe.emailpassword.api.utils raise_bad_input_exception( "All elements of formFields must contain an 'id' and 'value' field" ) + value = current_form_field["value"] + if current_form_field["id"] in [ + FORM_FIELD_EMAIL_ID, + FORM_FIELD_PASSWORD_ID, + ] and not isinstance(value, str): + # Ensure that the type is string else we will throw a bad input + # error. + raise_bad_input_exception( + f"{current_form_field['id']} value must be a string" + ) + if current_form_field["id"] == FORM_FIELD_EMAIL_ID and isinstance(value, str): value = value.strip() form_fields.append(FormField(current_form_field["id"], value)) @@ -157,7 +173,18 @@

Functions

raise_bad_input_exception( "All elements of formFields must contain an 'id' and 'value' field" ) + value = current_form_field["value"] + if current_form_field["id"] in [ + FORM_FIELD_EMAIL_ID, + FORM_FIELD_PASSWORD_ID, + ] and not isinstance(value, str): + # Ensure that the type is string else we will throw a bad input + # error. + raise_bad_input_exception( + f"{current_form_field['id']} value must be a string" + ) + if current_form_field["id"] == FORM_FIELD_EMAIL_ID and isinstance(value, str): value = value.strip() form_fields.append(FormField(current_form_field["id"], value)) @@ -188,7 +215,9 @@

Functions

input_field: Union[None, FormField] = find_first_occurrence_in_list( lambda x: x.id == field.id, inputs ) - is_invalid_value = input_field is None or input_field.value == "" + is_invalid_value = input_field is None or ( + isinstance(input_field.value, str) and input_field.value == "" + ) if not field.optional and is_invalid_value: validation_errors.append(ErrorFormField(field.id, "Field is not optional")) continue diff --git a/html/supertokens_python/recipe/emailpassword/types.html b/html/supertokens_python/recipe/emailpassword/types.html index 9e46ef7d4..c71226e44 100644 --- a/html/supertokens_python/recipe/emailpassword/types.html +++ b/html/supertokens_python/recipe/emailpassword/types.html @@ -40,7 +40,8 @@

Module supertokens_python.recipe.emailpassword.typesModule supertokens_python.recipe.emailpassword.typesClasses

class FormField -(id: str, value: str) +(id: str, value: Any)
@@ -206,9 +207,9 @@

Classes

Expand source code
class FormField:
-    def __init__(self, id: str, value: str):  # pylint: disable=redefined-builtin
+    def __init__(self, id: str, value: Any):  # pylint: disable=redefined-builtin
         self.id: str = id
-        self.value: str = value
+ self.value: Any = value
diff --git a/html/supertokens_python/recipe/emailpassword/utils.html b/html/supertokens_python/recipe/emailpassword/utils.html index ea0da10df..ed2bb11f5 100644 --- a/html/supertokens_python/recipe/emailpassword/utils.html +++ b/html/supertokens_python/recipe/emailpassword/utils.html @@ -42,9 +42,9 @@

Module supertokens_python.recipe.emailpassword.utilsModule supertokens_python.recipe.emailpassword.utilsModule supertokens_python.recipe.emailpassword.utilsFunctions

email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> EmailPasswordConfig: - if sign_up_feature is not None and not isinstance(sign_up_feature, InputSignUpFeature): # type: ignore - raise ValueError("sign_up_feature must be of type InputSignUpFeature or None") - - if override is not None and not isinstance(override, InputOverrideConfig): # type: ignore - raise ValueError("override must be of type InputOverrideConfig or None") + # NOTE: We don't need to check the instance of sign_up_feature and override + # as they will always be either None or the specified type. if override is None: override = InputOverrideConfig() From 674cc6ef683ee82aa536e7f12ef09b45c28238a8 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 24 Sep 2024 20:28:15 +0530 Subject: [PATCH 13/14] fix: Failing tests regarding any type support in formFields This commit fixes issues with some tests that were failing after adding support for any type of field values in formFields --- tests/emailpassword/test_passwordreset.py | 9 +++- .../input_validation/test_input_validation.py | 51 ++++++------------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/tests/emailpassword/test_passwordreset.py b/tests/emailpassword/test_passwordreset.py index e618b0bbf..f684b3290 100644 --- a/tests/emailpassword/test_passwordreset.py +++ b/tests/emailpassword/test_passwordreset.py @@ -119,9 +119,14 @@ async def test_email_validation_checks_in_generate_token_API( json={"formFields": [{"id": "email", "value": invalid_email}]}, ) - assert res.status_code == 200 dict_res = json.loads(res.text) - assert dict_res["status"] == "FIELD_ERROR" + assert res.status_code == 200 if invalid_email == "random" else 400 + if invalid_email == "random": + assert dict_res["status"] == "FIELD_ERROR" + assert dict_res["formFields"][0]["id"] == "email" + assert dict_res["formFields"][0]["error"] == "Email is not valid" + else: + assert dict_res["message"] == "email value must be a string" @mark.asyncio diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index 96beec0ea..08728031c 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -2,6 +2,7 @@ from typing import Any, Dict, List import pytest + from supertokens_python import InputAppInfo, SupertokensConfig, init from supertokens_python.recipe import ( emailpassword, @@ -31,22 +32,6 @@ async def test_init_validation_emailpassword(): ) assert "app_info must be an instance of InputAppInfo" == str(ex.value) - with pytest.raises(ValueError) as ex: - init( - supertokens_config=SupertokensConfig("http://localhost:3567"), - app_info=InputAppInfo( - app_name="SuperTokens Demo", - api_domain="http://api.supertokens.io", - website_domain="http://supertokens.io", - api_base_path="/auth", - ), - framework="fastapi", - recipe_list=[ - emailpassword.init(sign_up_feature="sign up"), # type: ignore - ], - ) - assert "sign_up_feature must be of type InputSignUpFeature or None" == str(ex.value) - with pytest.raises(ValueError) as ex: init( supertokens_config=SupertokensConfig("http://localhost:3567"), @@ -67,22 +52,6 @@ async def test_init_validation_emailpassword(): == str(ex.value) ) - with pytest.raises(ValueError) as ex: - init( - supertokens_config=SupertokensConfig("http://localhost:3567"), - app_info=InputAppInfo( - app_name="SuperTokens Demo", - api_domain="http://api.supertokens.io", - website_domain="http://supertokens.io", - api_base_path="/auth", - ), - framework="fastapi", - recipe_list=[ - emailpassword.init(override="override"), # type: ignore - ], - ) - assert "override must be of type InputOverrideConfig or None" == str(ex.value) - async def get_email_for_user_id(_: str, __: Dict[str, Any]): return GetEmailForUserIdOkResult("foo@example.com") @@ -307,7 +276,9 @@ async def send_email( clients=[ thirdparty.ProviderClientConfig( client_id=os.environ.get("GOOGLE_CLIENT_ID"), # type: ignore - client_secret=os.environ.get("GOOGLE_CLIENT_SECRET"), # type: ignore + client_secret=os.environ.get( + "GOOGLE_CLIENT_SECRET" + ), # type: ignore ) ], ) @@ -318,7 +289,9 @@ async def send_email( clients=[ thirdparty.ProviderClientConfig( client_id=os.environ.get("FACEBOOK_CLIENT_ID"), # type: ignore - client_secret=os.environ.get("FACEBOOK_CLIENT_SECRET"), # type: ignore + client_secret=os.environ.get( + "FACEBOOK_CLIENT_SECRET" + ), # type: ignore ) ], ) @@ -329,7 +302,9 @@ async def send_email( clients=[ thirdparty.ProviderClientConfig( client_id=os.environ.get("GITHUB_CLIENT_ID"), # type: ignore - client_secret=os.environ.get("GITHUB_CLIENT_SECRET"), # type: ignore + client_secret=os.environ.get( + "GITHUB_CLIENT_SECRET" + ), # type: ignore ) ], ) @@ -365,6 +340,9 @@ async def test_init_validation_session(): api_base_path="/auth", ), framework="fastapi", + # NOTE: Type is ignored in the following line because that + # is what is being tested for so that the SDK throws an error + # on invalid type. recipe_list=[session.init(error_handlers="error handlers")], # type: ignore ) assert "error_handlers must be an instance of ErrorHandlers or None" == str( @@ -401,6 +379,9 @@ async def test_init_validation_thirdparty(): ), framework="fastapi", recipe_list=[ + # NOTE: Type is ignored in the following line because that + # is what is being tested for so that the SDK throws an error + # on invalid type. thirdparty.init(sign_in_and_up_feature="sign in up") # type: ignore ], ) From acabab017d0ca9ddc2dd941d7735488483c94bef Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 24 Sep 2024 23:33:13 +0530 Subject: [PATCH 14/14] adding dev-v0.24.3 tag to this commit to ensure building