diff --git a/src/apps/lyric/authentication.py b/src/apps/lyric/authentication.py new file mode 100644 index 0000000..2fffd80 --- /dev/null +++ b/src/apps/lyric/authentication.py @@ -0,0 +1,16 @@ +from django.core.exceptions import ValidationError +from .models import Token, User + + +class PasswordlessAuthenticationBackend: + def authenticate(self, request, uid=None): + if uid is None: + return None + try: + token = Token.objects.get(uid=uid) + except (Token.DoesNotExist, ValidationError): + return None + try: + return User.objects.get(email=token.email) + except User.DoesNotExist: + return User.objects.create(email=token.email) diff --git a/src/apps/lyric/models.py b/src/apps/lyric/models.py index a937f30..49cecb1 100644 --- a/src/apps/lyric/models.py +++ b/src/apps/lyric/models.py @@ -3,7 +3,7 @@ from django.db import models class Token(models.Model): email = models.EmailField() - uid = models.UUIDField(default=uuid.uuid4) + uid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) class User(models.Model): id = models.BigAutoField(primary_key=True) diff --git a/src/apps/lyric/tests/test_authentication.py b/src/apps/lyric/tests/test_authentication.py new file mode 100644 index 0000000..baae82e --- /dev/null +++ b/src/apps/lyric/tests/test_authentication.py @@ -0,0 +1,44 @@ +import uuid +from django.http import HttpRequest +from django.test import TestCase + +from ..authentication import PasswordlessAuthenticationBackend +from ..models import Token, User + + +class AuthenticateTest(TestCase): + def test_returns_None_if_token_is_invalid_uuid(self): + result = PasswordlessAuthenticationBackend().authenticate( + HttpRequest(), "no-such-token" + ) + self.assertIsNone(result) + + def test_returns_None_if_token_uuid_not_found(self): + uid = uuid.uuid4() + result = PasswordlessAuthenticationBackend().authenticate( + HttpRequest(), uid + ) + self.assertIsNone(result) + + def test_returns_new_user_with_correct_email_if_token_exists(self): + email = "discoman@example.com" + token = Token.objects.create(email=email) + user = PasswordlessAuthenticationBackend().authenticate( + HttpRequest(), token.uid + ) + new_user = User.objects.get(email=email) + self.assertEqual(user, new_user) + + def test_returns_existing_user_with_correct_email_if_token_exists(self): + email = "discoman@example.com" + existing_user = User.objects.create(email=email) + token = Token.objects.create(email=email) + user = PasswordlessAuthenticationBackend().authenticate( + HttpRequest(), token.uid + ) + self.assertEqual(user, existing_user) + + def test_can_retrieve_token_by_uuid(self): + token = Token.objects.create(email="a@b.cde") + fetched = Token.objects.get(pk=token.uid) + self.assertEqual(fetched, token) diff --git a/src/apps/lyric/tests/test_models.py b/src/apps/lyric/tests/test_models.py index 1e96f71..46e65c5 100644 --- a/src/apps/lyric/tests/test_models.py +++ b/src/apps/lyric/tests/test_models.py @@ -1,3 +1,4 @@ +import uuid from django.contrib import auth from django.test import TestCase from ..models import Token, User @@ -16,6 +17,7 @@ class UserModelTest(TestCase): class TokenModelTest(TestCase): def test_links_user_with_autogen_uid(self): - token1 = Token.objects.create(id="123") - token2 = Token.objects.create(id="124") - self.assertNotEqual(token1.uid, token2.uid) + token1 = Token.objects.create(email="a@b.cde") + token2 = Token.objects.create(email="v@w.xyz") + self.assertNotEqual(token1.pk, token2.pk) + self.assertIsInstance(token1.pk, uuid.UUID)