diff --git a/bookwyrm/templates/password_reset.html b/bookwyrm/templates/password_reset.html index 99d114e4..e8da6562 100644 --- a/bookwyrm/templates/password_reset.html +++ b/bookwyrm/templates/password_reset.html @@ -8,9 +8,8 @@ {% for error in errors %}

{{ error }}

{% endfor %} -
+ {% csrf_token %} -
diff --git a/bookwyrm/templates/password_reset_request.html b/bookwyrm/templates/password_reset_request.html index 2c15b189..b491a6ac 100644 --- a/bookwyrm/templates/password_reset_request.html +++ b/bookwyrm/templates/password_reset_request.html @@ -7,7 +7,7 @@

Reset Password

{% if message %}

{{ message }}

{% endif %}

A link to reset your password will be sent to your email address

- + {% csrf_token %}
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 209dac01..d93570f6 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -43,12 +43,16 @@ urlpatterns = [ # TODO: robots.txt # authentication - re_path(r'^login/?$', views.LoginView.as_view()), - re_path(r'^register/?$', views.RegisterView.as_view()), + re_path(r'^login/?$', views.Login.as_view()), + re_path(r'^register/?$', views.Register.as_view()), + re_path(r'^logout/?$', views.Logout.as_view()), + re_path(r'^password-reset/?$', views.PasswordResetRequest.as_view()), + re_path(r'^password-reset/(?P[A-Za-z0-9]+)/?$', + views.PasswordReset.as_view()), + re_path(r'^change-password/?$', views.ChangePassword), + re_path(r'^about/?$', vviews.about_page), - re_path(r'^password-reset/?$', vviews.password_reset_request), - re_path(r'^password-reset/(?P[A-Za-z0-9]+)/?$', vviews.password_reset), re_path(r'^invite/?$', vviews.manage_invites), re_path(r'^invite/(?P[A-Za-z0-9]+)/?$', vviews.invite_page), @@ -92,11 +96,6 @@ urlpatterns = [ re_path(r'^search/?$', vviews.search), # internal action endpoints - re_path(r'^logout/?$', actions.user_logout), - re_path(r'^reset-password-request/?$', actions.password_reset_request), - re_path(r'^reset-password/?$', actions.password_reset), - re_path(r'^change-password/?$', actions.password_change), - re_path(r'^edit-profile/?$', actions.edit_profile), re_path(r'^import-data/?$', actions.import_data), diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index d87798f0..7977498b 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -6,95 +6,21 @@ from PIL import Image import dateutil.parser from dateutil.parser import ParserError -from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required, permission_required -from django.core.exceptions import PermissionDenied from django.core.files.base import ContentFile from django.db import transaction from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils import timezone -from django.views.decorators.http import require_GET, require_POST +from django.views.decorators.http import require_POST from bookwyrm import forms, models, outgoing, goodreads_import from bookwyrm.connectors import connector_manager from bookwyrm.broadcast import broadcast -from bookwyrm.emailing import password_reset_email -from bookwyrm.settings import DOMAIN from bookwyrm.vviews import get_user_from_username, get_edition -@login_required -@require_GET -def user_logout(request): - ''' done with this place! outa here! ''' - logout(request) - return redirect('/') - - -@require_POST -def password_reset_request(request): - ''' create a password reset token ''' - email = request.POST.get('email') - try: - user = models.User.objects.get(email=email) - except models.User.DoesNotExist: - return redirect('/password-reset') - - # remove any existing password reset cods for this user - models.PasswordReset.objects.filter(user=user).all().delete() - - # create a new reset code - code = models.PasswordReset.objects.create(user=user) - password_reset_email(code) - data = {'message': 'Password reset link sent to %s' % email} - return TemplateResponse(request, 'password_reset_request.html', data) - - -@require_POST -def password_reset(request): - ''' allow a user to change their password through an emailed token ''' - try: - reset_code = models.PasswordReset.objects.get( - code=request.POST.get('reset-code') - ) - except models.PasswordReset.DoesNotExist: - data = {'errors': ['Invalid password reset link']} - return TemplateResponse(request, 'password_reset.html', data) - - user = reset_code.user - - new_password = request.POST.get('password') - confirm_password = request.POST.get('confirm-password') - - if new_password != confirm_password: - data = {'errors': ['Passwords do not match']} - return TemplateResponse(request, 'password_reset.html', data) - - user.set_password(new_password) - user.save() - login(request, user) - reset_code.delete() - return redirect('/') - - -@login_required -@require_POST -def password_change(request): - ''' allow a user to change their password ''' - new_password = request.POST.get('password') - confirm_password = request.POST.get('confirm-password') - - if new_password != confirm_password: - return redirect('/user-edit') - - request.user.set_password(new_password) - request.user.save() - login(request, request.user) - return redirect('/user/%s' % request.user.localname) - - @login_required @require_POST def edit_profile(request): diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 7a92fa4c..0adf771f 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -1,2 +1,3 @@ ''' make sure all our nice views are available ''' -from .authentication import LoginView, RegisterView +from .authentication import Login, Register, Logout +from .password import PasswordResetRequest, PasswordReset, ChangePassword diff --git a/bookwyrm/views/authentication.py b/bookwyrm/views/authentication.py index 8c7d21d1..cc5ce625 100644 --- a/bookwyrm/views/authentication.py +++ b/bookwyrm/views/authentication.py @@ -1,9 +1,11 @@ -''' class views for login/register/password management views ''' -from django.contrib.auth import authenticate, login +''' class views for login/register views ''' +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils import timezone +from django.utils.decorators import method_decorator from django.views import View from bookwyrm import forms, models @@ -11,7 +13,7 @@ from bookwyrm.settings import DOMAIN # pylint: disable= no-self-use -class LoginView(View): +class Login(View): ''' authenticate an existing user ''' def get(self, request): ''' login page ''' @@ -49,7 +51,7 @@ class LoginView(View): return TemplateResponse(request, 'login.html', data) -class RegisterView(View): +class Register(View): ''' register a user ''' def post(self, request): ''' join the server ''' @@ -100,3 +102,12 @@ class RegisterView(View): login(request, user) return redirect('/') + + +@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('/') diff --git a/bookwyrm/views/password.py b/bookwyrm/views/password.py new file mode 100644 index 00000000..85c44d11 --- /dev/null +++ b/bookwyrm/views/password.py @@ -0,0 +1,102 @@ +''' class views for password management ''' +from django.contrib.auth import login +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import models +from bookwyrm.emailing import password_reset_email + + +# pylint: disable= no-self-use +class PasswordResetRequest(View): + ''' forgot password flow ''' + def get(self, request): + ''' password reset page ''' + return TemplateResponse( + request, + 'password_reset_request.html', + {'title': 'Reset Password'} + ) + + def post(self, request): + ''' create a password reset token ''' + email = request.POST.get('email') + try: + user = models.User.objects.get(email=email) + except models.User.DoesNotExist: + return redirect('/password-reset') + + # remove any existing password reset cods for this user + models.PasswordReset.objects.filter(user=user).all().delete() + + # create a new reset code + code = models.PasswordReset.objects.create(user=user) + password_reset_email(code) + data = {'message': 'Password reset link sent to %s' % email} + return TemplateResponse(request, 'password_reset_request.html', data) + + +class PasswordReset(View): + ''' set new password ''' + def get(self, request, code): + ''' endpoint for sending invites ''' + if request.user.is_authenticated: + return redirect('/') + try: + reset_code = models.PasswordReset.objects.get(code=code) + if not reset_code.valid(): + raise PermissionDenied + except models.PasswordReset.DoesNotExist: + raise PermissionDenied + + return TemplateResponse( + request, + 'password_reset.html', + {'title': 'Reset Password', 'code': reset_code.code} + ) + + def post(self, request, code): + ''' allow a user to change their password through an emailed token ''' + try: + reset_code = models.PasswordReset.objects.get( + code=code + ) + except models.PasswordReset.DoesNotExist: + data = {'errors': ['Invalid password reset link']} + return TemplateResponse(request, 'password_reset.html', data) + + user = reset_code.user + + new_password = request.POST.get('password') + confirm_password = request.POST.get('confirm-password') + + if new_password != confirm_password: + data = {'errors': ['Passwords do not match']} + return TemplateResponse(request, 'password_reset.html', data) + + user.set_password(new_password) + user.save() + login(request, user) + reset_code.delete() + return redirect('/') + + +@method_decorator(login_required, name='dispatch') +class ChangePassword(View): + ''' change password as logged in user ''' + def post(self, request): + ''' allow a user to change their password ''' + new_password = request.POST.get('password') + confirm_password = request.POST.get('confirm-password') + + if new_password != confirm_password: + return redirect('/user-edit') + + request.user.set_password(new_password) + request.user.save() + login(request, request.user) + return redirect('/user/%s' % request.user.localname) diff --git a/bookwyrm/vviews.py b/bookwyrm/vviews.py index ebae3696..66b26615 100644 --- a/bookwyrm/vviews.py +++ b/bookwyrm/vviews.py @@ -332,14 +332,6 @@ def about_page(request): return TemplateResponse(request, 'about.html', data) -@require_GET -def password_reset_request(request): - ''' invite management page ''' - return TemplateResponse( - request, - 'password_reset_request.html', - {'title': 'Reset Password'} - ) @require_GET