Django REST-Auth Password Reset Tokengenerator (Generates b36 uid on request, wants b64 uid on confirm)












0














I am completely confused by the django middleware available:



I simply want to get password-reset (and later password-change) functionality running, using django with rest_auth on the backend and Vue on the frontend.



Step 1: Requesting the Reset-Link via Mail



Views



So far I have made a CustomPasswordResetView:



# project/accounts/views.py
from rest_auth.views import PasswordResetView

class CustomPasswordResetView(PasswordResetView):
pass


Serializers



and a CustomPasswordResetSerializer:



# project/accounts/serializers.py
from rest_auth.serializers import PasswordResetSerializer

class CustomPasswordResetSerializer(PasswordResetSerializer):
email = serializers.EmailField()
password_reset_form_class = ResetPasswordForm

def validate_email(self, value):
# Create PasswordResetForm with the serializer
self.reset_form = self.password_reset_form_class(data=self.initial_data)
if not self.reset_form.is_valid():
raise serializers.ValidationError(self.reset_form.errors)

###### FILTER YOUR USER MODEL ######
if not get_user_model().objects.filter(email=value).exists():
raise serializers.ValidationError(_('Invalid e-mail address'))

return value

def save(self):
request = self.context.get('request')
# Set some values to trigger the send_email method.
opts = {
'use_https': request.is_secure(),
'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
'request': request,
}
opts.update(self.get_email_options())
self.reset_form.save(**opts)


Settings.py



In the settings.py I have these fields, which seem relevant to me for my problem:



# project/vuedj/settings.py
REST_AUTH_SERIALIZERS = {
"USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
"LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
"PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
}


(The complete settings.py is attached at the bottom)



URL patterns



My urls already catch my API request in order to send the Password-Reset Email:



# project/vuedj/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('api.urls')),
path('accounts/', include('allauth.urls')),
path('', api_views.index, name='home')
]


# project/api/urls.py
urlpatterns = [
path('auth/', include('accounts.urls')),
# other paths...
]


# project/accounts/urls.py
urlpatterns = [
path('', acc_views.UserListView.as_view(), name='user-list'),
path('login/', acc_views.UserLoginView.as_view(), name='login'),
path('logout/', acc_views.UserLogoutView.as_view(), name='logout'),
path('register/', acc_views.CustomRegisterView.as_view(), name='register'),
path('reset-password/', acc_views.CustomPasswordResetView.as_view(), name='reset-password'),
path('reset-password-confirm/', acc_views.CustomPasswordResetConfirmView.as_view(), name='reset-password-confirm'),
path('<int:pk>/', acc_views.UserDetailView.as_view(), name='user-detail')
]


Email with PW-Reset Token Generator



The CustomPasswordReset view will eventually generate a nice email with a nice pw-reset link. The link is valid, as I click it, I can reset the password through the allauth templates perfectly.



This code is used by rest-auth (indirectly) to generate the reset-token:



# project/.venv/Lib/site-packages/allauth/account/forms.py
def save(self, request, **kwargs):
current_site = get_current_site(request)
email = self.cleaned_data["email"]
token_generator = kwargs.get("token_generator",
default_token_generator)

for user in self.users:

temp_key = token_generator.make_token(user)

# save it to the password reset model
# password_reset = PasswordReset(user=user, temp_key=temp_key)
# password_reset.save()

# send the password reset email
path = reverse("account_reset_password_from_key",
kwargs=dict(uidb36=user_pk_to_url_str(user),
key=temp_key))
url = build_absolute_uri(
request, path)

context = {"current_site": current_site,
"user": user,
"password_reset_url": url,
"request": request}

if app_settings.AUTHENTICATION_METHOD
!= AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail(
'account/email/password_reset_key',
email,
context)
return self.cleaned_data["email"]


This PasswordResetTokenGenerator is used in the code above:



# project/.venv/Lib/site-packages/django/contrib/auth/tokens.py
class PasswordResetTokenGenerator:
"""
Strategy object used to generate and check tokens for the password
reset mechanism.
"""
key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
secret = settings.SECRET_KEY

def make_token(self, user):
"""
Return a token that can be used once to do a password reset
for the given user.
"""
return self._make_token_with_timestamp(user, self._num_days(self._today()))

def check_token(self, user, token):
"""
Check that a password reset token is correct for a given user.
"""
if not (user and token):
return False
# Parse the token
try:
ts_b36, hash = token.split("-")
except ValueError:
return False

try:
ts = base36_to_int(ts_b36)
except ValueError:
return False

# Check that the timestamp/uid has not been tampered with
if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
return False

# Check the timestamp is within limit. Timestamps are rounded to
# midnight (server time) providing a resolution of only 1 day. If a
# link is generated 5 minutes before midnight and used 6 minutes later,
# that counts as 1 day. Therefore, PASSWORD_RESET_TIMEOUT_DAYS = 1 means
# "at least 1 day, could be up to 2."
if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
return False

return True


The classes above will be called by the rest_auth PasswordResetView:



# project/.venv/Lib/site-packages/rest_auth/views.py
class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.

Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)

def post(self, request, *args, **kwargs):
# Create a serializer with request.data
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)

serializer.save() # <----- Code from above (TokenGenerator) will be called inside this .save() method
# Return the success message with OK HTTP status
return Response(
{"detail": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)


As you can see, the Tokengenerator will return a uidb36 with the token.
It also assumes a uidb36 when the user would confirm the password-reset.
A generated token (for example the full link in the generated mail) would look like this:



http://localhost:8000/accounts/password/reset/key/16-52h-42b222e6dc30690b2e91/


Where 16 is the user id in base 36 (uidb36), I do not yet know what 52h means, but I assume, the third part of the token is the token itself (42b222e6dc30690b2e91)



Step 2: Send the token to the backend (aka "User clicks link")



I am stuck here.
The API-Endpoints of the Rest-Auth-Framework say:




/rest-auth/password/reset/confirm/ (POST)
uid
token
new_password1
new_password2




And when I send an object e.g:



{
uid: '16', // TODO maybe I have to convert it to base10...
token: '42b222e6dc30690b2e91',
new_password1: 'test123A$',
new_password2: 'test123A$'
}


via my api to http://localhost:8000/api/v1/auth/reset-password/ with the object above in the body of an axios-post request, my CustomPasswordResetConfirmView is triggered like expected, which is also just a Subclass of PasswordResetConfirmView from rest_auth, so this code is executed:



# project/.venv/Lib/site-packages/rest_auth/views.py
class PasswordResetConfirmView(GenericAPIView):
"""
Password reset e-mail link is confirmed, therefore
this resets the user's password.

Accepts the following POST parameters: token, uid,
new_password1, new_password2
Returns the success/fail message.
"""
serializer_class = PasswordResetConfirmSerializer
permission_classes = (AllowAny,)

@sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)

