diff --git a/api/custom_auth/serializers.py b/api/custom_auth/serializers.py index 03cab2cad5fa..9d0058fb4676 100644 --- a/api/custom_auth/serializers.py +++ b/api/custom_auth/serializers.py @@ -2,7 +2,12 @@ from common.core.utils import is_saas from django.conf import settings -from djoser.serializers import UserCreateSerializer # type: ignore[import-untyped] +from djoser.serializers import ( # type: ignore[import-untyped] + TokenCreateSerializer, + UserCreateSerializer, +) +from email_validator import EmailNotValidError +from email_validator import validate_email as email_validator from rest_framework import serializers from rest_framework.authtoken.models import Token from rest_framework.exceptions import PermissionDenied @@ -20,6 +25,16 @@ ) +class CustomTokenCreateSerializer(TokenCreateSerializer): # type: ignore[misc] + def validate_email(self, value: str) -> str: + try: + email_validator(value, check_deliverability=False) + except EmailNotValidError: + raise serializers.ValidationError("Invalid email format.") + + return value + + class CustomTokenSerializer(serializers.ModelSerializer): # type: ignore[type-arg] class Meta: model = Token diff --git a/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py b/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py index 48a22e00060d..802653d4ba14 100644 --- a/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py +++ b/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py @@ -746,3 +746,104 @@ def test_marketing_consent_given_defaults_to_true( # Then assert response.status_code == status.HTTP_201_CREATED assert response.json()["marketing_consent_given"] is True + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "invalid_email", + [ + "invalid_email", + "12345", + "invalid@email@com.com", + "invalid_email.com", + "invalid_email@com", + "foo@..!.bar.", + ], +) +def test_register_returns_error_if_email_is_invalid( + api_client: APIClient, + invalid_email: str, +) -> None: + # Given + url = reverse("api-v1:custom_auth:ffadminuser-list") + + password = FFAdminUser.objects.make_random_password() + register_data = { + "email": invalid_email, + "password": password, + "re_password": password, + "first_name": "Test", + "last_name": "User", + } + + # When + response = api_client.post(url, data=register_data) + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()["email"][0] == "Enter a valid email address." + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "invalid_email", + [ + "invalid_email", + "12345", + "invalid@email@com.com", + "invalid_email.com", + "invalid_email@com", + "foo@..!.bar.", + ], +) +def test_login_returns_error_if_email_is_invalid( + api_client: APIClient, + invalid_email: str, +) -> None: + # Given + url = reverse("api-v1:custom_auth:custom-mfa-authtoken-login") + + password = FFAdminUser.objects.make_random_password() + + register_data = { + "email": invalid_email, + "password": password, + } + + # When + response = api_client.post(url, data=register_data) + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()["email"][0] == "Invalid email format." + + +@pytest.mark.django_db +def test_register_and_login_with_email_including_plus_sign( + api_client: APIClient, +) -> None: + # Given + email_address = "valid+withplussign@example.com" + + password = FFAdminUser.objects.make_random_password() + + login_url = reverse("api-v1:custom_auth:custom-mfa-authtoken-login") + register_url = reverse("api-v1:custom_auth:ffadminuser-list") + login_data = { + "email": email_address, + "password": password, + } + register_data = { + **login_data, + "re_password": password, + "first_name": "Test", + "last_name": "User", + } + + # When + register_response = api_client.post(register_url, data=register_data) + login_response = api_client.post(login_url, data=login_data) + + # Then + assert register_response.status_code == status.HTTP_201_CREATED + assert login_response.status_code == status.HTTP_200_OK