Django Rest Auth – UID invalid value error
Question:
I have a DRF server, and I’ve been trying to send a password reset email, using the PasswordResetForm
.
I receive an email as expected, though when trying to send a reset the password, I receive:
{
"uid": [
"Invalid value"
]
}
After some investigation, I’ve found that in Django’s PasswordResetConfirmSerializer
‘s file, there is a conditional import which is responsible for the issue – I’m using allauth
and so it imports the wrong uid_decoder
module (it works using the other module):
# this imports the wrong uid_decoder
if 'allauth' in settings.INSTALLED_APPS:
from allauth.account.forms import default_token_generator
from allauth.account.utils import url_str_to_user_pk as uid_decoder
else:
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_decode as uid_decoder
Is there a better way to handle the issue than editing the dj_rest_auth file?
Thanks!
Email send code, if needed:
from django.conf import settings
from django.contrib.auth.forms import PasswordResetForm
def send_user_invite(email):
# send invitation to reset password & join the platform
form_options = {
"use_https": True,
"from_email": getattr(settings, "DEFAULT_FROM_EMAIL"),
# "request": request,
"subject_template_name": "registration/password_reset_subject.txt",
"email_template_name": "users/invite_with_password_reset.html",
"extra_email_context": {"reset_base_url": settings.RESET_BASE_URL},
}
form = PasswordResetForm(data={"email": email})
if form.is_valid():
form.save(**form_options)
Answers:
My workaround was to write a custom subclass for ResetPasswordForm
, as follows:
# helpers.py
from server.users.forms import SendInviteForm
def send_user_invite(email):
# send invitation to reset password & join the platform
form = SendInviteForm(data={"email": email})
if form.is_valid():
form.save(None)
from os import path
from allauth.account.forms import (
EmailAwarePasswordResetTokenGenerator,
ResetPasswordForm,
)
from allauth.account.utils import user_pk_to_url_str
from django.conf import settings
from django.contrib.auth import forms as admin_forms
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
class SendInviteForm(ResetPasswordForm):
"""
used to send an invitation to onboard the platform and reset the password
"""
default_token_generator = EmailAwarePasswordResetTokenGenerator()
def send_email_invite(self, email, uri, uid, token):
context = {
"uri": uri,
"uid": uid,
"token": token,
}
msg_plain = render_to_string("users/invite_with_password_reset.txt", context)
msg_html = render_to_string("users/invite_with_password_reset.html", context)
send_mail(
"Welcome!",
msg_plain,
None,
[email],
html_message=msg_html,
)
def save(self, request, **kwargs):
email = self.cleaned_data["email"]
token_generator = kwargs.get("token_generator", self.default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
uri = path.join(settings.CLIENT_BASE_URL, "he/welcome/reset-password")
self.send_email_invite(email, uri, user_pk_to_url_str(user), temp_key)
return self.cleaned_data["email"]
Late to the party, but facing the same issue. I’m also being tripped up but that if 'allauth' in settings.INSTALLED_APPS
statement in dj_rest_auth
‘s PasswordResetConfirmSerializer
validate()
method.
My workaround was to subclass PasswordResetConfirmSerializer
and pick the import statements that work for my setup, in my case use allauth
‘s default_token_generator
but django
‘s urlsafe_base64_decode
like so:
from dj_rest_auth.serializers import PasswordResetConfirmSerializer as _PasswordResetConfirmSerializer
class PasswordResetConfirmSerializer(_PasswordResetConfirmSerializer):
# Copy of parent class method with "if 'allauth'" taken out
def validate(self, attrs):
from allauth.account.forms import default_token_generator
from django.utils.http import urlsafe_base64_decode as uid_decoder
# Decode the uidb64 (allauth use base36) to uid to get User object
try:
uid = force_str(uid_decoder(attrs['uid']))
self.user = User._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
raise ValidationError({'uid': [_('Invalid value')]})
if not default_token_generator.check_token(self.user, attrs['token']):
raise ValidationError({'token': [_('Invalid value')]})
self.custom_validation(attrs)
# Construct SetPasswordForm instance
self.set_password_form = self.set_password_form_class(
user=self.user, data=attrs,
)
if not self.set_password_form.is_valid():
raise serializers.ValidationError(self.set_password_form.errors)
return attrs
Then in settings.py
set:
REST_AUTH = {
'PASSWORD_RESET_CONFIRM_SERIALIZER': 'users.serializers.PasswordResetConfirmSerializer'
}
A bit messy, but it works.
I have a DRF server, and I’ve been trying to send a password reset email, using the PasswordResetForm
.
I receive an email as expected, though when trying to send a reset the password, I receive:
{
"uid": [
"Invalid value"
]
}
After some investigation, I’ve found that in Django’s PasswordResetConfirmSerializer
‘s file, there is a conditional import which is responsible for the issue – I’m using allauth
and so it imports the wrong uid_decoder
module (it works using the other module):
# this imports the wrong uid_decoder
if 'allauth' in settings.INSTALLED_APPS:
from allauth.account.forms import default_token_generator
from allauth.account.utils import url_str_to_user_pk as uid_decoder
else:
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_decode as uid_decoder
Is there a better way to handle the issue than editing the dj_rest_auth file?
Thanks!
Email send code, if needed:
from django.conf import settings
from django.contrib.auth.forms import PasswordResetForm
def send_user_invite(email):
# send invitation to reset password & join the platform
form_options = {
"use_https": True,
"from_email": getattr(settings, "DEFAULT_FROM_EMAIL"),
# "request": request,
"subject_template_name": "registration/password_reset_subject.txt",
"email_template_name": "users/invite_with_password_reset.html",
"extra_email_context": {"reset_base_url": settings.RESET_BASE_URL},
}
form = PasswordResetForm(data={"email": email})
if form.is_valid():
form.save(**form_options)
My workaround was to write a custom subclass for ResetPasswordForm
, as follows:
# helpers.py
from server.users.forms import SendInviteForm
def send_user_invite(email):
# send invitation to reset password & join the platform
form = SendInviteForm(data={"email": email})
if form.is_valid():
form.save(None)
from os import path
from allauth.account.forms import (
EmailAwarePasswordResetTokenGenerator,
ResetPasswordForm,
)
from allauth.account.utils import user_pk_to_url_str
from django.conf import settings
from django.contrib.auth import forms as admin_forms
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
class SendInviteForm(ResetPasswordForm):
"""
used to send an invitation to onboard the platform and reset the password
"""
default_token_generator = EmailAwarePasswordResetTokenGenerator()
def send_email_invite(self, email, uri, uid, token):
context = {
"uri": uri,
"uid": uid,
"token": token,
}
msg_plain = render_to_string("users/invite_with_password_reset.txt", context)
msg_html = render_to_string("users/invite_with_password_reset.html", context)
send_mail(
"Welcome!",
msg_plain,
None,
[email],
html_message=msg_html,
)
def save(self, request, **kwargs):
email = self.cleaned_data["email"]
token_generator = kwargs.get("token_generator", self.default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
uri = path.join(settings.CLIENT_BASE_URL, "he/welcome/reset-password")
self.send_email_invite(email, uri, user_pk_to_url_str(user), temp_key)
return self.cleaned_data["email"]
Late to the party, but facing the same issue. I’m also being tripped up but that if 'allauth' in settings.INSTALLED_APPS
statement in dj_rest_auth
‘s PasswordResetConfirmSerializer
validate()
method.
My workaround was to subclass PasswordResetConfirmSerializer
and pick the import statements that work for my setup, in my case use allauth
‘s default_token_generator
but django
‘s urlsafe_base64_decode
like so:
from dj_rest_auth.serializers import PasswordResetConfirmSerializer as _PasswordResetConfirmSerializer
class PasswordResetConfirmSerializer(_PasswordResetConfirmSerializer):
# Copy of parent class method with "if 'allauth'" taken out
def validate(self, attrs):
from allauth.account.forms import default_token_generator
from django.utils.http import urlsafe_base64_decode as uid_decoder
# Decode the uidb64 (allauth use base36) to uid to get User object
try:
uid = force_str(uid_decoder(attrs['uid']))
self.user = User._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
raise ValidationError({'uid': [_('Invalid value')]})
if not default_token_generator.check_token(self.user, attrs['token']):
raise ValidationError({'token': [_('Invalid value')]})
self.custom_validation(attrs)
# Construct SetPasswordForm instance
self.set_password_form = self.set_password_form_class(
user=self.user, data=attrs,
)
if not self.set_password_form.is_valid():
raise serializers.ValidationError(self.set_password_form.errors)
return attrs
Then in settings.py
set:
REST_AUTH = {
'PASSWORD_RESET_CONFIRM_SERIALIZER': 'users.serializers.PasswordResetConfirmSerializer'
}
A bit messy, but it works.