From bab82bf808fac46ecf2d3f17b4c2ec76bb71ed62 Mon Sep 17 00:00:00 2001 From: Oren Date: Sun, 25 Oct 2026 20:28:40 -0400 Subject: [PATCH] fix(backend): reject duplicate email when creating a user via the API (#13100) UserCreateSerializer did not check whether the email address was already in use (signup does, via allauth). Email based flows such as the login link look users up by email and silently send nothing when more than one user matches, while the API still reports success. Two existing tests reused one email for several users; give them distinct addresses. --- src/backend/InvenTree/users/serializers.py | 13 ++++++++++ src/backend/InvenTree/users/test_api.py | 29 +++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/backend/InvenTree/users/serializers.py b/src/backend/InvenTree/users/serializers.py index 528b1beeee..393b858cf2 100644 --- a/src/backend/InvenTree/users/serializers.py +++ b/src/backend/InvenTree/users/serializers.py @@ -544,6 +544,19 @@ class UserCreateSerializer(ExtendedUserSerializer): _('You do not have permission to create users') ) + # Email-based flows (password reset, login link) look users up by email, + # and fail silently if more than one user has the same address + if email := attrs.get('email'): + from allauth.account.models import EmailAddress + + if ( + User.objects.filter(email__iexact=email).exists() + or EmailAddress.objects.filter(email__iexact=email).exists() + ): + raise serializers.ValidationError({ + 'email': _('A user with this email address already exists') + }) + # Generate a random password password = make_random_password(length=14) attrs.update({'password': password}) diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index 4644ef23f8..c332e2d81d 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -131,11 +131,24 @@ class UserAPITests(InvenTreeAPITestCase): ).exists() ) + # A second user with the same email address (any case) is rejected + response = self.post( + url, + data={**data, 'username': 'test2', 'email': 'AA@example.org'}, + expected_code=400, + ) + self.assertIn('email', response.data) + # Try to adjust the 'is_superuser' field # Only a "superuser" can set this field response = self.post( url, - data={**data, 'username': 'Superuser', 'is_superuser': True}, + data={ + **data, + 'username': 'Superuser', + 'email': 'su@example.org', + 'is_superuser': True, + }, expected_code=403, ) @@ -147,7 +160,12 @@ class UserAPITests(InvenTreeAPITestCase): response = self.post( url, - data={**data, 'username': 'Superuser', 'is_superuser': True}, + data={ + **data, + 'username': 'Superuser', + 'email': 'su@example.org', + 'is_superuser': True, + }, expected_code=201, ) @@ -186,7 +204,12 @@ class UserAPITests(InvenTreeAPITestCase): # Creating a user without specifying groups must still work response = self.post( url, - data={**data, 'username': 'ungrouped', 'group_ids': []}, + data={ + **data, + 'username': 'ungrouped', + 'email': 'ungrouped@example.org', + 'group_ids': [], + }, expected_code=201, ) self.assertEqual(response.data['groups'], [])