From 99a18b6e903e68d6a1c7300f16379fe7e64d6917 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Tue, 20 Jan 2026 20:13:57 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9(backend)=20use=20case-insensitive?= =?UTF-8?q?=20email=20matching=20in=20the=20external=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a minor issue in the external API where users were matched using case-sensitive email comparison, while authentication treats emails as case-insensitive. This caused inconsistencies that are now resolved. Spotted by T. Lemeur from Centrale. --- CHANGELOG.md | 1 + src/backend/core/external_api/viewsets.py | 8 ++++++-- src/backend/core/tests/test_external_api_token.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8470f92c..88232c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to - ♿(frontend) improve contrast for selected options #863 - ♿️(frontend) announce copy state in invite dialog #877 - 📝(frontend) align close dialog label in rooms locale #878 +- 🩹(backend) use case-insensitive email matching in the external api #887 ## [1.3.0] - 2026-01-13 diff --git a/src/backend/core/external_api/viewsets.py b/src/backend/core/external_api/viewsets.py index a4ab2110..11b6fc6b 100644 --- a/src/backend/core/external_api/viewsets.py +++ b/src/backend/core/external_api/viewsets.py @@ -5,7 +5,7 @@ from logging import getLogger from django.conf import settings from django.contrib.auth.hashers import check_password -from django.core.exceptions import ValidationError +from django.core.exceptions import SuspiciousOperation, ValidationError from django.core.validators import validate_email import jwt @@ -93,7 +93,7 @@ class ApplicationViewSet(viewsets.GenericViewSet): ) try: - user = models.User.objects.get(email=email) + user = models.User.objects.get(email__iexact=email) except models.User.DoesNotExist as e: if ( settings.APPLICATION_ALLOW_USER_CREATION @@ -123,6 +123,10 @@ class ApplicationViewSet(viewsets.GenericViewSet): ) else: raise drf_exceptions.NotFound("User not found.") from e + except models.User.MultipleObjectsReturned as e: + raise SuspiciousOperation( + "Multiple user accounts share a common email." + ) from e now = datetime.now(timezone.utc) scope = " ".join(application.scopes or []) diff --git a/src/backend/core/tests/test_external_api_token.py b/src/backend/core/tests/test_external_api_token.py index 450dec9d..0701f384 100644 --- a/src/backend/core/tests/test_external_api_token.py +++ b/src/backend/core/tests/test_external_api_token.py @@ -22,7 +22,7 @@ pytestmark = pytest.mark.django_db def test_api_applications_generate_token_success(settings): """Valid credentials should return a JWT token.""" settings.APPLICATION_JWT_SECRET_KEY = "devKey" - user = UserFactory(email="user@example.com") + UserFactory(email="User.Family@example.com") application = ApplicationFactory( active=True, scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE], @@ -40,7 +40,7 @@ def test_api_applications_generate_token_success(settings): "client_id": application.client_id, "client_secret": plain_secret, "grant_type": "client_credentials", - "scope": user.email, + "scope": "user.family@example.com", }, format="json", )