Merge pull request #1374 from bookwyrm-social/login-views

Login views
This commit is contained in:
Mouse Reeve 2021-09-07 15:22:54 -07:00 committed by GitHub
commit e3b1d83fef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 207 additions and 157 deletions

View file

@ -0,0 +1,110 @@
""" test for app action functionality """
from unittest.mock import patch
from django.contrib.auth.models import AnonymousUser
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import forms, models, views
# pylint: disable=too-many-public-methods
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
class LoginViews(TestCase):
"""login and password management"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
"bookwyrm.activitystreams.populate_stream_task.delay"
):
self.local_user = models.User.objects.create_user(
"mouse@your.domain.here",
"mouse@mouse.com",
"password",
local=True,
localname="mouse",
)
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
models.SiteSettings.objects.create(id=1, require_confirm_email=False)
def test_login_get(self, *_):
"""there are so many views, this just makes sure it LOADS"""
login = views.Login.as_view()
request = self.factory.get("")
request.user = self.anonymous_user
result = login(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
request.user = self.local_user
result = login(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_localname(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse@mouse.com"
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_username(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse@your.domain.here"
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_email(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse"
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_invalid_credentials(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse"
form.data["password"] = "passsword1"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
result = view(request)
result.render()
self.assertEqual(result.status_code, 200)
self.assertEqual(
result.context_data["login_form"].non_field_errors,
"Username or password are incorrect",
)

View file

@ -8,14 +8,14 @@ from django.template.response import TemplateResponse
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from bookwyrm import forms, models, views from bookwyrm import models, views
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay") @patch("bookwyrm.activitystreams.populate_stream_task.delay")
class AuthenticationViews(TestCase): class RegisterViews(TestCase):
"""login and password management""" """login and password management"""
def setUp(self): def setUp(self):
@ -38,82 +38,6 @@ class AuthenticationViews(TestCase):
id=1, require_confirm_email=False id=1, require_confirm_email=False
) )
def test_login_get(self, *_):
"""there are so many views, this just makes sure it LOADS"""
login = views.Login.as_view()
request = self.factory.get("")
request.user = self.anonymous_user
result = login(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
request.user = self.local_user
result = login(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_localname(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse@mouse.com"
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.authentication.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_username(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse@your.domain.here"
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.authentication.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_email(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse"
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.authentication.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_login_post_invalid_credentials(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Login.as_view()
form = forms.LoginForm()
form.data["localname"] = "mouse"
form.data["password"] = "passsword1"
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.authentication.login"):
result = view(request)
result.render()
self.assertEqual(result.status_code, 200)
self.assertEqual(
result.context_data["login_form"].non_field_errors,
"Username or password are incorrect",
)
def test_register(self, *_): def test_register(self, *_):
"""create a user""" """create a user"""
view = views.Register.as_view() view = views.Register.as_view()
@ -126,7 +50,7 @@ class AuthenticationViews(TestCase):
"email": "aa@bb.cccc", "email": "aa@bb.cccc",
}, },
) )
with patch("bookwyrm.views.authentication.login"): with patch("bookwyrm.views.register.login"):
response = view(request) response = view(request)
self.assertEqual(models.User.objects.count(), 2) self.assertEqual(models.User.objects.count(), 2)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -151,7 +75,7 @@ class AuthenticationViews(TestCase):
"email": "aa@bb.cccc", "email": "aa@bb.cccc",
}, },
) )
with patch("bookwyrm.views.authentication.login"): with patch("bookwyrm.views.register.login"):
response = view(request) response = view(request)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
nutria = models.User.objects.get(localname="nutria") nutria = models.User.objects.get(localname="nutria")
@ -169,7 +93,7 @@ class AuthenticationViews(TestCase):
"register/", "register/",
{"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"}, {"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"},
) )
with patch("bookwyrm.views.authentication.login"): with patch("bookwyrm.views.register.login"):
response = view(request) response = view(request)
self.assertEqual(models.User.objects.count(), 2) self.assertEqual(models.User.objects.count(), 2)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -248,7 +172,7 @@ class AuthenticationViews(TestCase):
"invite_code": "testcode", "invite_code": "testcode",
}, },
) )
with patch("bookwyrm.views.authentication.login"): with patch("bookwyrm.views.register.login"):
response = view(request) response = view(request)
self.assertEqual(models.User.objects.count(), 2) self.assertEqual(models.User.objects.count(), 2)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)

View file

@ -1,7 +1,5 @@
""" make sure all our nice views are available """ """ make sure all our nice views are available """
from .announcements import Announcements, Announcement, delete_announcement from .announcements import Announcements, Announcement, delete_announcement
from .authentication import Login, Register, Logout
from .authentication import ConfirmEmail, ConfirmEmailCode, resend_link
from .author import Author, EditAuthor from .author import Author, EditAuthor
from .block import Block, unblock from .block import Block, unblock
from .books import Book, EditBook, ConfirmEditBook from .books import Book, EditBook, ConfirmEditBook
@ -28,11 +26,13 @@ from .landing import About, Home, Landing
from .list import Lists, SavedLists, List, Curate, UserLists from .list import Lists, SavedLists, List, Curate, UserLists
from .list import save_list, unsave_list from .list import save_list, unsave_list
from .list import delete_list from .list import delete_list
from .login import Login, Logout
from .notifications import Notifications from .notifications import Notifications
from .outbox import Outbox from .outbox import Outbox
from .reading import edit_readthrough, create_readthrough from .reading import edit_readthrough, create_readthrough
from .reading import delete_readthrough, delete_progressupdate from .reading import delete_readthrough, delete_progressupdate
from .reading import ReadingStatus from .reading import ReadingStatus
from .register import Register, ConfirmEmail, ConfirmEmailCode, resend_link
from .reports import Report, Reports, make_report, resolve_report, suspend_user from .reports import Report, Reports, make_report, resolve_report, suspend_user
from .rss_feed import RssFeed from .rss_feed import RssFeed
from .password import PasswordResetRequest, PasswordReset, ChangePassword from .password import PasswordResetRequest, PasswordReset, ChangePassword

View file

@ -83,7 +83,7 @@ class Invite(View):
} }
return TemplateResponse(request, "invite.html", data) return TemplateResponse(request, "invite.html", data)
# post handling is in views.authentication.Register # post handling is in views.register.Register
class ManageInviteRequests(View): class ManageInviteRequests(View):

83
bookwyrm/views/login.py Normal file
View file

@ -0,0 +1,83 @@
""" class views for login/register views """
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters
from bookwyrm import forms, models
from bookwyrm.settings import DOMAIN
# pylint: disable=no-self-use
@method_decorator(csrf_exempt, name="dispatch")
class Login(View):
"""authenticate an existing user"""
def get(self, request, confirmed=None):
"""login page"""
if request.user.is_authenticated:
return redirect("/")
# send user to the login page
data = {
"show_confirmed_email": confirmed,
"login_form": forms.LoginForm(),
"register_form": forms.RegisterForm(),
}
return TemplateResponse(request, "login.html", data)
@sensitive_variables("password")
@method_decorator(sensitive_post_parameters("password"))
def post(self, request):
"""authentication action"""
if request.user.is_authenticated:
return redirect("/")
login_form = forms.LoginForm(request.POST)
localname = login_form.data["localname"]
if "@" in localname: # looks like an email address to me
try:
username = models.User.objects.get(email=localname).username
except models.User.DoesNotExist: # maybe it's a full username?
username = localname
else:
username = "%s@%s" % (localname, DOMAIN)
password = login_form.data["password"]
# perform authentication
user = authenticate(request, username=username, password=password)
if user is not None:
# successful login
login(request, user)
user.last_active_date = timezone.now()
user.save(broadcast=False, update_fields=["last_active_date"])
if request.POST.get("first_login"):
return redirect("get-started-profile")
return redirect(request.GET.get("next", "/"))
# maybe the user is pending email confirmation
if models.User.objects.filter(
username=username, is_active=False, deactivation_reason="pending"
).exists():
return redirect("confirm-email")
# login errors
login_form.non_field_errors = _("Username or password are incorrect")
register_form = forms.RegisterForm()
data = {"login_form": login_form, "register_form": register_form}
return TemplateResponse(request, "login.html", data)
@method_decorator(login_required, name="dispatch")
class Logout(View):
"""log out"""
def get(self, request):
"""done with this place! outa here!"""
logout(request)
return redirect("/")

View file

@ -1,90 +1,23 @@
""" class views for login/register views """ """ class views for login/register views """
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.views import View from django.views import View
from django.views.decorators.http import require_POST
from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters
from bookwyrm import emailing, forms, models from bookwyrm import emailing, forms, models
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
# pylint: disable=no-self-use # pylint: disable=no-self-use
@method_decorator(csrf_exempt, name="dispatch")
class Login(View):
"""authenticate an existing user"""
def get(self, request, confirmed=None):
"""login page"""
if request.user.is_authenticated:
return redirect("/")
# send user to the login page
data = {
"show_confirmed_email": confirmed,
"login_form": forms.LoginForm(),
"register_form": forms.RegisterForm(),
}
return TemplateResponse(request, "login.html", data)
def post(self, request):
"""authentication action"""
if request.user.is_authenticated:
return redirect("/")
login_form = forms.LoginForm(request.POST)
localname = login_form.data["localname"]
if "@" in localname: # looks like an email address to me
try:
username = models.User.objects.get(email=localname).username
except models.User.DoesNotExist: # maybe it's a full username?
username = localname
else:
username = "%s@%s" % (localname, DOMAIN)
password = login_form.data["password"]
# perform authentication
user = authenticate(request, username=username, password=password)
if user is not None:
# successful login
login(request, user)
user.last_active_date = timezone.now()
user.save(broadcast=False, update_fields=["last_active_date"])
if request.POST.get("first_login"):
return redirect("get-started-profile")
return redirect(request.GET.get("next", "/"))
# maybe the user is pending email confirmation
if models.User.objects.filter(
username=username, is_active=False, deactivation_reason="pending"
).exists():
return redirect("confirm-email")
# login errors
login_form.non_field_errors = _("Username or password are incorrect")
register_form = forms.RegisterForm()
data = {"login_form": login_form, "register_form": register_form}
return TemplateResponse(request, "login.html", data)
@method_decorator(login_required, name="dispatch")
class Logout(View):
"""log out"""
def get(self, request):
"""done with this place! outa here!"""
logout(request)
return redirect("/")
class Register(View): class Register(View):
"""register a user""" """register a user"""
@sensitive_variables("password")
@method_decorator(sensitive_post_parameters("password"))
def post(self, request): def post(self, request):
"""join the server""" """join the server"""
settings = models.SiteSettings.get() settings = models.SiteSettings.get()