Switching between mobile email app and browser app is pretty common scenario in mobile environment. After follow password reset url django-allauth return bad token text.
This is due to url was accessed from mail app.
iOS 12.1.3 , latest Gmail and Chrome
django-allauth==0.38.0
How to reproduce:
Expected result:
User will be shown password reset form (which will be shown if you copy and paste provided url )
Here is how the current system works:
What happens on iOS, in particular in Gmail's app is that Gmail follows the link for you to its final URL in its own "internal browser" and then opens only the destination URL in the actual external browser. The problem here is that the session is now saved in your Gmail's browser, and the actual browser that you intend to use only sees this fake key and an empty session. Hence the library says that it cannot reset anything, because the token is bad.
To fix this, you can manually overwrite this view, get rid of this redirect dance, and just reset a password via the original URL with password reset key in its GET parameter.
In your views.py, add this overwritten method:
from allauth.account.forms import UserTokenForm
from allauth.account.views import PasswordResetFromKeyView as AllauthPasswordResetFromKeyView
from allauth.account.views import _ajax_response
from django.views.generic.edit import FormView
class PasswordResetFromKeyView(AllauthPasswordResetFromKeyView):
def dispatch(self, request, uidb36, key, **kwargs):
self.request = request
self.key = key
token_form = UserTokenForm(
data={'uidb36': uidb36, 'key': self.key})
if token_form.is_valid():
# Store the key in the session and redirect to the
# password reset form at a URL without the key. That
# avoids the possibility of leaking the key in the
# HTTP Referer header.
# (Ab)using forms here to be able to handle errors in XHR #890
token_form = UserTokenForm(
data={'uidb36': uidb36, 'key': self.key})
if token_form.is_valid():
self.reset_user = token_form.reset_user
return super(FormView, self).dispatch(request, uidb36, self.key, **kwargs)
self.reset_user = None
response = self.render_to_response(
self.get_context_data(token_fail=True)
)
return _ajax_response(self.request, response, form=token_form)
Then in your urls.py add this view before allauth's urls like so:
from django.urls import include, path, url
from project.views import PasswordResetFromKeyView
urlpatterns = [
url(r"^password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$", PasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"),
url(include('allauth.urls')),
]
@Danilka thank you for your input. I'm not an experienced programmer, can you please explain why are you validating the token_form twice with the same data?!
Most helpful comment
Here is how the current system works:
What happens on iOS, in particular in Gmail's app is that Gmail follows the link for you to its final URL in its own "internal browser" and then opens only the destination URL in the actual external browser. The problem here is that the session is now saved in your Gmail's browser, and the actual browser that you intend to use only sees this fake key and an empty session. Hence the library says that it cannot reset anything, because the token is bad.
To fix this, you can manually overwrite this view, get rid of this redirect dance, and just reset a password via the original URL with password reset key in its GET parameter.
In your views.py, add this overwritten method:
Then in your urls.py add this view before allauth's urls like so: