3

I'm using Django 3.1 with its auth contrib module. I have an API-only application, in which I initiate a password reset using the following Django view

class ResetPasswordView(SuccessMessageMixin, PasswordResetView):
    reset_password_template_name = 'templates/users/password_reset.html'
    email_template_name = 'users/password_reset_email.html'
    subject_template_name = 'users/password_reset_subject'
    success_message = "We've emailed you instructions for setting your password, " \
                      "if an account exists with the email you entered. You should receive them shortly." \
                      " If you don't receive an email, " \
                      "please make sure you've entered the address you registered with, and check your spam folder."
    success_url = reverse_lazy('users-home')

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        request.csrf_processing_done = True
        return super().dispatch(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        email = json.loads(request.body).get('username')
        try:
            if User.objects.get(email=email).is_active:
                form = PasswordResetForm({'email': email})
                print("form valid? %s" % form.is_valid())
                if form.is_valid():
                    request = HttpRequest()
                    request.META['SERVER_NAME'] = socket.gethostbyname('localhost') #'127.0.0.1'
                    request.META['SERVER_PORT'] = 8000
                    # calling save() sends the email
                    # check the form in the source code for the signature and defaults
                    form.save(request=request,
                        use_https=False,
                        from_email="laredotornado@yahoo.com",
                        email_template_name='../templates/users/password_reset_email.html')
                print("email: %s " % email)
                return super(ResetPasswordView, self).post(request, *args, **kwargs)
        except Exception as e:
            print("\n\nerror ...\n\n")
            print(e)
            # this for if the email is not in the db of the system
            return super(ResetPasswordView, self).post(request, *args, **kwargs)

This generates an email in which a link appears, which looks similar to

http://127.0.0.1:8000/password-reset-confirm/Mg/bhd3nc-29fa9003c9c61c2bda5cff0a66b38bdf/

My question is, how do I submit this token (with the user's desired new password) back to the server so that the srver validates the token anad then updates the password for the user?

Edit: Stack trace per the answer given ...

Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/directory/views.py", line 395, in dispatch
    return super().dispatch(request, **data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/contrib/auth/views.py", line 260, in dispatch
    assert 'uidb64' in kwargs and 'token' in kwargs

Exception Type: AssertionError at /password-reset-complete
Dave
  • 15,639
  • 133
  • 442
  • 830

2 Answers2

0

Reuse django.contrib.auth.views.PasswordResetConfirmView, which calls self.token_generator.check_token(self.user, session_token).

import json

from django.contrib.auth.views import INTERNAL_RESET_SESSION_TOKEN, PasswordResetConfirmView
from django.views.decorators.csrf import csrf_exempt


class ConfirmResetPasswordView(PasswordResetConfirmView):

    @csrf_exempt
    def dispatch(self, request):
        data = self.data = json.loads(request.body)
        # self.user = self.get_user(data["uidb64"])
        # self.token_generator.check_token(self.user, data['token'])
        self.request.session[INTERNAL_RESET_SESSION_TOKEN] = data.pop('token')
        data['token'] = self.reset_url_token
        return super().dispatch(request, **data)

    def get_form_kwargs(self):
        return {
            'data': self.data,
            'user': self.user,
        }

    def form_valid(self, form):
        _ = form.save()
        del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
        return HttpResponse()

Sample POST request body:

{
    "uidb64": "Mg",
    "token": "bhd3nc-29fa9003c9c61c2bda5cff0a66b38bdf",
    "new_password1": "new_password",
    "new_password2": "new_password"
}
aaron
  • 39,695
  • 6
  • 46
  • 102
  • Thanks I set the view and submitted the post request as you have it, but the line "return super().dispatch(request, **data)" is giving the error, "No exception message supplied". – Dave Feb 04 '23 at 21:26
  • Can you share a screenshot of how you submitted the POST request? The fields are not in your POST request. – aaron Feb 05 '23 at 03:16
  • I'll make you a counter-proposal -- if you help me get the answer, even if the bounty expires, I'll go and upvote 15 of your posts, giving you the equivalent of value of what the bounty would have been worth. – Dave Feb 05 '23 at 17:19
  • Can you share a screenshot of how you submitted the POST request? This answer works as-is. – aaron Feb 05 '23 at 17:21
  • I wrote a test that shows it works: [github.com/acjh/try-python/compare/django...so-75043246#mysite/polls/tests.py](https://github.com/acjh/try-python/compare/django...so-75043246#diff-476a378993d5f8e2cc57642602cd6400b6d41e12b53587f8ee6e1f730eab819d) – aaron Feb 05 '23 at 18:31
  • Is this resolved? I demonstrated that it works and you didn't even follow up on this. – aaron Apr 30 '23 at 03:06
  • @Dave Is this resolved? – aaron Aug 09 '23 at 14:17
-1

It depends on the authentication strategy implemented with your API. From the fact that you are sending a user a token, I assume that you are using token authentication. :) It means that on the API side you need to implement password reset endpoint with token authentication. Do not forget to expire/blacklist the token.

Another strategy would be to run the password reset process using Django password reset views, without messing up with the API.

alv2017
  • 752
  • 5
  • 14