diff --git a/api/custom_auth/serializers.py b/api/custom_auth/serializers.py index 03cab2cad5fa..20975e1f691c 100644 --- a/api/custom_auth/serializers.py +++ b/api/custom_auth/serializers.py @@ -1,8 +1,12 @@ +import re from typing import Any 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 rest_framework import serializers from rest_framework.authtoken.models import Token from rest_framework.exceptions import PermissionDenied @@ -19,6 +23,15 @@ USER_REGISTRATION_WITHOUT_INVITE_ERROR_MESSAGE, ) +EMAIL_REGEX = re.compile(r"^[^@]+@(?:\w+\.)+\w{2,}$") + + +class CustomTokenCreateSerializer(TokenCreateSerializer): # type: ignore[misc] + def validate_email(self, value: str) -> str: + if not EMAIL_REGEX.match(value): + raise serializers.ValidationError("Invalid email format.") + return value + class CustomTokenSerializer(serializers.ModelSerializer): # type: ignore[type-arg] class Meta: diff --git a/api/custom_auth/views.py b/api/custom_auth/views.py index ee4d8d003b60..566bd1c8604a 100644 --- a/api/custom_auth/views.py +++ b/api/custom_auth/views.py @@ -30,7 +30,7 @@ from custom_auth.mfa.trench.responses import ErrorResponse from custom_auth.mfa.trench.serializers import CodeLoginSerializer from custom_auth.mfa.trench.utils import user_token_generator -from custom_auth.serializers import CustomUserDelete +from custom_auth.serializers import CustomTokenCreateSerializer, CustomUserDelete from integrations.lead_tracking.hubspot.services import ( register_hubspot_tracker_and_track_user, ) @@ -46,6 +46,7 @@ class CustomAuthTokenLoginOrRequestMFACode(TokenCreateView): # type: ignore[mis Class to handle throttling for login requests """ + serializer_class = CustomTokenCreateSerializer authentication_classes = [] # type: ignore[var-annotated] throttle_classes = [ScopedRateThrottle] throttle_scope = "login" diff --git a/api/integrations/lead_tracking/hubspot/services.py b/api/integrations/lead_tracking/hubspot/services.py index 2a8665dedf35..893a33ebf005 100644 --- a/api/integrations/lead_tracking/hubspot/services.py +++ b/api/integrations/lead_tracking/hubspot/services.py @@ -69,7 +69,8 @@ def register_hubspot_tracker( def create_self_hosted_onboarding_lead( email: str, first_name: str, last_name: str, organisation_name: str ) -> None: - email_domain = email.split("@")[1] + email_parts = email.split("@") + email_domain = email_parts[1] if len(email_parts) > 1 else organisation_name hubspot_client = HubspotClient() company = hubspot_client.get_company_by_domain(email_domain) if not company: 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 884a989fc373..90f9e3e1d4f9 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 @@ -730,3 +730,33 @@ 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.parametrize( + "invalid_email", + [ + "invalid_email", + "12345", + "invalid@email@com.com", + "invalid_email.com", + "invalid_email@com", + "foo@..!.bar.", + ], +) +def test_create_user_returns_error_if_email_is_invalid( + staff_client: APIClient, + invalid_email: str, +) -> None: + # Given + url = reverse("api-v1:custom_auth:custom-mfa-authtoken-login") + register_data = { + "email": invalid_email, + "password": "password", + } + + # When + response = staff_client.post(url, data=register_data) + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()["email"][0] == "Invalid email format." diff --git a/api/tests/unit/users/test_unit_users_models.py b/api/tests/unit/users/test_unit_users_models.py index e85942b3ab23..e349f538e40c 100644 --- a/api/tests/unit/users/test_unit_users_models.py +++ b/api/tests/unit/users/test_unit_users_models.py @@ -234,5 +234,5 @@ def test_delete_user(): # type: ignore[no-untyped-def] assert Organisation.objects.filter(name="org1").count() == 1 -def test_user_email_domain_property(): # type: ignore[no-untyped-def] +def test_user_email_domain_property() -> None: assert FFAdminUser(email="test@example.com").email_domain == "example.com"