Browse Source

Implement create_email uniqueness checks

David Burke 9 months ago
parent
commit
d038528d26
4 changed files with 31 additions and 13 deletions
  1. 19 3
      apps/users/api.py
  2. 9 7
      apps/users/tests/test_api.py
  3. 2 3
      apps/users/views.py
  4. 1 0
      pyproject.toml

+ 19 - 3
apps/users/api.py

@@ -1,5 +1,6 @@
 from allauth.account.models import EmailAddress
 from asgiref.sync import sync_to_async
+from django.db.utils import IntegrityError
 from django.http import Http404, HttpResponse
 from django.shortcuts import aget_object_or_404
 from ninja import Router
@@ -114,12 +115,27 @@ async def list_emails(request: AuthHttpRequest, user_id: MeID):
 async def create_email(
     request: AuthHttpRequest, user_id: MeID, payload: EmailAddressIn
 ):
+    """
+    Create a new unverified email address. Will return 400 if the email already exists
+    and is verified.
+    """
     if user_id != request.auth.user_id and user_id != "me":
         raise Http404
     user_id = request.auth.user_id
-    email_address = await EmailAddress.objects.acreate(
-        email=payload.email, user_id=user_id
-    )
+    if await EmailAddress.objects.filter(email=payload.email, verified=True).aexists():
+        raise HttpError(
+            400,
+            "Email already exists",
+        )
+    try:
+        email_address = await EmailAddress.objects.acreate(
+            email=payload.email, user_id=user_id
+        )
+    except IntegrityError:
+        raise HttpError(
+            400,
+            "Email already exists",
+        )
     await sync_to_async(email_address.send_confirmation)(request, signup=False)
     return 201, email_address
 

+ 9 - 7
apps/users/tests/test_api.py

@@ -162,22 +162,24 @@ class UsersTestCase(GlitchTipTestCase):
         )
 
     def test_emails_create_dupe_email(self):
-        url = reverse("user-emails-list", args=["me"])
+        url = reverse("api:create_email", args=["me"])
         email_address = baker.make(
             "account.EmailAddress",
             user=self.user,
             email="something@example.com",
         )
         data = {"email": email_address.email}
-        res = self.client.post(url, data)
-        self.assertContains(res, "this account", status_code=400)
+        res = self.client.post(url, data, format="json")
+        self.assertContains(res, "already exists", status_code=400)
 
     def test_emails_create_dupe_email_other_user(self):
-        url = reverse("user-emails-list", args=["me"])
-        email_address = baker.make("account.EmailAddress", email="a@example.com")
+        url = reverse("api:create_email", args=["me"])
+        email_address = baker.make(
+            "account.EmailAddress", email="a@example.com", verified=True
+        )
         data = {"email": email_address.email}
-        res = self.client.post(url, data)
-        self.assertContains(res, "another account", status_code=400)
+        res = self.client.post(url, data, format="json")
+        self.assertContains(res, "already exists", status_code=400)
 
     def test_emails_set_primary(self):
         url = reverse("api:set_email_as_primary", args=["me"])

+ 2 - 3
apps/users/views.py

@@ -2,10 +2,9 @@ from allauth.account.models import EmailAddress
 from dj_rest_auth.registration.views import (
     SocialAccountDisconnectView as BaseSocialAccountDisconnectView,
 )
-from django.core.exceptions import ObjectDoesNotExist, ValidationError
-from django.http import Http404
+from django.core.exceptions import ValidationError
 from django.shortcuts import get_object_or_404
-from rest_framework import exceptions, mixins, status, viewsets
+from rest_framework import exceptions, status, viewsets
 from rest_framework.decorators import action
 from rest_framework.response import Response
 

+ 1 - 0
pyproject.toml

@@ -4,6 +4,7 @@ version = "0.1.0"
 description = "Django backend that powers GlitchTip, an open source reimplementation of Sentry"
 authors = ["David Burke"]
 license = "MIT"
+package-mode = false
 
 [tool.poetry.dependencies]
 python = "^3.10"