Skip to content

Commit dd17c8b

Browse files
Merge pull request #523 from deepjyoti30Alt/fix/optional-form-validation
Fix optional form validation
2 parents 41da84d + ea8f567 commit dd17c8b

File tree

2 files changed

+167
-14
lines changed

2 files changed

+167
-14
lines changed

supertokens_python/recipe/emailpassword/api/utils.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,26 @@ async def validate_form_or_throw_error(
3434
tenant_id: str,
3535
):
3636
validation_errors: List[ErrorFormField] = []
37-
if len(config_form_fields) != len(inputs):
38-
raise_bad_input_exception("Are you sending too many / too few formFields?")
37+
if len(config_form_fields) < len(inputs):
38+
raise_bad_input_exception("Are you sending too many formFields?")
3939

4040
for field in config_form_fields:
4141
input_field: Union[None, FormField] = find_first_occurrence_in_list(
4242
lambda x: x.id == field.id, inputs
4343
)
44-
if input_field is None or (input_field.value == "" and not field.optional):
44+
is_invalid_value = input_field is None or input_field.value == ""
45+
if not field.optional and is_invalid_value:
4546
validation_errors.append(ErrorFormField(field.id, "Field is not optional"))
46-
else:
47-
error = await field.validate(input_field.value, tenant_id)
48-
if error is not None:
49-
validation_errors.append(ErrorFormField(field.id, error))
47+
continue
48+
49+
# If the field was invalid and not optional, execution won't reach here.
50+
# so we need to skip it if the value is invalid and optional.
51+
if is_invalid_value:
52+
continue
53+
54+
error = await field.validate(input_field.value, tenant_id)
55+
if error is not None:
56+
validation_errors.append(ErrorFormField(field.id, error))
5057

5158
if len(validation_errors) != 0:
5259
# raise BadInputError(msg="Error in input formFields")

tests/emailpassword/test_signin.py

