Reputation: 2459
I use standard Django view, password_reset_confirm(), to reset user's password. After user follows password reset link in the letter, he enters new password and then view redirects him to the site root:
urls.py
url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm', {
'template_name': 'website/auth/password_reset_confirm.html',
'post_reset_redirect': '/',
}, name='password_reset_confirm'),
After Django redirects user, he is not authenticated. I don't want him to type password again, instead, I want to authenticate him right after he set new password.
To implement this feature, I created a delegate view. It wraps standard one and handles its output. Because standard view redirects user only if password reset succeeded, I check status code of response it returns, and if it's a redirect, retrieve user from DB again and authenticate him.
urls.py
url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
views.password_reset_confirm_delegate, {
'template_name': 'website/auth/password_reset_confirm.html',
'post_reset_redirect': '/',
}, name='password_reset_confirm'),
views.py
@sensitive_post_parameters()
@never_cache
def password_reset_confirm_delegate(request, **kwargs):
response = password_reset_confirm(request, **kwargs)
# TODO Other way?
if response.status_code == 302:
try:
uid = urlsafe_base64_decode(kwargs['uidb64'])
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
pass
else:
user = authenticate(username=user.username, passwordless=True)
login(request, user)
return response
backends.py
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username, passwordless=False):
if not passwordless:
return None
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
settings.py
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'website.backends.PasswordlessAuthBackend'
)
Are there any better ways to do this?
Upvotes: 8
Views: 2857
Reputation: 1234
Starting Django 1.11 your can do this by using the class based view. You need to override the password_reset_confirm url to pass post_reset_login=True
and success_url
to PasswordResetConfirmView:
urlpatterns += [
url(
r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
views.PasswordResetConfirmView.as_view(
post_reset_login=True,
success_url=reverse_lazy('studygroups_login_redirect')
),
name='password_reset_confirm'
),
]
Upvotes: 8
Reputation: 27861
Since password_reset_confirm
is not class-based-view, you cant cleanly customize it in any significant way without resorting to middleware-type tricks. Therefore what you are doing seems to be the most efficient way at the moment.
If django would be been passing request
to the SetPasswordForm
(similar to how DRF passes request
to serializers), you could of overwritten the form's save()
to login the user there however as now that is also not possible.
You can also look into other 3rd party libs which implement auth as class based views. From a quick google search, the most promising are:
auth.views
as class based views. You can overwrite form_valid
in order to authenticate user.Upvotes: 1