Search code examples
pythondjangodjango-usersdjango-sites

Django (v3.1) PASSWORD RESET LINK: AssertionError at line 260 of django/contrib/auth/views.py assert 'uidb64' in kwargs and 'token' in kwargs


I am having trouble getting past an Assertion error when clicking the password reset link in Django 3.1.2. I have Django running within a Docker container.

The emailed link appears to be correct, as it uses the proper domain 'localhost'. However, after clicking the link, the error message replaces the domain 'localhost' with 'Django:8000'.

HTML FILES

password_reset_email.html

{% load i18n %}{% autoescape off %}
{% trans "You're receiving this e-mail because you requested a password reset" %}
{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.

{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb36=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The Super Awesome team{% endblocktrans %}

{% endautoescape %}

password_reset_confirm.html

{% extends "admin/base_site.html" %}
{% load i18n %}

{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset confirmation' %}</div>{% endblock %}

{% block title %}{% trans 'Password reset' %}{% endblock %}

{% block content %}

{% if validlink %}

<h1>{% trans 'Enter new password' %}</h1>

<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>

<form action="" method="post">
{{ form.new_password1.errors }}
<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
{{ form.new_password2.errors }}
<p class="aligned wide"><label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}</p>
<p><input type="submit" value="{% trans 'Change my password' %}" /></p>
</form>

{% else %}

<h1>{% trans 'Password reset unsuccessful' %}</h1>

<p>{% trans "The password reset link was invalid, possibly because it has already been used.  Please request a new password reset." %}</p>

{% endif %}

{% endblock %}

URL SCRIPT:

urls.py

auth_patterns = 
[
    path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
    re_path(r'^password_reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),

]

Full error Traceback:

Environment:


Request Method: GET
Request URL: https://django:8000/accounts/password_reset/mjq5-ax0zta-7bba9cbeb8c411bfd0dcdee5d5ae10a6/

Django Version: 3.1.2
Python Version: 3.8.10
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.messages',
 'django.contrib.sessions',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'django.contrib.sites',
 'formtools',
 'django_extensions',
 'template_utils',
 'countries',
 'pagination',
 'seeker',
 )
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'pagination.middleware.PaginationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'disc.middleware.LowerCaseMiddleware')



Traceback (most recent call last):
  File "/root/.local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/root/.local/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/contrib/auth/views.py", line 260, in dispatch
    assert 'uidb64' in kwargs and 'token' in kwargs

Exception Type: AssertionError at /accounts/password_reset/mjq5-ax0zta-7bba9cbeb8c411bfd0dcdee5d5ae10a6/
Exception Value: 

docker-compose.yml

version: '3'

services:
  django:
    shm_size: 1g
    build:
      context: .
      dockerfile: docker/django.dockerfile
    env_file:
      - .env
    expose:
      - "8000"
    ports:
      - "10080:8000"
    volumes:
      - ./run/django/data:/opt/data
      - ./run/django/settings:/opt/settings
      - ./media/:/media
      - ./run/django/logs:/opt/logs
      - /var/run/docker.sock:/var/run/docker.sock
    healthcheck:
      test: ["CMD", "curl", "-sf", "localhost:8000", "-o", "/dev/null"]
      interval: 180s
      timeout: 10s
      retries: 3

  nginx:
    build:
      context: docker/nginx
      dockerfile: nginx.dockerfile
    shm_size: 512m
    environment:
      - SSL_CERTIFICATE=${SSL_CERTIFICATE}
      - DEPLOYMENT_DNS="${DEPLOYMENT_DNS:-localhost}"
    ports:
      - "${DISC_HTTP_PORT:-80}:80"
      - "${DISC_HTTPS_PORT:-443}:443"
    volumes:
      - "${CREDENTIALS_PATH:-./run/nginx/credentials}:/credentials"
      - "${LETSENCRYPT_CONFIG_PATH:-./run/nginx/letsencrypt/config}:/etc/letsencrypt"
      - "${LETSENCRYPT_DATA_PATH:-./run/nginx/letsencrypt/data}:/data/letsencrypt"
      - ./static:/static
      - ./media/:/media
    healthcheck:
      test: ["CMD", "curl", "-sf", "--header", "Host: healthcheck.local", "localhost:80", "-o", "/dev/null"]
      interval: 180s
      timeout: 10s
      retries: 3
    depends_on:
      - django

Solution

  • This AssertionError results from the url pattern in the link for password_reset_confirm calling uidb36, while the line 260 in File "/root/.local/lib/python3.8/site-packages/django/contrib/auth/views.py", is specifically looking for a uidb64 encoding. The uidb36 encoding is a legacy fragment resulting from the age of this project that has been upgraded periodically throughout the years...

    > Line 260: assert 'uidb64' in kwargs and 'token' in kwargs

    > Corrected link to password_reset_confirm:
    re_path(r'^password_reset/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),

    Note, after changing the password reset confirm link to uidb64, as below, the corresponding change also has to be made in the email link: {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}