Lines changed: 153 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
from fastapi import FastAPI
1818
from fastapi.requests import Request
19-
from tests.testclient import TestClientWithNoCookieJar as TestClient
2019
from pytest import fixture, mark
20+
2121
from supertokens_python import InputAppInfo, SupertokensConfig, init
2222
from supertokens_python.asyncio import delete_user, get_user_count
2323
from supertokens_python.framework.fastapi import get_middleware
@@ -31,6 +31,7 @@
3131
refresh_session,
3232
)
3333
from supertokens_python.utils import is_version_gte
34+
from tests.testclient import TestClientWithNoCookieJar as TestClient
3435
from tests.utils import (
3536
clean_st,
3637
extract_all_cookies,
@@ -424,9 +425,11 @@ async def test_bad_input_not_a_JSON_to_signin_api(driver_config_client: TestClie
424425

425426
response_2 = driver_config_client.post(url="/auth/signin", json={"x": "y"})
426427

427-
assert response_2.status_code == 400
428+
assert response_2.status_code == 200
428429
dict_response = json.loads(response_2.text)
429-
assert dict_response["message"] == "Are you sending too many / too few formFields?"
430+
assert dict_response["status"] == "FIELD_ERROR"
431+
assert len(dict_response["formFields"]) == 2
432+
assert dict_response["formFields"][0]["error"] == "Field is not optional"
430433

431434

432435
@mark.asyncio
@@ -456,9 +459,11 @@ async def test_bad_input_not_a_JSON_to_signin_API(driver_config_client: TestClie
456459

457460
response_2 = driver_config_client.post(url="/auth/signin", json={"x": "y"})
458461

459-
assert response_2.status_code == 400
462+
assert response_2.status_code == 200
460463
dict_response = json.loads(response_2.text)
461-
assert dict_response["message"] == "Are you sending too many / too few formFields?"
464+
assert dict_response["status"] == "FIELD_ERROR"
465+
assert len(dict_response["formFields"]) == 2
466+
assert dict_response["formFields"][0]["error"] == "Field is not optional"
462467

463468

464469
@mark.asyncio
@@ -580,7 +585,12 @@ async def test_formFields_has_no_email_field(driver_config_client: TestClient):
580585
json={"formFields": [{"id": "password", "value": "validpassword123"}]},
581586
)
582587

583-
assert response_2.status_code == 400
588+
assert response_2.status_code == 200
589+
dict_response = json.loads(response_2.text)
590+
assert dict_response["status"] == "FIELD_ERROR"
591+
assert len(dict_response["formFields"]) == 1
592+
assert dict_response["formFields"][0]["error"] == "Field is not optional"
593+
assert dict_response["formFields"][0]["id"] == "email"
584594

585595

586596
@mark.asyncio
@@ -613,7 +623,12 @@ async def test_formFields_has_no_password_field(driver_config_client: TestClient
613623
json={"formFields": [{"id": "email", "value": "randomgmail.com"}]},
614624
)
615625

616-
assert response_2.status_code == 400
626+
assert response_2.status_code == 200
627+
dict_response = json.loads(response_2.text)
628+
assert dict_response["status"] == "FIELD_ERROR"
629+
assert len(dict_response["formFields"]) == 2
630+
assert dict_response["formFields"][0]["error"] == "Field is not optional"
631+
assert dict_response["formFields"][1]["error"] == "Email is not valid"
617632

618633

619634
@mark.asyncio
@@ -658,4 +673,135 @@ async def test_delete_user(driver_config_client: TestClient):
658673
assert error_raised
659674

660675

676+
@mark.asyncio
677+
async def test_optional_custom_field_without_input(driver_config_client: TestClient):
678+
init(
679+
supertokens_config=SupertokensConfig("http://localhost:3567"),
680+
app_info=InputAppInfo(
681+
app_name="SuperTokens Demo",
682+
api_domain="http://api.supertokens.io",
683+
website_domain="http://supertokens.io",
684+
api_base_path="/auth",
685+
),
686+
framework="fastapi",
687+
recipe_list=[
688+
emailpassword.init(
689+
sign_up_feature=emailpassword.InputSignUpFeature(
690+
form_fields=[
691+
emailpassword.InputFormField("test_field", optional=True)
692+
]
693+
)
694+
),
695+
session.init(get_token_transfer_method=lambda _, __, ___: "cookie"),
696+
],
697+
)
698+
start_st()
699+
700+
response_1 = sign_up_request(
701+
driver_config_client, "[email protected]", "validpassword123"
702+
)
703+
assert response_1.status_code == 200
704+
dict_response = json.loads(response_1.text)
705+
assert dict_response["status"] == "OK"
706+
707+
response_2 = driver_config_client.post(
708+
url="/auth/signin",
709+
json={
710+
"formFields": [
711+
{"id": "email", "value": "[email protected]"},
712+
{"id": "password", "value": "validpassword123"},
713+
]
714+
},
715+
)
716+
717+
assert response_2.status_code == 200
718+
dict_response = json.loads(response_2.text)
719+
assert dict_response["status"] == "OK"
720+
721+
722+
@mark.asyncio
723+
async def test_too_many_fields(driver_config_client: TestClient):
724+
init(
725+
supertokens_config=SupertokensConfig("http://localhost:3567"),
726+
app_info=InputAppInfo(
727+
app_name="SuperTokens Demo",
728+
api_domain="http://api.supertokens.io",
729+
website_domain="http://supertokens.io",
730+
api_base_path="/auth",
731+
),
732+
framework="fastapi",
733+
recipe_list=[
734+
emailpassword.init(
735+
sign_up_feature=emailpassword.InputSignUpFeature(
736+
form_fields=[
737+
emailpassword.InputFormField("test_field", optional=True)
738+
]
739+
)
740+
),
741+
session.init(get_token_transfer_method=lambda _, __, ___: "cookie"),
742+
],
743+
)
744+
start_st()
745+
746+
response_1 = sign_up_request(
747+
driver_config_client, "[email protected]", "validpassword123"
748+
)
749+
assert response_1.status_code == 200
750+
dict_response = json.loads(response_1.text)
751+
assert dict_response["status"] == "OK"
752+
753+
response_2 = driver_config_client.post(
754+
url="/auth/signin",
755+
json={
756+
"formFields": [
757+
{"id": "email", "value": "[email protected]"},
758+
{"id": "password", "value": "validpassword123"},
759+
{"id": "test_field", "value": "gmail.com"},
760+
{"id": "extra_field", "value": "[email protected]"},
761+
]
762+
},
763+
)
764+
765+
assert response_2.status_code == 400
766+
dict_response = json.loads(response_2.text)
767+
assert dict_response["message"] == "Are you sending too many formFields?"
768+
769+
770+
@mark.asyncio
771+
async def test_non_optional_custom_field_without_input(
772+
driver_config_client: TestClient,
773+
):
774+
init(
775+
supertokens_config=SupertokensConfig("http://localhost:3567"),
776+
app_info=InputAppInfo(
777+
app_name="SuperTokens Demo",
778+
api_domain="http://api.supertokens.io",
779+
website_domain="http://supertokens.io",
780+
api_base_path="/auth",
781+
),
782+
framework="fastapi",
783+
recipe_list=[
784+
emailpassword.init(
785+
sign_up_feature=emailpassword.InputSignUpFeature(
786+
form_fields=[
787+
emailpassword.InputFormField("test_field", optional=False)
788+
]
789+
)
790+
),
791+
session.init(get_token_transfer_method=lambda _, __, ___: "cookie"),
792+
],
793+
)
794+
start_st()
795+
796+
response_1 = sign_up_request(
797+
driver_config_client, "[email protected]", "validpassword123"
798+
)
799+
assert response_1.status_code == 200
800+
dict_response = json.loads(response_1.text)
801+
assert dict_response["status"] == "FIELD_ERROR"
802+
assert len(dict_response["formFields"]) == 1
803+
assert dict_response["formFields"][0]["error"] == "Field is not optional"
804+
assert dict_response["formFields"][0]["id"] == "test_field"
805+
806+
661807
# TODO add few more tests

0 commit comments

Comments
 (0)