Repository: hydroshare/hydroshare

Issue:
Users unable to reset password
We had several users contact help with the issue that they can't reset their passwords using the reset pw functionality, They can successfully request a link that is send to their mailbox but upon clicking they get an undescriptive message that an Error occured, see below. 
![pw error](https://user-images.githubusercontent.com/5427742/116433187-40cfb380-a817-11eb-8fd5-3c616c204e54.png)

This does not happen for all users. 

I'll post the issue from the user here as well:

Hi,
I’m trying to log in to HydroShare but cannot remember my password. HydroShare recognizes my login (AnneNolin) and sends a link to my email, but the link is broken and I get an error message that just says “Error”.
Please let me know how I can reset my password so I can login to HydroShare.
Thank you,
Anne Nolin


Discussion:
Here is a hint to what is going on in the logs.
```
[28/Apr/2021 15:16:14] ERROR [django.request:135] Internal Server Error: /email_verify_password_reset/5q6-9e9aa98dcb75e88e23bd/4gi/
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/hydroshare/theme/views.py", line 560, in email_verify_password_reset
    if not user.is_active:
AttributeError: 'NoneType' object has no attribute 'is_active'
```
I see multiple tries for this.  My guess would be that the user requested multiple password resets and then clicked the link on a previous request which is expired by the new request.  We could probably update the password reset logic to just resend the same link each time.  Pretty sure we've gone around this issue before.
@sblack-usu if you are able to make a change that would satisfy this user without too much trouble, I'd appreciate it.  I am happy to discuss options that might involve reusing the same link, or having an error message that explains that the link expired, and if it is because the user requested a new one indicating when a new one was sent (and maybe resending).
My theory about the older links expiring with new password reset requests is incorrect.  The last login time of the user is baked into the token so all the links are valid for 7 days or until one of the links is clicked which updates the user login time and invalidates the other links.

If you look at the screenshot above, you can see Anne is logged in.  I believe she clicked a link and then clicked a link again (could have been the same one or a different one).  The first click actually logged her in, updated the timestamp, and redirected her to the reset password page.  The second link click tried to do the same thing, but the token was invalid because the user login date was updated.  The resulting error message is the screenshot above which needs to be updated to be handled correctly and not distract from what actually happened.  This can be fixed by rearranging some of the logic in the password reset view.

Resolving patch:
diff --git a/theme/views.py b/theme/views.py
--- a/theme/views.py
+++ b/theme/views.py
@@ -557,12 +557,12 @@ def email_verify_password_reset(request, uidb36=None, token=None):
     """
 
     user = authenticate(uidb36=uidb36, token=token)
-    if not user.is_active:
-        # password reset for user that hasn't hit the verification email, since they're resetting
-        # the password, we know the email is good
-        user.is_active = True
-        user.save()
     if user is not None:
+        if not user.is_active:
+            # password reset for user that hasn't hit the verification email, since they're resetting
+            # the password, we know the email is good
+            user.is_active = True
+            user.save()
         auth_login(request, user)
         # redirect to user to password reset page
         return HttpResponseRedirect(reverse('new_password_for_reset', kwargs={'token': token}))


Test patch: