PasswordResetConfirmView doesn't work through redirect in newest Safari

249 views
Skip to first unread message

Henrik Ossipoff Hansen

unread,
Jan 29, 2019, 12:32:50 PM1/29/19
to Django users
Hi all,

Recently we've upgraded a project from Django 2.x to 2.1, which meant upgrading our old reset password functions based views to the newer class based models. However, since the change we're experiencing issues with PasswordResetConfirmView.

What we experiencing, in short, is that when we send our mails through Mandrill (the SMTP service from Mailchimp), and the user presses the reset password link, they get an error saying the link is invalid/expired. This happens only if the customer goes through the Mandrill redirect link AND uses the newest stable version of Safari shipping with macOS Mojave or the newest iOS versions.

I've been digging through the code, and it seems that the session framework/cookie framework simply isn't working when going through that particular redirect link. When the user lands on the first page in the confirmation view (which includes the full token), they get "None" as their sessionid. Just before the redirect (where the token is put into session, and replaced with "set password"), they get an actual sessionid - but when they're redirected to the "set-password" page, they now have a new sessionid, which of course means PasswordResetConfirmView cannot find their token in session storage, leading to the error message.

While I realise this might not be an issue with Django, and rather the way Mandrill deals with their redirection of links/some weird inner-workings in Safari, I'm wondering how other people have dealt with this issue.

Any help appreciated :)

Jason

unread,
Jan 29, 2019, 3:49:12 PM1/29/19
to Django users
You're not the only one with this issue.

There's an open ticket on the django bug tracker at https://code.djangoproject.com/ticket/29975

Unfortunately, doesn't seem to be much action on this.  It really is a breaking change with Safari.

Henrik Ossipoff Hansen

unread,
Jan 30, 2019, 2:27:35 AM1/30/19
to Django users
Ah darn it - I didn't even think of checking the bug tracker first. Usually when we're experiencing things like this it's because we've messed up somehow :)

I guess for now we'll see if we can backport the old function based view which didn't do that redirect straight after landing on the page with the reset token, in order for the tracking prevention to not kick in.

Thanks!

Jason

unread,
Jan 30, 2019, 10:07:38 AM1/30/19
to Django users
I agree, when you hear hoofbeats, you think horses and not zebras. Except in this case, it was indeed a zebra you saw :-)

René Fleschenberg

unread,
Jan 30, 2019, 1:47:46 PM1/30/19
to django...@googlegroups.com
Hi,

I am currently using the code below as a workaround.

Please be super-careful with this. I did not test this extensively. Do
not use it if your password reset page requests external resources. Make
sure you understand the security implications before you deploy it.

apollo13 suggested this on IRC as a temporary fix, but any bugs in the
implementation are mine. Obviously, no warranty ;)


```
from django.contrib.auth import views as auth_views
from django.contrib.auth.views import INTERNAL_RESET_SESSION_TOKEN
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters


class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):

# https://code.djangoproject.com/ticket/29975

@method_decorator(sensitive_post_parameters())
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
assert 'uidb64' in kwargs and 'token' in kwargs

self.validlink = False
self.user = self.get_user(kwargs['uidb64'])

if self.user is not None:
token = kwargs['token']
if self.token_generator.check_token(self.user, token):
self.validlink = True
form = self.get_form()
if form.is_valid():
self.request.session[
INTERNAL_RESET_SESSION_TOKEN
] = 'dummy'
return self.form_valid(form)
return self.form_invalid(form)

return self.render_to_response(self.get_context_data())
```


--
René Fleschenberg

Reply all
Reply to author
Forward
0 new messages