def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{"detail": _("Password has been reset with the new password.")}
)


The line serializer.is_valid(raise_exception=True) will call run_validation of the Serializer(BaseSerializer) of the rest_framework.
This will further use the PasswordResetConfirmSerializer of rest_auth:



# project/.venv/Lib/site-packages/rest_auth/serializers.py
class PasswordResetConfirmSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset e-mail.
"""
new_password1 = serializers.CharField(max_length=128)
new_password2 = serializers.CharField(max_length=128)
uid = serializers.CharField()
token = serializers.CharField()

set_password_form_class = SetPasswordForm

def custom_validation(self, attrs):
pass

def validate(self, attrs):
self._errors = {}

# Decode the uidb64 to uid to get User object
try:
uid = force_text(uid_decoder(attrs['uid']))
self.user = UserModel._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
raise ValidationError({'uid': ['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)
if not default_token_generator.check_token(self.user, attrs['token']):
raise ValidationError({'token': ['Invalid value']})

return attrs


And as you can finally see, this class is expecting a uidb64 instead of a uidb36 for the user id, and I do not even want to know whether the token-format is anyhow matching what is expected here.



I really cannot find good documentation about how to setup rest_auth properly for the full password-reset process: I got the email working, but for me it seems, rest_auth would generate a wrong token/reset-link for what it is actually expecting back from the user.



Summary



I believe, the password-reset-confirmation process is ending in the correct backend-code, while the email/token-generation is messed up.



All I want is to retrieve a uid and a token which I can send back to django rest-auth in order to let users reset their passwords.
Currently, it seems that these uids and tokens are created by one library and consumed by another library which both expect and create different formats of tokens and uids?



Thanks in advance!



Full settings.py



Here is my full settings.py:



# project/vuedj/settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
SECRET_KEY = persisted_settings.SECRET_KEY
DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = r'^/api/.*$'
CORS_ALLOW_CREDENTIALS = True

# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.github',
'rest_auth',
'rest_auth.registration',
'sceneries',
'accounts',
'api',
'app',
]

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = 'app-messages'
SITE_ID = 1

AUTH_USER_MODEL = 'accounts.User'
ACCOUNT_USER_MODEL_USERNAME_FIELD = 'username'
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'

ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = 'none'
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = True
ACCOUNT_USER_EMAIL_FIELD = 'email'
ACCOUNT_LOGOUT_ON_GET = True
ACCOUNT_FORMS = {"login": "accounts.forms.UserLoginForm"}
LOGIN_REDIRECT_URL = 'home'
LOGIN_URL = 'api/v1/accounts/login/'

CSRF_COOKIE_NAME = "csrftoken"

REST_AUTH_SERIALIZERS = {
"USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
"LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
"PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
}

REST_AUTH_REGISTER_SERIALIZERS = {
"REGISTER_SERIALIZER": "accounts.serializers.CustomRegisterSerializer",
}

# Following is added to enable registration with email instead of username
AUTHENTICATION_BACKENDS = (
# Needed to login by username in Django admin, regardless of `allauth`
"django.contrib.auth.backends.ModelBackend",
# `allauth` specific authentication methods, such as login by e-mail
"allauth.account.auth_backends.AuthenticationBackend",
)

MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'vuedj.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'templates/',
'templates/emails/'
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'vuedj.wsgi.application'

try:
DATABASES = persisted_settings.DATABASES
except AttributeError:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
STATIC_ROOT = os.path.join(BASE_DIR, '../staticfiles/static')
MEDIA_ROOT = os.path.join(BASE_DIR, '../staticfiles/mediafiles')
STATIC_URL = '/static/'
MEDIA_URL = '/media/'

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

NOSE_ARGS = [
'--with-coverage',
'--cover-package=app', # For multiple apps use '--cover-package=foo, bar'
]









share|improve this question





























    0














    I am completely confused by the django middleware available:



    I simply want to get password-reset (and later password-change) functionality running, using django with rest_auth on the backend and Vue on the frontend.



    Step 1: Requesting the Reset-Link via Mail



    Views



    So far I have made a CustomPasswordResetView:



    # project/accounts/views.py
    from rest_auth.views import PasswordResetView

    class CustomPasswordResetView(PasswordResetView):
    pass


    Serializers



    and a CustomPasswordResetSerializer:



    # project/accounts/serializers.py
    from rest_auth.serializers import PasswordResetSerializer

    class CustomPasswordResetSerializer(PasswordResetSerializer):
    email = serializers.EmailField()
    password_reset_form_class = ResetPasswordForm

    def validate_email(self, value):
    # Create PasswordResetForm with the serializer
    self.reset_form = self.password_reset_form_class(data=self.initial_data)
    if not self.reset_form.is_valid():
    raise serializers.ValidationError(self.reset_form.errors)

    ###### FILTER YOUR USER MODEL ######
    if not get_user_model().objects.filter(email=value).exists():
    raise serializers.ValidationError(_('Invalid e-mail address'))

    return value

    def save(self):
    request = self.context.get('request')
    # Set some values to trigger the send_email method.
    opts = {
    'use_https': request.is_secure(),
    'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
    'request': request,
    }
    opts.update(self.get_email_options())
    self.reset_form.save(**opts)


    Settings.py



    In the settings.py I have these fields, which seem relevant to me for my problem:



    # project/vuedj/settings.py
    REST_AUTH_SERIALIZERS = {
    "USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
    "LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
    "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
    }


    (The complete settings.py is attached at the bottom)



    URL patterns



    My urls already catch my API request in order to send the Password-Reset Email:



    # project/vuedj/urls.py
    urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('api.urls')),
    path('accounts/', include('allauth.urls')),
    path('', api_views.index, name='home')
    ]


    # project/api/urls.py
    urlpatterns = [
    path('auth/', include('accounts.urls')),
    # other paths...
    ]


    # project/accounts/urls.py
    urlpatterns = [
    path('', acc_views.UserListView.as_view(), name='user-list'),
    path('login/', acc_views.UserLoginView.as_view(), name='login'),
    path('logout/', acc_views.UserLogoutView.as_view(), name='logout'),
    path('register/', acc_views.CustomRegisterView.as_view(), name='register'),
    path('reset-password/', acc_views.CustomPasswordResetView.as_view(), name='reset-password'),
    path('reset-password-confirm/', acc_views.CustomPasswordResetConfirmView.as_view(), name='reset-password-confirm'),
    path('<int:pk>/', acc_views.UserDetailView.as_view(), name='user-detail')
    ]


    Email with PW-Reset Token Generator



    The CustomPasswordReset view will eventually generate a nice email with a nice pw-reset link. The link is valid, as I click it, I can reset the password through the allauth templates perfectly.



    This code is used by rest-auth (indirectly) to generate the reset-token:



    # project/.venv/Lib/site-packages/allauth/account/forms.py
    def save(self, request, **kwargs):
    current_site = get_current_site(request)
    email = self.cleaned_data["email"]
    token_generator = kwargs.get("token_generator",
    default_token_generator)

    for user in self.users:

    temp_key = token_generator.make_token(user)

    # save it to the password reset model
    # password_reset = PasswordReset(user=user, temp_key=temp_key)
    # password_reset.save()

    # send the password reset email
    path = reverse("account_reset_password_from_key",
    kwargs=dict(uidb36=user_pk_to_url_str(user),
    key=temp_key))
    url = build_absolute_uri(
    request, path)

    context = {"current_site": current_site,
    "user": user,
    "password_reset_url": url,
    "request": request}

    if app_settings.AUTHENTICATION_METHOD
    != AuthenticationMethod.EMAIL:
    context['username'] = user_username(user)
    get_adapter(request).send_mail(
    'account/email/password_reset_key',
    email,
    context)
    return self.cleaned_data["email"]


    This PasswordResetTokenGenerator is used in the code above:



    # project/.venv/Lib/site-packages/django/contrib/auth/tokens.py
    class PasswordResetTokenGenerator:
    """
    Strategy object used to generate and check tokens for the password
    reset mechanism.
    """
    key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
    secret = settings.SECRET_KEY

    def make_token(self, user):
    """
    Return a token that can be used once to do a password reset
    for the given user.
    """
    return self._make_token_with_timestamp(user, self._num_days(self._today()))

    def check_token(self, user, token):
    """
    Check that a password reset token is correct for a given user.
    """
    if not (user and token):
    return False
    # Parse the token
    try:
    ts_b36, hash = token.split("-")
    except ValueError:
    return False

    try:
    ts = base36_to_int(ts_b36)
    except ValueError:
    return False

    # Check that the timestamp/uid has not been tampered with
    if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
    return False

    # Check the timestamp is within limit. Timestamps are rounded to
    # midnight (server time) providing a resolution of only 1 day. If a
    # link is generated 5 minutes before midnight and used 6 minutes later,
    # that counts as 1 day. Therefore, PASSWORD_RESET_TIMEOUT_DAYS = 1 means
    # "at least 1 day, could be up to 2."
    if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
    return False

    return True


    The classes above will be called by the rest_auth PasswordResetView:



    # project/.venv/Lib/site-packages/rest_auth/views.py
    class PasswordResetView(GenericAPIView):
    """
    Calls Django Auth PasswordResetForm save method.

    Accepts the following POST parameters: email
    Returns the success/fail message.
    """
    serializer_class = PasswordResetSerializer
    permission_classes = (AllowAny,)

    def post(self, request, *args, **kwargs):
    # Create a serializer with request.data
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)

    serializer.save() # <----- Code from above (TokenGenerator) will be called inside this .save() method
    # Return the success message with OK HTTP status
    return Response(
    {"detail": _("Password reset e-mail has been sent.")},
    status=status.HTTP_200_OK
    )


    As you can see, the Tokengenerator will return a uidb36 with the token.
    It also assumes a uidb36 when the user would confirm the password-reset.
    A generated token (for example the full link in the generated mail) would look like this:



    http://localhost:8000/accounts/password/reset/key/16-52h-42b222e6dc30690b2e91/


    Where 16 is the user id in base 36 (uidb36), I do not yet know what 52h means, but I assume, the third part of the token is the token itself (42b222e6dc30690b2e91)



    Step 2: Send the token to the backend (aka "User clicks link")



    I am stuck here.
    The API-Endpoints of the Rest-Auth-Framework say:




    /rest-auth/password/reset/confirm/ (POST)
    uid
    token
    new_password1
    new_password2




    And when I send an object e.g:



    {
    uid: '16', // TODO maybe I have to convert it to base10...
    token: '42b222e6dc30690b2e91',
    new_password1: 'test123A$',
    new_password2: 'test123A$'
    }


    via my api to http://localhost:8000/api/v1/auth/reset-password/ with the object above in the body of an axios-post request, my CustomPasswordResetConfirmView is triggered like expected, which is also just a Subclass of PasswordResetConfirmView from rest_auth, so this code is executed:



    # project/.venv/Lib/site-packages/rest_auth/views.py
    class PasswordResetConfirmView(GenericAPIView):
    """
    Password reset e-mail link is confirmed, therefore
    this resets the user's password.

    Accepts the following POST parameters: token, uid,
    new_password1, new_password2
    Returns the success/fail message.
    """
    serializer_class = PasswordResetConfirmSerializer
    permission_classes = (AllowAny,)

    @sensitive_post_parameters_m
    def dispatch(self, *args, **kwargs):
    return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(
    {"detail": _("Password has been reset with the new password.")}
    )


    The line serializer.is_valid(raise_exception=True) will call run_validation of the Serializer(BaseSerializer) of the rest_framework.
    This will further use the PasswordResetConfirmSerializer of rest_auth:



    # project/.venv/Lib/site-packages/rest_auth/serializers.py
    class PasswordResetConfirmSerializer(serializers.Serializer):
    """
    Serializer for requesting a password reset e-mail.
    """
    new_password1 = serializers.CharField(max_length=128)
    new_password2 = serializers.CharField(max_length=128)
    uid = serializers.CharField()
    token = serializers.CharField()

    set_password_form_class = SetPasswordForm

    def custom_validation(self, attrs):
    pass

    def validate(self, attrs):
    self._errors = {}

    # Decode the uidb64 to uid to get User object
    try:
    uid = force_text(uid_decoder(attrs['uid']))
    self.user = UserModel._default_manager.get(pk=uid)
    except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
    raise ValidationError({'uid': ['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)
    if not default_token_generator.check_token(self.user, attrs['token']):
    raise ValidationError({'token': ['Invalid value']})

    return attrs


    And as you can finally see, this class is expecting a uidb64 instead of a uidb36 for the user id, and I do not even want to know whether the token-format is anyhow matching what is expected here.



    I really cannot find good documentation about how to setup rest_auth properly for the full password-reset process: I got the email working, but for me it seems, rest_auth would generate a wrong token/reset-link for what it is actually expecting back from the user.



    Summary



    I believe, the password-reset-confirmation process is ending in the correct backend-code, while the email/token-generation is messed up.



    All I want is to retrieve a uid and a token which I can send back to django rest-auth in order to let users reset their passwords.
    Currently, it seems that these uids and tokens are created by one library and consumed by another library which both expect and create different formats of tokens and uids?



    Thanks in advance!



    Full settings.py



    Here is my full settings.py:



    # project/vuedj/settings.py
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
    SECRET_KEY = persisted_settings.SECRET_KEY
    DEBUG = True
    ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
    CORS_ORIGIN_ALLOW_ALL = True
    CORS_URLS_REGEX = r'^/api/.*$'
    CORS_ALLOW_CREDENTIALS = True

    # Application definition

    INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.github',
    'rest_auth',
    'rest_auth.registration',
    'sceneries',
    'accounts',
    'api',
    'app',
    ]

    EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
    EMAIL_FILE_PATH = 'app-messages'
    SITE_ID = 1

    AUTH_USER_MODEL = 'accounts.User'
    ACCOUNT_USER_MODEL_USERNAME_FIELD = 'username'
    ACCOUNT_AUTHENTICATION_METHOD = 'username_email'

    ACCOUNT_EMAIL_REQUIRED = True
    ACCOUNT_EMAIL_VERIFICATION = 'none'
    ACCOUNT_UNIQUE_EMAIL = True
    ACCOUNT_USERNAME_REQUIRED = True
    ACCOUNT_USER_EMAIL_FIELD = 'email'
    ACCOUNT_LOGOUT_ON_GET = True
    ACCOUNT_FORMS = {"login": "accounts.forms.UserLoginForm"}
    LOGIN_REDIRECT_URL = 'home'
    LOGIN_URL = 'api/v1/accounts/login/'

    CSRF_COOKIE_NAME = "csrftoken"

    REST_AUTH_SERIALIZERS = {
    "USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
    "LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
    "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
    }

    REST_AUTH_REGISTER_SERIALIZERS = {
    "REGISTER_SERIALIZER": "accounts.serializers.CustomRegisterSerializer",
    }

    # Following is added to enable registration with email instead of username
    AUTHENTICATION_BACKENDS = (
    # Needed to login by username in Django admin, regardless of `allauth`
    "django.contrib.auth.backends.ModelBackend",
    # `allauth` specific authentication methods, such as login by e-mail
    "allauth.account.auth_backends.AuthenticationBackend",
    )

    MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]

    ROOT_URLCONF = 'vuedj.urls'

    TEMPLATES = [
    {
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [
    'templates/',
    'templates/emails/'
    ],
    'APP_DIRS': True,
    'OPTIONS': {
    'context_processors': [
    'django.template.context_processors.debug',
    'django.template.context_processors.request',
    'django.contrib.auth.context_processors.auth',
    'django.contrib.messages.context_processors.messages',
    ],
    },
    },
    ]

    WSGI_APPLICATION = 'vuedj.wsgi.application'

    try:
    DATABASES = persisted_settings.DATABASES
    except AttributeError:
    DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
    }

    REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
    'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.IsAuthenticated',
    ]
    }

    AUTH_PASSWORD_VALIDATORS = [
    {
    'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
    'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
    'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
    'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
    ]

    LANGUAGE_CODE = 'en-us'
    TIME_ZONE = 'UTC'
    USE_I18N = True
    USE_L10N = True
    USE_TZ = True

    STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
    )
    STATIC_ROOT = os.path.join(BASE_DIR, '../staticfiles/static')
    MEDIA_ROOT = os.path.join(BASE_DIR, '../staticfiles/mediafiles')
    STATIC_URL = '/static/'
    MEDIA_URL = '/media/'

    TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

    NOSE_ARGS = [
    '--with-coverage',
    '--cover-package=app', # For multiple apps use '--cover-package=foo, bar'
    ]









    share|improve this question



























      0












      0








      0







      I am completely confused by the django middleware available:



      I simply want to get password-reset (and later password-change) functionality running, using django with rest_auth on the backend and Vue on the frontend.



      Step 1: Requesting the Reset-Link via Mail



      Views



      So far I have made a CustomPasswordResetView:



      # project/accounts/views.py
      from rest_auth.views import PasswordResetView

      class CustomPasswordResetView(PasswordResetView):
      pass


      Serializers



      and a CustomPasswordResetSerializer:



      # project/accounts/serializers.py
      from rest_auth.serializers import PasswordResetSerializer

      class CustomPasswordResetSerializer(PasswordResetSerializer):
      email = serializers.EmailField()
      password_reset_form_class = ResetPasswordForm

      def validate_email(self, value):
      # Create PasswordResetForm with the serializer
      self.reset_form = self.password_reset_form_class(data=self.initial_data)
      if not self.reset_form.is_valid():
      raise serializers.ValidationError(self.reset_form.errors)

      ###### FILTER YOUR USER MODEL ######
      if not get_user_model().objects.filter(email=value).exists():
      raise serializers.ValidationError(_('Invalid e-mail address'))

      return value

      def save(self):
      request = self.context.get('request')
      # Set some values to trigger the send_email method.
      opts = {
      'use_https': request.is_secure(),
      'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
      'request': request,
      }
      opts.update(self.get_email_options())
      self.reset_form.save(**opts)


      Settings.py



      In the settings.py I have these fields, which seem relevant to me for my problem:



      # project/vuedj/settings.py
      REST_AUTH_SERIALIZERS = {
      "USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
      "LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
      "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
      }


      (The complete settings.py is attached at the bottom)



      URL patterns



      My urls already catch my API request in order to send the Password-Reset Email:



      # project/vuedj/urls.py
      urlpatterns = [
      path('admin/', admin.site.urls),
      path('api/v1/', include('api.urls')),
      path('accounts/', include('allauth.urls')),
      path('', api_views.index, name='home')
      ]


      # project/api/urls.py
      urlpatterns = [
      path('auth/', include('accounts.urls')),
      # other paths...
      ]


      # project/accounts/urls.py
      urlpatterns = [
      path('', acc_views.UserListView.as_view(), name='user-list'),
      path('login/', acc_views.UserLoginView.as_view(), name='login'),
      path('logout/', acc_views.UserLogoutView.as_view(), name='logout'),
      path('register/', acc_views.CustomRegisterView.as_view(), name='register'),
      path('reset-password/', acc_views.CustomPasswordResetView.as_view(), name='reset-password'),
      path('reset-password-confirm/', acc_views.CustomPasswordResetConfirmView.as_view(), name='reset-password-confirm'),
      path('<int:pk>/', acc_views.UserDetailView.as_view(), name='user-detail')
      ]


      Email with PW-Reset Token Generator



      The CustomPasswordReset view will eventually generate a nice email with a nice pw-reset link. The link is valid, as I click it, I can reset the password through the allauth templates perfectly.



      This code is used by rest-auth (indirectly) to generate the reset-token:



      # project/.venv/Lib/site-packages/allauth/account/forms.py
      def save(self, request, **kwargs):
      current_site = get_current_site(request)
      email = self.cleaned_data["email"]
      token_generator = kwargs.get("token_generator",
      default_token_generator)

      for user in self.users:

      temp_key = token_generator.make_token(user)

      # save it to the password reset model
      # password_reset = PasswordReset(user=user, temp_key=temp_key)
      # password_reset.save()

      # send the password reset email
      path = reverse("account_reset_password_from_key",
      kwargs=dict(uidb36=user_pk_to_url_str(user),
      key=temp_key))
      url = build_absolute_uri(
      request, path)

      context = {"current_site": current_site,
      "user": user,
      "password_reset_url": url,
      "request": request}

      if app_settings.AUTHENTICATION_METHOD
      != AuthenticationMethod.EMAIL:
      context['username'] = user_username(user)
      get_adapter(request).send_mail(
      'account/email/password_reset_key',
      email,
      context)
      return self.cleaned_data["email"]


      This PasswordResetTokenGenerator is used in the code above:



      # project/.venv/Lib/site-packages/django/contrib/auth/tokens.py
      class PasswordResetTokenGenerator:
      """
      Strategy object used to generate and check tokens for the password
      reset mechanism.
      """
      key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
      secret = settings.SECRET_KEY

      def make_token(self, user):
      """
      Return a token that can be used once to do a password reset
      for the given user.
      """
      return self._make_token_with_timestamp(user, self._num_days(self._today()))

      def check_token(self, user, token):
      """
      Check that a password reset token is correct for a given user.
      """
      if not (user and token):
      return False
      # Parse the token
      try:
      ts_b36, hash = token.split("-")
      except ValueError:
      return False

      try:
      ts = base36_to_int(ts_b36)
      except ValueError:
      return False

      # Check that the timestamp/uid has not been tampered with
      if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
      return False

      # Check the timestamp is within limit. Timestamps are rounded to
      # midnight (server time) providing a resolution of only 1 day. If a
      # link is generated 5 minutes before midnight and used 6 minutes later,
      # that counts as 1 day. Therefore, PASSWORD_RESET_TIMEOUT_DAYS = 1 means
      # "at least 1 day, could be up to 2."
      if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
      return False

      return True


      The classes above will be called by the rest_auth PasswordResetView:



      # project/.venv/Lib/site-packages/rest_auth/views.py
      class PasswordResetView(GenericAPIView):
      """
      Calls Django Auth PasswordResetForm save method.

      Accepts the following POST parameters: email
      Returns the success/fail message.
      """
      serializer_class = PasswordResetSerializer
      permission_classes = (AllowAny,)

      def post(self, request, *args, **kwargs):
      # Create a serializer with request.data
      serializer = self.get_serializer(data=request.data)
      serializer.is_valid(raise_exception=True)

      serializer.save() # <----- Code from above (TokenGenerator) will be called inside this .save() method
      # Return the success message with OK HTTP status
      return Response(
      {"detail": _("Password reset e-mail has been sent.")},
      status=status.HTTP_200_OK
      )


      As you can see, the Tokengenerator will return a uidb36 with the token.
      It also assumes a uidb36 when the user would confirm the password-reset.
      A generated token (for example the full link in the generated mail) would look like this:



      http://localhost:8000/accounts/password/reset/key/16-52h-42b222e6dc30690b2e91/


      Where 16 is the user id in base 36 (uidb36), I do not yet know what 52h means, but I assume, the third part of the token is the token itself (42b222e6dc30690b2e91)



      Step 2: Send the token to the backend (aka "User clicks link")



      I am stuck here.
      The API-Endpoints of the Rest-Auth-Framework say:




      /rest-auth/password/reset/confirm/ (POST)
      uid
      token
      new_password1
      new_password2




      And when I send an object e.g:



      {
      uid: '16', // TODO maybe I have to convert it to base10...
      token: '42b222e6dc30690b2e91',
      new_password1: 'test123A$',
      new_password2: 'test123A$'
      }


      via my api to http://localhost:8000/api/v1/auth/reset-password/ with the object above in the body of an axios-post request, my CustomPasswordResetConfirmView is triggered like expected, which is also just a Subclass of PasswordResetConfirmView from rest_auth, so this code is executed:



      # project/.venv/Lib/site-packages/rest_auth/views.py
      class PasswordResetConfirmView(GenericAPIView):
      """
      Password reset e-mail link is confirmed, therefore
      this resets the user's password.

      Accepts the following POST parameters: token, uid,
      new_password1, new_password2
      Returns the success/fail message.
      """
      serializer_class = PasswordResetConfirmSerializer
      permission_classes = (AllowAny,)

      @sensitive_post_parameters_m
      def dispatch(self, *args, **kwargs):
      return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)

      def post(self, request, *args, **kwargs):
      serializer = self.get_serializer(data=request.data)
      serializer.is_valid(raise_exception=True)
      serializer.save()
      return Response(
      {"detail": _("Password has been reset with the new password.")}
      )


      The line serializer.is_valid(raise_exception=True) will call run_validation of the Serializer(BaseSerializer) of the rest_framework.
      This will further use the PasswordResetConfirmSerializer of rest_auth:



      # project/.venv/Lib/site-packages/rest_auth/serializers.py
      class PasswordResetConfirmSerializer(serializers.Serializer):
      """
      Serializer for requesting a password reset e-mail.
      """
      new_password1 = serializers.CharField(max_length=128)
      new_password2 = serializers.CharField(max_length=128)
      uid = serializers.CharField()
      token = serializers.CharField()

      set_password_form_class = SetPasswordForm

      def custom_validation(self, attrs):
      pass

      def validate(self, attrs):
      self._errors = {}

      # Decode the uidb64 to uid to get User object
      try:
      uid = force_text(uid_decoder(attrs['uid']))
      self.user = UserModel._default_manager.get(pk=uid)
      except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
      raise ValidationError({'uid': ['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)
      if not default_token_generator.check_token(self.user, attrs['token']):
      raise ValidationError({'token': ['Invalid value']})

      return attrs


      And as you can finally see, this class is expecting a uidb64 instead of a uidb36 for the user id, and I do not even want to know whether the token-format is anyhow matching what is expected here.



      I really cannot find good documentation about how to setup rest_auth properly for the full password-reset process: I got the email working, but for me it seems, rest_auth would generate a wrong token/reset-link for what it is actually expecting back from the user.



      Summary



      I believe, the password-reset-confirmation process is ending in the correct backend-code, while the email/token-generation is messed up.



      All I want is to retrieve a uid and a token which I can send back to django rest-auth in order to let users reset their passwords.
      Currently, it seems that these uids and tokens are created by one library and consumed by another library which both expect and create different formats of tokens and uids?



      Thanks in advance!



      Full settings.py



      Here is my full settings.py:



      # project/vuedj/settings.py
      BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
      PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
      SECRET_KEY = persisted_settings.SECRET_KEY
      DEBUG = True
      ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
      CORS_ORIGIN_ALLOW_ALL = True
      CORS_URLS_REGEX = r'^/api/.*$'
      CORS_ALLOW_CREDENTIALS = True

      # Application definition

      INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      'django.contrib.sites',
      'rest_framework',
      'rest_framework.authtoken',
      'corsheaders',
      'allauth',
      'allauth.account',
      'allauth.socialaccount',
      'allauth.socialaccount.providers.github',
      'rest_auth',
      'rest_auth.registration',
      'sceneries',
      'accounts',
      'api',
      'app',
      ]

      EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
      EMAIL_FILE_PATH = 'app-messages'
      SITE_ID = 1

      AUTH_USER_MODEL = 'accounts.User'
      ACCOUNT_USER_MODEL_USERNAME_FIELD = 'username'
      ACCOUNT_AUTHENTICATION_METHOD = 'username_email'

      ACCOUNT_EMAIL_REQUIRED = True
      ACCOUNT_EMAIL_VERIFICATION = 'none'
      ACCOUNT_UNIQUE_EMAIL = True
      ACCOUNT_USERNAME_REQUIRED = True
      ACCOUNT_USER_EMAIL_FIELD = 'email'
      ACCOUNT_LOGOUT_ON_GET = True
      ACCOUNT_FORMS = {"login": "accounts.forms.UserLoginForm"}
      LOGIN_REDIRECT_URL = 'home'
      LOGIN_URL = 'api/v1/accounts/login/'

      CSRF_COOKIE_NAME = "csrftoken"

      REST_AUTH_SERIALIZERS = {
      "USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
      "LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
      "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
      }

      REST_AUTH_REGISTER_SERIALIZERS = {
      "REGISTER_SERIALIZER": "accounts.serializers.CustomRegisterSerializer",
      }

      # Following is added to enable registration with email instead of username
      AUTHENTICATION_BACKENDS = (
      # Needed to login by username in Django admin, regardless of `allauth`
      "django.contrib.auth.backends.ModelBackend",
      # `allauth` specific authentication methods, such as login by e-mail
      "allauth.account.auth_backends.AuthenticationBackend",
      )

      MIDDLEWARE = [
      'corsheaders.middleware.CorsMiddleware',
      'django.middleware.security.SecurityMiddleware',
      'django.contrib.sessions.middleware.SessionMiddleware',
      'django.middleware.common.CommonMiddleware',
      'django.middleware.csrf.CsrfViewMiddleware',
      'django.contrib.auth.middleware.AuthenticationMiddleware',
      'django.contrib.messages.middleware.MessageMiddleware',
      'django.middleware.clickjacking.XFrameOptionsMiddleware',
      ]

      ROOT_URLCONF = 'vuedj.urls'

      TEMPLATES = [
      {
      'BACKEND': 'django.template.backends.django.DjangoTemplates',
      'DIRS': [
      'templates/',
      'templates/emails/'
      ],
      'APP_DIRS': True,
      'OPTIONS': {
      'context_processors': [
      'django.template.context_processors.debug',
      'django.template.context_processors.request',
      'django.contrib.auth.context_processors.auth',
      'django.contrib.messages.context_processors.messages',
      ],
      },
      },
      ]

      WSGI_APPLICATION = 'vuedj.wsgi.application'

      try:
      DATABASES = persisted_settings.DATABASES
      except AttributeError:
      DATABASES = {
      'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
      }
      }

      REST_FRAMEWORK = {
      'DEFAULT_AUTHENTICATION_CLASSES': [
      'rest_framework.authentication.TokenAuthentication',
      ],
      'DEFAULT_PERMISSION_CLASSES': [
      'rest_framework.permissions.IsAuthenticated',
      ]
      }

      AUTH_PASSWORD_VALIDATORS = [
      {
      'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
      },
      {
      'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
      },
      {
      'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
      },
      {
      'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
      },
      ]

      LANGUAGE_CODE = 'en-us'
      TIME_ZONE = 'UTC'
      USE_I18N = True
      USE_L10N = True
      USE_TZ = True

      STATICFILES_DIRS = (
      os.path.join(BASE_DIR, 'static'),
      )
      STATIC_ROOT = os.path.join(BASE_DIR, '../staticfiles/static')
      MEDIA_ROOT = os.path.join(BASE_DIR, '../staticfiles/mediafiles')
      STATIC_URL = '/static/'
      MEDIA_URL = '/media/'

      TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

      NOSE_ARGS = [
      '--with-coverage',
      '--cover-package=app', # For multiple apps use '--cover-package=foo, bar'
      ]









      share|improve this question















      I am completely confused by the django middleware available:



      I simply want to get password-reset (and later password-change) functionality running, using django with rest_auth on the backend and Vue on the frontend.



      Step 1: Requesting the Reset-Link via Mail



      Views



      So far I have made a CustomPasswordResetView:



      # project/accounts/views.py
      from rest_auth.views import PasswordResetView

      class CustomPasswordResetView(PasswordResetView):
      pass


      Serializers



      and a CustomPasswordResetSerializer:



      # project/accounts/serializers.py
      from rest_auth.serializers import PasswordResetSerializer

      class CustomPasswordResetSerializer(PasswordResetSerializer):
      email = serializers.EmailField()
      password_reset_form_class = ResetPasswordForm

      def validate_email(self, value):
      # Create PasswordResetForm with the serializer
      self.reset_form = self.password_reset_form_class(data=self.initial_data)
      if not self.reset_form.is_valid():
      raise serializers.ValidationError(self.reset_form.errors)

      ###### FILTER YOUR USER MODEL ######
      if not get_user_model().objects.filter(email=value).exists():
      raise serializers.ValidationError(_('Invalid e-mail address'))

      return value

      def save(self):
      request = self.context.get('request')
      # Set some values to trigger the send_email method.
      opts = {
      'use_https': request.is_secure(),
      'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
      'request': request,
      }
      opts.update(self.get_email_options())
      self.reset_form.save(**opts)


      Settings.py



      In the settings.py I have these fields, which seem relevant to me for my problem:



      # project/vuedj/settings.py
      REST_AUTH_SERIALIZERS = {
      "USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
      "LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
      "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
      }


      (The complete settings.py is attached at the bottom)



      URL patterns



      My urls already catch my API request in order to send the Password-Reset Email:



      # project/vuedj/urls.py
      urlpatterns = [
      path('admin/', admin.site.urls),
      path('api/v1/', include('api.urls')),
      path('accounts/', include('allauth.urls')),
      path('', api_views.index, name='home')
      ]


      # project/api/urls.py
      urlpatterns = [
      path('auth/', include('accounts.urls')),
      # other paths...
      ]


      # project/accounts/urls.py
      urlpatterns = [
      path('', acc_views.UserListView.as_view(), name='user-list'),
      path('login/', acc_views.UserLoginView.as_view(), name='login'),
      path('logout/', acc_views.UserLogoutView.as_view(), name='logout'),
      path('register/', acc_views.CustomRegisterView.as_view(), name='register'),
      path('reset-password/', acc_views.CustomPasswordResetView.as_view(), name='reset-password'),
      path('reset-password-confirm/', acc_views.CustomPasswordResetConfirmView.as_view(), name='reset-password-confirm'),
      path('<int:pk>/', acc_views.UserDetailView.as_view(), name='user-detail')
      ]


      Email with PW-Reset Token Generator



      The CustomPasswordReset view will eventually generate a nice email with a nice pw-reset link. The link is valid, as I click it, I can reset the password through the allauth templates perfectly.



      This code is used by rest-auth (indirectly) to generate the reset-token:



      # project/.venv/Lib/site-packages/allauth/account/forms.py
      def save(self, request, **kwargs):
      current_site = get_current_site(request)
      email = self.cleaned_data["email"]
      token_generator = kwargs.get("token_generator",
      default_token_generator)

      for user in self.users:

      temp_key = token_generator.make_token(user)

      # save it to the password reset model
      # password_reset = PasswordReset(user=user, temp_key=temp_key)
      # password_reset.save()

      # send the password reset email
      path = reverse("account_reset_password_from_key",
      kwargs=dict(uidb36=user_pk_to_url_str(user),
      key=temp_key))
      url = build_absolute_uri(
      request, path)

      context = {"current_site": current_site,
      "user": user,
      "password_reset_url": url,
      "request": request}

      if app_settings.AUTHENTICATION_METHOD
      != AuthenticationMethod.EMAIL:
      context['username'] = user_username(user)
      get_adapter(request).send_mail(
      'account/email/password_reset_key',
      email,
      context)
      return self.cleaned_data["email"]


      This PasswordResetTokenGenerator is used in the code above:



      # project/.venv/Lib/site-packages/django/contrib/auth/tokens.py
      class PasswordResetTokenGenerator:
      """
      Strategy object used to generate and check tokens for the password
      reset mechanism.
      """
      key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
      secret = settings.SECRET_KEY

      def make_token(self, user):
      """
      Return a token that can be used once to do a password reset
      for the given user.
      """
      return self._make_token_with_timestamp(user, self._num_days(self._today()))

      def check_token(self, user, token):
      """
      Check that a password reset token is correct for a given user.
      """
      if not (user and token):
      return False
      # Parse the token
      try:
      ts_b36, hash = token.split("-")
      except ValueError:
      return False

      try:
      ts = base36_to_int(ts_b36)
      except ValueError:
      return False

      # Check that the timestamp/uid has not been tampered with
      if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
      return False

      # Check the timestamp is within limit. Timestamps are rounded to
      # midnight (server time) providing a resolution of only 1 day. If a
      # link is generated 5 minutes before midnight and used 6 minutes later,
      # that counts as 1 day. Therefore, PASSWORD_RESET_TIMEOUT_DAYS = 1 means
      # "at least 1 day, could be up to 2."
      if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
      return False

      return True


      The classes above will be called by the rest_auth PasswordResetView:



      # project/.venv/Lib/site-packages/rest_auth/views.py
      class PasswordResetView(GenericAPIView):
      """
      Calls Django Auth PasswordResetForm save method.

      Accepts the following POST parameters: email
      Returns the success/fail message.
      """
      serializer_class = PasswordResetSerializer
      permission_classes = (AllowAny,)

      def post(self, request, *args, **kwargs):
      # Create a serializer with request.data
      serializer = self.get_serializer(data=request.data)
      serializer.is_valid(raise_exception=True)

      serializer.save() # <----- Code from above (TokenGenerator) will be called inside this .save() method
      # Return the success message with OK HTTP status
      return Response(
      {"detail": _("Password reset e-mail has been sent.")},
      status=status.HTTP_200_OK
      )


      As you can see, the Tokengenerator will return a uidb36 with the token.
      It also assumes a uidb36 when the user would confirm the password-reset.
      A generated token (for example the full link in the generated mail) would look like this:



      http://localhost:8000/accounts/password/reset/key/16-52h-42b222e6dc30690b2e91/


      Where 16 is the user id in base 36 (uidb36), I do not yet know what 52h means, but I assume, the third part of the token is the token itself (42b222e6dc30690b2e91)



      Step 2: Send the token to the backend (aka "User clicks link")



      I am stuck here.
      The API-Endpoints of the Rest-Auth-Framework say:




      /rest-auth/password/reset/confirm/ (POST)
      uid
      token
      new_password1
      new_password2




      And when I send an object e.g:



      {
      uid: '16', // TODO maybe I have to convert it to base10...
      token: '42b222e6dc30690b2e91',
      new_password1: 'test123A$',
      new_password2: 'test123A$'
      }


      via my api to http://localhost:8000/api/v1/auth/reset-password/ with the object above in the body of an axios-post request, my CustomPasswordResetConfirmView is triggered like expected, which is also just a Subclass of PasswordResetConfirmView from rest_auth, so this code is executed:



      # project/.venv/Lib/site-packages/rest_auth/views.py
      class PasswordResetConfirmView(GenericAPIView):
      """
      Password reset e-mail link is confirmed, therefore
      this resets the user's password.

      Accepts the following POST parameters: token, uid,
      new_password1, new_password2
      Returns the success/fail message.
      """
      serializer_class = PasswordResetConfirmSerializer
      permission_classes = (AllowAny,)

      @sensitive_post_parameters_m
      def dispatch(self, *args, **kwargs):
      return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)

      def post(self, request, *args, **kwargs):
      serializer = self.get_serializer(data=request.data)
      serializer.is_valid(raise_exception=True)
      serializer.save()
      return Response(
      {"detail": _("Password has been reset with the new password.")}
      )


      The line serializer.is_valid(raise_exception=True) will call run_validation of the Serializer(BaseSerializer) of the rest_framework.
      This will further use the PasswordResetConfirmSerializer of rest_auth:



      # project/.venv/Lib/site-packages/rest_auth/serializers.py
      class PasswordResetConfirmSerializer(serializers.Serializer):
      """
      Serializer for requesting a password reset e-mail.
      """
      new_password1 = serializers.CharField(max_length=128)
      new_password2 = serializers.CharField(max_length=128)
      uid = serializers.CharField()
      token = serializers.CharField()

      set_password_form_class = SetPasswordForm

      def custom_validation(self, attrs):
      pass

      def validate(self, attrs):
      self._errors = {}

      # Decode the uidb64 to uid to get User object
      try:
      uid = force_text(uid_decoder(attrs['uid']))
      self.user = UserModel._default_manager.get(pk=uid)
      except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
      raise ValidationError({'uid': ['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)
      if not default_token_generator.check_token(self.user, attrs['token']):
      raise ValidationError({'token': ['Invalid value']})

      return attrs


      And as you can finally see, this class is expecting a uidb64 instead of a uidb36 for the user id, and I do not even want to know whether the token-format is anyhow matching what is expected here.



      I really cannot find good documentation about how to setup rest_auth properly for the full password-reset process: I got the email working, but for me it seems, rest_auth would generate a wrong token/reset-link for what it is actually expecting back from the user.



      Summary



      I believe, the password-reset-confirmation process is ending in the correct backend-code, while the email/token-generation is messed up.



      All I want is to retrieve a uid and a token which I can send back to django rest-auth in order to let users reset their passwords.
      Currently, it seems that these uids and tokens are created by one library and consumed by another library which both expect and create different formats of tokens and uids?



      Thanks in advance!



      Full settings.py



      Here is my full settings.py:



      # project/vuedj/settings.py
      BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
      PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
      SECRET_KEY = persisted_settings.SECRET_KEY
      DEBUG = True
      ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
      CORS_ORIGIN_ALLOW_ALL = True
      CORS_URLS_REGEX = r'^/api/.*$'
      CORS_ALLOW_CREDENTIALS = True

      # Application definition

      INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      'django.contrib.sites',
      'rest_framework',
      'rest_framework.authtoken',
      'corsheaders',
      'allauth',
      'allauth.account',
      'allauth.socialaccount',
      'allauth.socialaccount.providers.github',
      'rest_auth',
      'rest_auth.registration',
      'sceneries',
      'accounts',
      'api',
      'app',
      ]

      EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
      EMAIL_FILE_PATH = 'app-messages'
      SITE_ID = 1

      AUTH_USER_MODEL = 'accounts.User'
      ACCOUNT_USER_MODEL_USERNAME_FIELD = 'username'
      ACCOUNT_AUTHENTICATION_METHOD = 'username_email'

      ACCOUNT_EMAIL_REQUIRED = True
      ACCOUNT_EMAIL_VERIFICATION = 'none'
      ACCOUNT_UNIQUE_EMAIL = True
      ACCOUNT_USERNAME_REQUIRED = True
      ACCOUNT_USER_EMAIL_FIELD = 'email'
      ACCOUNT_LOGOUT_ON_GET = True
      ACCOUNT_FORMS = {"login": "accounts.forms.UserLoginForm"}
      LOGIN_REDIRECT_URL = 'home'
      LOGIN_URL = 'api/v1/accounts/login/'

      CSRF_COOKIE_NAME = "csrftoken"

      REST_AUTH_SERIALIZERS = {
      "USER_DETAILS_SERIALIZER": "accounts.serializers.CustomUserDetailsSerializer",
      "LOGIN_SERIALIZER": "accounts.serializers.CustomUserLoginSerializer",
      "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer"
      }

      REST_AUTH_REGISTER_SERIALIZERS = {
      "REGISTER_SERIALIZER": "accounts.serializers.CustomRegisterSerializer",
      }

      # Following is added to enable registration with email instead of username
      AUTHENTICATION_BACKENDS = (
      # Needed to login by username in Django admin, regardless of `allauth`
      "django.contrib.auth.backends.ModelBackend",
      # `allauth` specific authentication methods, such as login by e-mail
      "allauth.account.auth_backends.AuthenticationBackend",
      )

      MIDDLEWARE = [
      'corsheaders.middleware.CorsMiddleware',
      'django.middleware.security.SecurityMiddleware',
      'django.contrib.sessions.middleware.SessionMiddleware',
      'django.middleware.common.CommonMiddleware',
      'django.middleware.csrf.CsrfViewMiddleware',
      'django.contrib.auth.middleware.AuthenticationMiddleware',
      'django.contrib.messages.middleware.MessageMiddleware',
      'django.middleware.clickjacking.XFrameOptionsMiddleware',
      ]

      ROOT_URLCONF = 'vuedj.urls'

      TEMPLATES = [
      {
      'BACKEND': 'django.template.backends.django.DjangoTemplates',
      'DIRS': [
      'templates/',
      'templates/emails/'
      ],
      'APP_DIRS': True,
      'OPTIONS': {
      'context_processors': [
      'django.template.context_processors.debug',
      'django.template.context_processors.request',
      'django.contrib.auth.context_processors.auth',
      'django.contrib.messages.context_processors.messages',
      ],
      },
      },
      ]

      WSGI_APPLICATION = 'vuedj.wsgi.application'

      try:
      DATABASES = persisted_settings.DATABASES
      except AttributeError:
      DATABASES = {
      'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
      }
      }

      REST_FRAMEWORK = {
      'DEFAULT_AUTHENTICATION_CLASSES': [
      'rest_framework.authentication.TokenAuthentication',
      ],
      'DEFAULT_PERMISSION_CLASSES': [
      'rest_framework.permissions.IsAuthenticated',
      ]
      }

      AUTH_PASSWORD_VALIDATORS = [
      {
      'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
      },
      {
      'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
      },
      {
      'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
      },
      {
      'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
      },
      ]

      LANGUAGE_CODE = 'en-us'
      TIME_ZONE = 'UTC'
      USE_I18N = True
      USE_L10N = True
      USE_TZ = True

      STATICFILES_DIRS = (
      os.path.join(BASE_DIR, 'static'),
      )
      STATIC_ROOT = os.path.join(BASE_DIR, '../staticfiles/static')
      MEDIA_ROOT = os.path.join(BASE_DIR, '../staticfiles/mediafiles')
      STATIC_URL = '/static/'
      MEDIA_URL = '/media/'

      TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

      NOSE_ARGS = [
      '--with-coverage',
      '--cover-package=app', # For multiple apps use '--cover-package=foo, bar'
      ]






      python django django-rest-framework django-allauth django-rest-auth






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 2 days ago

























      asked 2 days ago









      ElectRocnic

      316211




      316211





























          active

          oldest

          votes











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53945056%2fdjango-rest-auth-password-reset-tokengenerator-generates-b36-uid-on-request-wa%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown






























          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53945056%2fdjango-rest-auth-password-reset-tokengenerator-generates-b36-uid-on-request-wa%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          generate and download xml file after input submit (php and mysql) - JPK

          Angular Downloading a file using contenturl with Basic Authentication

          Can't read property showImagePicker of undefined in react native iOS