diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47556be4b252c6150988d3b7bacc74edb0151514..a658e3ecbb4ffcc443863b93c6908a3dca305394 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,6 +13,7 @@ Test: - python3 manage.py collectstatic --no-input script: - python3 manage.py test lists + - python3 manage.py test accounts tags: - test diff --git a/accounts/authentication.py b/accounts/authentication.py deleted file mode 100644 index fa4af7bf582cb3c73eef1f50de4cb8b37b1233e8..0000000000000000000000000000000000000000 --- a/accounts/authentication.py +++ /dev/null @@ -1,23 +0,0 @@ -import sys -from accounts.models import ListUser, Token - -class PasswordlessAuthenticationBackend(object): - - def authenticate(self, request, uid): - print('uid', uid, file=sys.stderr) - if not Token.objects.filter(uid=uid).exists(): - print('no token found', file=sys.stderr) - return None - token = Token.objects.get(uid=uid) - print('got token', file=sys.stderr) - try: - user = ListUser.objects.get(email=token.email) - print('got user', file=sys.stderr) - return user - except ListUser.DoesNotExist: - print('new user', file=sys.stderr) - return ListUser.objects.create(email=token.email) - - - def get_user(self, email): - return ListUser.objects.get(email=email) \ No newline at end of file diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py index 1aeb64daf4a75d25ed3251f694d6ec1c52b7e88f..68db95b8703067a722bca0828ca0a27abc9a7d02 100644 --- a/accounts/migrations/0001_initial.py +++ b/accounts/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-11-09 11:00 +# Generated by Django 2.2.4 on 2019-11-13 14:02 from django.db import migrations, models @@ -8,30 +8,13 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0011_update_proxy_permissions'), ] operations = [ migrations.CreateModel( - name='Token', + name='User', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('email', models.EmailField(max_length=254)), - ('uid', models.CharField(max_length=255)), - ], - ), - migrations.CreateModel( - name='ListUser', - fields=[ - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('email', models.EmailField(max_length=254, primary_key=True, serialize=False)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], - options={ - 'abstract': False, - }, ), ] diff --git a/accounts/migrations/0002_token.py b/accounts/migrations/0002_token.py new file mode 100644 index 0000000000000000000000000000000000000000..0c80f1a591bd987ddfcd144d7166ccd9fab4f8d8 --- /dev/null +++ b/accounts/migrations/0002_token.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.4 on 2019-11-13 14:15 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Token', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(max_length=254)), + ('uid', models.CharField(default=uuid.uuid4, max_length=40)), + ], + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 7e2df5ab6783a3c2814ce5fe97889f125d131a2c..a5bc6ddbb0f5d6a7703d0b60bcffe72f3814bd76 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,31 +1,14 @@ from django.db import models -from django.contrib.auth.models import ( - AbstractBaseUser, BaseUserManager, PermissionsMixin -) -class Token(models.Model): - email = models.EmailField() - uid = models.CharField(max_length=255) - -class ListUserManager(BaseUserManager): - - def create_user(self, email): - ListUser.objects.create(email=email) +import uuid - def create_superuser(self, email, password): - self.create_user(email) - -class ListUser(AbstractBaseUser, PermissionsMixin): +class User(models.Model): email = models.EmailField(primary_key=True) + REQUIRED_FIELDS = [] USERNAME_FIELD = 'email' - #REQUIRED_FIELDS = ['email', 'height'] + is_anonymous = False + is_authenticated = True - objects = ListUserManager() - - @property - def is_staff(self): - return self.email == 'harry.percival@example.com' - - @property - def is_active(self): - return True \ No newline at end of file +class Token(models.Model): + email = models.EmailField() + uid = models.CharField(default=uuid.uuid4, max_length=40) \ No newline at end of file diff --git a/accounts/templates/login_email_sent.html b/accounts/templates/login_email_sent.html deleted file mode 100644 index 2aa6e4ffd35016363271165018e8bbc5dfa387f9..0000000000000000000000000000000000000000 --- a/accounts/templates/login_email_sent.html +++ /dev/null @@ -1,7 +0,0 @@ -<html> -<h1>Email sent</h1> - -<p>Check your email, you'll find a message with a link that will log you into -the site.</p> - -</html> \ No newline at end of file diff --git a/accounts/tests.py b/accounts/tests.py deleted file mode 100644 index 7ce503c2dd97ba78597f6ff6e4393132753573f6..0000000000000000000000000000000000000000 --- a/accounts/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/accounts/tests/__init__.py b/accounts/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/accounts/tests/test_models.py b/accounts/tests/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..8bbc9d584628f2de1639d4e972e843ac545edbb4 --- /dev/null +++ b/accounts/tests/test_models.py @@ -0,0 +1,23 @@ +from django.test import TestCase +from django.contrib.auth import get_user_model +from accounts.models import Token + +User = get_user_model() + + +class UserModelTest(TestCase): + + def test_user_is_valid_with_email_only(self): + user = User(email='a@b.com') + user.full_clean() # should not raise + + def test_email_is_primary_key(self): + user = User(email='a@b.com') + self.assertEqual(user.pk, 'a@b.com') + +class TokenModelTest(TestCase): + + def test_links_user_with_auto_generated_uid(self): + token1 = Token.objects.create(email='a@b.com') + token2 = Token.objects.create(email='a@b.com') + self.assertNotEqual(token1.uid, token2.uid) \ No newline at end of file diff --git a/accounts/urls.py b/accounts/urls.py deleted file mode 100644 index 10f0e241ced16c3e23ac731cc40a705c2d33ec0d..0000000000000000000000000000000000000000 --- a/accounts/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.conf.urls import url -from accounts import views - -urlpatterns = [ - url(r'^send_email$', views.send_login_email, name='send_login_email'), - url(r'^login$', views.login, name='login'), - url(r'^logout$', views.logout, name='logout'), -] \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index 5e4d4a251d26a82c8efcba3b7bca56c82939c39d..91ea44a218fbd2f408430959283f0419c921093e 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,35 +1,3 @@ -import uuid -import sys -from django.contrib.auth import authenticate -from django.contrib.auth import login as auth_login, logout as auth_logout -from django.core.mail import send_mail -from django.shortcuts import redirect, render -from django.core.mail import send_mail +from django.shortcuts import render -from accounts.models import Token - -def send_login_email(request): - email = request.POST['email'] - uid = str(uuid.uuid4()) - Token.objects.create(email=email, uid=uid) - print('saving uid', uid, 'for email', email, file=sys.stderr) - url = request.build_absolute_uri(f'/accounts/login?uid={uid}') - send_mail( - 'Your login link for Superlists', - f'Use this link to log in:\n\n{url}', - 'noreply@superlists', - [email], - ) - return render(request, 'login_email_sent.html') - -def login(request): - print('login view', file=sys.stderr) - uid = request.GET.get('uid') - user = authenticate(uid=uid) - if user is not None: - auth_login(request, user) - return redirect('/') - -def logout(request): - auth_logout(request) - return redirect('/') \ No newline at end of file +# Create your views here. diff --git a/functional_tests/test_login.py b/functional_tests/test_login.py index 1aa1676435b857148de6f82aff389566d214097b..0b03176ab6f85f6ebc0c9565b4704bd22fe3ff96 100644 --- a/functional_tests/test_login.py +++ b/functional_tests/test_login.py @@ -1,48 +1,48 @@ -from django.core import mail -from selenium.webdriver.common.keys import Keys -import re - -from .base import FunctionalTest - -TEST_EMAIL = 'edith@example.com' -SUBJECT = 'Your login link for Superlists' - - -class LoginTest(FunctionalTest): - - def test_can_get_email_link_to_log_in(self): - # Edith goes to the awesome superlists site - # and notices a "Log in" section in the navbar for the first time - # It's telling her to enter her email address, so she does - self.browser.get(self.live_server_url) - self.browser.find_element_by_name('email').send_keys(TEST_EMAIL) - self.browser.find_element_by_name('email').send_keys(Keys.ENTER) - - # A message appears telling her an email has been sent - self.wait_for(lambda: self.assertIn( - 'Check your email', - self.browser.find_element_by_tag_name('body').text - )) - - # She checks her email and finds a message - email = mail.outbox[0] - self.assertIn(TEST_EMAIL, email.to) - self.assertEqual(email.subject, SUBJECT) - - # It has a url link in it - self.assertIn('Use this link to log in', email.body) - url_search = re.search(r'http://.+/.+$', email.body) - if not url_search: - self.fail(f'Could not find url in email body:\n{email.body}') - url = url_search.group(0) - self.assertIn(self.live_server_url, url) - - # she clicks it - self.browser.get(url) - - # she is logged in! - self.wait_for( - lambda: self.browser.find_element_by_link_text('Log out') - ) - navbar = self.browser.find_element_by_css_selector('.navbar') - self.assertIn(TEST_EMAIL, navbar.text) \ No newline at end of file +# from django.core import mail +# from selenium.webdriver.common.keys import Keys +# import re + +# from .base import FunctionalTest + +# TEST_EMAIL = 'edith@example.com' +# SUBJECT = 'Your login link for Superlists' + + +# class LoginTest(FunctionalTest): + +# def test_can_get_email_link_to_log_in(self): +# # Edith goes to the awesome superlists site +# # and notices a "Log in" section in the navbar for the first time +# # It's telling her to enter her email address, so she does +# self.browser.get(self.live_server_url) +# self.browser.find_element_by_name('email').send_keys(TEST_EMAIL) +# self.browser.find_element_by_name('email').send_keys(Keys.ENTER) + +# # A message appears telling her an email has been sent +# self.wait_for(lambda: self.assertIn( +# 'Check your email', +# self.browser.find_element_by_tag_name('body').text +# )) + +# # She checks her email and finds a message +# email = mail.outbox[0] +# self.assertIn(TEST_EMAIL, email.to) +# self.assertEqual(email.subject, SUBJECT) + +# # It has a url link in it +# self.assertIn('Use this link to log in', email.body) +# url_search = re.search(r'http://.+/.+$', email.body) +# if not url_search: +# self.fail(f'Could not find url in email body:\n{email.body}') +# url = url_search.group(0) +# self.assertIn(self.live_server_url, url) + +# # she clicks it +# self.browser.get(url) + +# # she is logged in! +# self.wait_for( +# lambda: self.browser.find_element_by_link_text('Log out') +# ) +# navbar = self.browser.find_element_by_css_selector('.navbar') +# self.assertIn(TEST_EMAIL, navbar.text) \ No newline at end of file diff --git a/lists/templates/base.html b/lists/templates/base.html index ce64fa9a75017e302ee409b1d3b9da69a9ab6084..b8b48f2e8a7b86369bb295d491c348bcf063b5b4 100644 --- a/lists/templates/base.html +++ b/lists/templates/base.html @@ -16,17 +16,16 @@ <body> <div class="container"> - <div class="navbar"> - {% if user.is_authenticated %} - <p>Logged in as {{ user.email }}</p> - <p><a id="id_logout" href="{% url 'logout' %}">Log out</a></p> - {% else %} - <form method="POST" action ="{% url 'send_login_email' %}"> - Enter email to log in: <input name="email" type="text" /> + <nav class="navbar navbar-default" role="navigation"> + <div class="container-fluid"> + <a class="navbar-brand" href="/">Superlists</a> + <form class="navbar-form navbar-right" method="POST" action="#"> + <span>Enter email to log in:</span> + <input class="form-control" name="email" type="text" /> {% csrf_token %} </form> - {% endif %} - </div> + </div> + </nav> <div class="row"> <div class="col-md-6 col-md-offset-3 jumbotron"> diff --git a/superlists/settings.py b/superlists/settings.py index bd3b2e398bb85c92c4f6897c496d36d4a6b7db42..e15bca59fa238852d9cf708a69fa35acb90b205e 100644 --- a/superlists/settings.py +++ b/superlists/settings.py @@ -45,10 +45,7 @@ INSTALLED_APPS = [ 'accounts', ] -AUTH_USER_MODEL = 'accounts.ListUser' -AUTHENTICATION_BACKENDS = [ - 'accounts.authentication.PasswordlessAuthenticationBackend', -] +AUTH_USER_MODEL = 'accounts.User' MIDDLEWARE = [ 'whitenoise.middleware.WhiteNoiseMiddleware', diff --git a/superlists/urls.py b/superlists/urls.py index 372a0c7bb20650bfe9f885c5a491a3f055fca92e..581fb60ba524f0c42d13070f069c0c27fa757e39 100644 --- a/superlists/urls.py +++ b/superlists/urls.py @@ -16,10 +16,10 @@ Including another URLconf from django.conf.urls import include, url from lists import views as list_views from lists import urls as list_urls -from accounts import urls as accounts_urls +# from accounts import urls as accounts_urls urlpatterns = [ url(r'^$', list_views.home_page, name='home'), url(r'^lists/', include(list_urls)), - url(r'^accounts/', include(accounts_urls)), + # url(r'^accounts/', include(accounts_urls)), ]