🚸(email) we should ignore case when looking for existing emails

Our products mostly rely on email regardless of their case.

Next step would be to normalize email to lower case when storing
them in database (or sending them to dimail if not done yet).
This commit is contained in:
Quentin BEY
2026-02-10 22:45:15 +01:00
parent a29ef05d8b
commit aaa9b27c61
5 changed files with 53 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ and this project adheres to
### Changed
- 🚸(email) we should ignore case when looking for existing emails #1056
- 🏗️(core) migrate from pip to uv
- ✨(front) add show invitations mails domains access #1040

View File

@@ -998,7 +998,7 @@ class BaseInvitation(BaseModel):
super().clean()
# Check if a user already exists for the provided email
if User.objects.filter(email=self.email).exists():
if User.objects.filter(email__iexact=self.email).exists():
raise EmailAlreadyKnownException
@property

View File

@@ -17,6 +17,8 @@ from freezegun import freeze_time
from core import factories, models
from mailbox_manager.exceptions import EmailAlreadyKnownException
pytestmark = pytest.mark.django_db
@@ -48,6 +50,17 @@ def test_models_invitations_team_required():
factories.InvitationFactory(team=None)
def test_models_invitations_email_case_insensitive_duplicate_check():
"""The email validation should be case-insensitive when checking for existing users."""
# Create a user with a lowercase email
factories.UserFactory(email="john.doe@example.com")
# Try to create an invitation with different case
# This should raise the same exception as if the email was exactly the same
with pytest.raises(EmailAlreadyKnownException):
factories.InvitationFactory(email="John.Doe@Example.COM")
def test_models_invitations_team_should_be_team_instance():
"""The "team" field should be a team instance."""
with pytest.raises(ValueError, match='Invitation.team" must be a "Team" instance'):

View File

@@ -419,7 +419,7 @@ class MailDomainInvitationViewset(
try:
return super().create(request, *args, **kwargs)
except EmailAlreadyKnownException as exc:
user = models.User.objects.get(email=email)
user = models.User.objects.get(email__iexact=email)
models.MailDomainAccess.objects.create(
user=user,

View File

@@ -158,3 +158,40 @@ def test_api_domain_invitations__inviting_known_email_should_create_access():
assert not models.MailDomainInvitation.objects.exists()
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()
def test_api_domain_invitations__inviting_known_email_case_insensitive():
"""Email matching should be case-insensitive when creating access for existing users."""
# Create a user with lowercase email
existing_user = core_factories.UserFactory(email="john.doe@example.com")
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
client = APIClient()
client.force_login(access.user)
# Try to invite with same email different case - should create access for existing user
response = client.post(
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
{
"email": "John.Doe@Example.COM",
"role": "owner",
},
format="json",
)
assert response.status_code == status.HTTP_201_CREATED
assert (
response.json()["detail"]
== "Email already known. Invitation not sent but access created instead."
)
# No invitation should be created
assert not models.MailDomainInvitation.objects.exists()
# Access should be created for the existing user (not a new user)
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()
assert models.MailDomainAccess.objects.filter(
user=existing_user, domain=access.domain
).exists()
# Ensure only one user exists (no duplicate user created)
assert models.User.objects.filter(email__iexact="john.doe@example.com").count() == 1