Skip to content

Commit aaa9b27

Browse files
committed
🚸(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).
1 parent a29ef05 commit aaa9b27

File tree

5 files changed

+53
-2
lines changed

5 files changed

+53
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to
2222

2323
### Changed
2424

25+
- 🚸(email) we should ignore case when looking for existing emails #1056
2526
- 🏗️(core) migrate from pip to uv
2627
- ✨(front) add show invitations mails domains access #1040
2728

src/backend/core/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ def clean(self):
998998
super().clean()
999999

10001000
# Check if a user already exists for the provided email
1001-
if User.objects.filter(email=self.email).exists():
1001+
if User.objects.filter(email__iexact=self.email).exists():
10021002
raise EmailAlreadyKnownException
10031003

10041004
@property

src/backend/core/tests/test_models_invitations.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
from core import factories, models
1919

20+
from mailbox_manager.exceptions import EmailAlreadyKnownException
21+
2022
pytestmark = pytest.mark.django_db
2123

2224

@@ -48,6 +50,17 @@ def test_models_invitations_team_required():
4850
factories.InvitationFactory(team=None)
4951

5052

53+
def test_models_invitations_email_case_insensitive_duplicate_check():
54+
"""The email validation should be case-insensitive when checking for existing users."""
55+
# Create a user with a lowercase email
56+
factories.UserFactory(email="john.doe@example.com")
57+
58+
# Try to create an invitation with different case
59+
# This should raise the same exception as if the email was exactly the same
60+
with pytest.raises(EmailAlreadyKnownException):
61+
factories.InvitationFactory(email="John.Doe@Example.COM")
62+
63+
5164
def test_models_invitations_team_should_be_team_instance():
5265
"""The "team" field should be a team instance."""
5366
with pytest.raises(ValueError, match='Invitation.team" must be a "Team" instance'):

src/backend/mailbox_manager/api/client/viewsets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ def create(self, request, *args, **kwargs):
419419
try:
420420
return super().create(request, *args, **kwargs)
421421
except EmailAlreadyKnownException as exc:
422-
user = models.User.objects.get(email=email)
422+
user = models.User.objects.get(email__iexact=email)
423423

424424
models.MailDomainAccess.objects.create(
425425
user=user,

src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_create.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,40 @@ def test_api_domain_invitations__inviting_known_email_should_create_access():
158158

159159
assert not models.MailDomainInvitation.objects.exists()
160160
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()
161+
162+
163+
def test_api_domain_invitations__inviting_known_email_case_insensitive():
164+
"""Email matching should be case-insensitive when creating access for existing users."""
165+
# Create a user with lowercase email
166+
existing_user = core_factories.UserFactory(email="john.doe@example.com")
167+
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
168+
169+
client = APIClient()
170+
client.force_login(access.user)
171+
172+
# Try to invite with same email different case - should create access for existing user
173+
response = client.post(
174+
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
175+
{
176+
"email": "John.Doe@Example.COM",
177+
"role": "owner",
178+
},
179+
format="json",
180+
)
181+
assert response.status_code == status.HTTP_201_CREATED
182+
assert (
183+
response.json()["detail"]
184+
== "Email already known. Invitation not sent but access created instead."
185+
)
186+
187+
# No invitation should be created
188+
assert not models.MailDomainInvitation.objects.exists()
189+
190+
# Access should be created for the existing user (not a new user)
191+
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()
192+
assert models.MailDomainAccess.objects.filter(
193+
user=existing_user, domain=access.domain
194+
).exists()
195+
196+
# Ensure only one user exists (no duplicate user created)
197+
assert models.User.objects.filter(email__iexact="john.doe@example.com").count() == 1

0 commit comments

Comments
 (0)