diff --git a/dal/dal/templates/error.html b/dal/dal/templates/error.html index 0001244..2dba709 100644 --- a/dal/dal/templates/error.html +++ b/dal/dal/templates/error.html @@ -6,10 +6,12 @@ While trying to {{service}}, an error was encountered: {{error}}

You can try to:
+{% if urlname %}

or
+{% endif %}
diff --git a/dal/dal/templates/resetpasswordnew.html b/dal/dal/templates/resetpasswordnew.html new file mode 100644 index 0000000..133c94f --- /dev/null +++ b/dal/dal/templates/resetpasswordnew.html @@ -0,0 +1,14 @@ + Set new password for {{user}} + +

Please set new password for {{user}}

+

+
+ {% csrf_token %} + New Password:
+ +
Please confirm new password:
+ +
+ + +
diff --git a/dal/dal/templates/send_resetrequest.html b/dal/dal/templates/send_resetrequest.html index c03ab7d..1a54e99 100644 --- a/dal/dal/templates/send_resetrequest.html +++ b/dal/dal/templates/send_resetrequest.html @@ -2,7 +2,8 @@

Reset request processed and confirmation email sent



-You will shortly get the confirmation email at {{email}} to confirm that you wish to reset the password for {{user}}. +You will shortly get the confirmation email to confirm that you wish to reset the password for {{user}}.
+Please follow the link in the email to reset your password.

diff --git a/dal/dal/urls.py b/dal/dal/urls.py index cdd5ee6..8f53d1e 100644 --- a/dal/dal/urls.py +++ b/dal/dal/urls.py @@ -18,7 +18,7 @@ from django.urls import path from django.conf.urls import url from django.contrib import admin -from .views import Register, ChangeData, ChangePassword, ResetPassword, DeleteAccount, Index, LogOut +from .views import Register, ChangeData, ChangePassword, ResetPassword, DeleteAccount, Index, LogOut, ResetRequest urlpatterns = [ # path('admin/', admin.site.urls), @@ -29,5 +29,7 @@ urlpatterns = [ path('deleteaccount/', DeleteAccount.as_view(), name="account_delete"), path('index/', Index.as_view(), name="index"), path('logout/', LogOut.as_view(), name="logout"), + path('reset///', ResetRequest.as_view()), + path('reset/', ResetRequest.as_view(), name="reset"), path('', Index.as_view(), name="index"), ] diff --git a/dal/dal/views.py b/dal/dal/views.py index 5393444..175db04 100644 --- a/dal/dal/views.py +++ b/dal/dal/views.py @@ -6,6 +6,9 @@ from django.http import HttpResponse, HttpResponseRedirect from django.core.validators import validate_email, ValidationError from django.urls import reverse_lazy from django_nameko import get_pool +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from base64 import b64encode, b64decode +from datetime import datetime # Check to see if the username is already taken # Helper function, not to be set up as a view @@ -170,16 +173,96 @@ class ResetPassword(View): urlname = 'reset_password' service = 'send a password reset request' user = request.POST.get('user') - if check_user_exists(user): - # TODO: Get a good backend for reset requests - # Sending the reset request - email = self.send_resetrequest(user) - return render(request, 'send_resetrequest.html', { 'user': user, 'email': email } ) - return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The user does not exist.' } ) + # First, check if the user exists + if not check_user_exists(user): + return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The user does not exist.' } ) + # user exists, so try to get email + with get_pool().next() as rpc: + (state, tmp1, tmp2, email) = rpc.getuserdata.get_data(user) + # Either error with the datalookup or no email provided + if state == "error" or email == 'No email given' or not email: + return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Unable to retrieve email address for user.' } ) + # Try to send the email out + emailsend = self.email(user, email) + # Email got sent out + if emailsend == True: + return render(request, 'send_resetrequest.html', { 'user': user } ) + # Error while trying to send email + else: + return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': emailsend } ) + + def email(self, user, email): + #TODO figure out how to send email + email_from = 'Userservice at ungleich ' + to = '%s <%s>' % (user, email) + subject = 'Password reset request for %s' % user + no-reply = True + link = self.build_reset_link(user) + body = 'This is an automated email which was triggered by a reset request for the user %s.\n' % user + body += 'If you received this email in error, please disregard it. If you get multiple emails like this, please contact us to look into potential abuse.\n' + body += 'To reset your password, please follow the link below:\n' + body += '%s\n\n' % link + body += 'The link will remain active for 24 hours.\n' + # For debug + return link + + def build_reset_link(self, user): + host = 'localhost:8000' + x = PasswordResetTokenGenerator() + token = x.make_token(user) + buser = bytes(user, 'utf-8') + userpart = b64encode(buser) + d = datetime.now() + # TODO Make Model und put it into the database + link = 'https://%s/reset/%s/%s/' % (host, userpart.decode('utf-8'), token) + return link + + +# Catch the resetrequest and check it +class ResetRequest(View): + + # Gets the URL with user in b64 and the token, and checks it + # Also cleans the database + def get(self, request, user=None, token=None): + # Cleans up outdated tokens + self.clean_db() + if user == None or token == None: + return HttpResponse('Invalid URL.', status=404) + # extract user from b64 format + tmp_user = bytes(user, 'utf-8') + user = b64decode(tmp_user) + user_clean = user.decode('utf-8') + d = datetime.now() + #TODO write the model and check if token is still active and belongs to the user + # set checks_out = True if yes + if not checks_out: + return HttpResponse('Invalid URL.', status=404) + else: + return render(request, 'resetpasswordnew.html', { 'user': user_clean } ) + + + # Gets the post form with the new password and sets it + def post(self, request): + service = 'reset the password' + password1 = request.POST.get("password1") + password2 = request.POST.get("password2") + user = request.POST.get("user") + if password1 == "" or not password1 or password2 == "" or not password2: + return render(request, 'error.html', { 'service': service, 'error': 'Please supply a password and confirm it.' } ) + if password1 != password2: + return render(request, 'error.html', { 'service': service, 'error': 'The supplied passwords do not match.' } ) + with get_pool().next() as rpc: + pwd = r'%s' % password1 + result = rpc.changepassword.change_password(user, pwd) + if result == True: + return render(request, 'changedpassword.html', { 'user': user } ) + else: + return render(request, 'error.html', { 'service': service, 'error': result } ) + + # Cleans up outdated tokens + def clean_db(self): + # TODO write the model and use this to clean tokens > 24h old - def send_resetrequest(self, user): - #TODO: call nameko to get the associated email and send a confirmation mail - return "test@example.com" # The logged in user can change the password here diff --git a/nameko-func.py b/nameko-func.py index 7b95046..035f69d 100644 --- a/nameko-func.py +++ b/nameko-func.py @@ -222,19 +222,6 @@ class ChangeUserData(object): return True -# Request a password reset -# TODO: Set up a system for it -# Basic idea: send email to customer with a confirmation request, set reply-to for something that handles it, -class PasswordResetRequest(object): - name = "passwordresetrequest" - dispatch = EventDispatcher() - - @rpc - def send_request(self, user): - # TODO: Find a good system for that - return "To be done" - - # change the password for the user class ChangePassword(object): name = "changepassword"