Unable to use password reset via auth_views.PasswordResetConfirmView to login

Hi there,

I am using a custom User Model and Django’s Authentication to reset the user password with a slight modification. Instead of directly resetting password, I am sending an email to user (as I am in production mode so, sending email directly at CLI/Console) containing password reset link, doing all this using . Upon opening that link, user gets a form to submit new password, after submitting the password user gets a page stating that password has been reset successfully and a link to go to login page. On to this point everything is working perfectly fine. But when I use that newly set password to login the user, I am unable to login. I am using Argon2 Password Hashing Algorithms to store passwords.

My settings.py is:

PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.ScryptPasswordHasher",
]

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

AUTH_USER_MODEL = "accounts.User"

My models.py is:

from ast import arguments

from django.db import models
from django.contrib.auth.models import (
    AbstractBaseUser,
    BaseUserManager,
    PermissionsMixin,
)


class UserManager(BaseUserManager):
    def _create_user(
        self,
        login: str,
        email: str,
        password: str,
        is_staff: bool,
        is_admin: bool,
        **extra_fields: arguments
    ) -> dict:
        if not email:
            raise ValueError("Users must have an email address")
        if not login:
            raise ValueError(
                "Users must have a login name \
                (username) to use the system"
            )

        now = timezone.now()
        email = self.normalize_email(email)

        user = self.model(
            login=login,
            email=email,
            is_staff=is_staff,
            is_active=True,
            is_superuser=is_admin,
            last_login=now,
            date_joined=now,
            **extra_fields
        )

        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_user(
        self, login: str, email: str, password: str, **extra_fields: arguments
    ) -> dict:
        user = self._create_user(login, email, password, False, False, **extra_fields)

        return user

    def create_superuser(
        self, login: str, email: str, password: str, **extra_fields: arguments
    ) -> dict:
        user = self._create_user(login, email, password, True, True, **extra_fields)

        return user


class User(AbstractBaseUser, PermissionsMixin):
    login = models.CharField(max_length=254, unique=True)
    email = models.EmailField(max_length=254, unique=True)
    first_name = models.CharField(max_length=254, null=True, blank=True)
    last_name = models.CharField(max_length=254, null=True, blank=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    last_login = models.DateTimeField(null=True, blank=True)
    date_joined = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = "login"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = ["email"]

    objects = UserManager()

    def get_absolute_url(self) -> str:
        return "/users/%i/" % (self.pk)

My admin.py is:

from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

class UserAdmin(BaseUserAdmin):
    fieldsets = (
        (None, {"fields": (
                    "login",
                    "email",
                    "password",
                    "first_name",
                    "last_name",
                    "last_login",
            )},
        ),
        ("Permissions", {"fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
            )}),
    )

    add_fieldsets = ((None,{
                "classes": ("wide",),
                "fields": (
                    "login",
                    "email",
                    "password1",
                    "password2"
        )}),
    )

    list_display = (
        "login",
        "email",
        "first_name",
        "last_name",
        "is_superuser",
        "last_login",
    )

    list_filter = (
        "is_staff",
        "is_superuser",
        "is_active",
        "groups",
    )

    search_fields = (
        "email",
        "login",
    )

    ordering = (
        "email",
        "login",
    )

    filter_horizontal = (
        "groups",
        "user_permissions",
    )

My forms.py is:

class LoginForm(AuthenticationForm):
    username = forms.CharField(
        max_length=254,
        required=True,
        widget=forms.TextInput(
            attrs={"class": "form-control", "placeholder": "Your login name"}
        ),
    )
    password = forms.CharField(
        max_length=254,
        required=True,
        widget=forms.PasswordInput(
            attrs={"class": "form-control", "placeholder": "Your password"}
        ),
    )

My urls.py is:

from django.contrib.auth import views as auth_views

from .views import (
    Login,
    LoginSuccess,
    LoginError,
    Logout,
    PasswordReset
)

urlpatterns = [
    path("password-reset/", PasswordReset.as_view(), name="password_reset"),  # Password Reset Page
    path(
        "password-reset/<uidb64>/<token>/",
        auth_views.PasswordResetConfirmView.as_view(
            template_name="accounts/password-reset-confirm.html"
        ),
        name="password_reset_confirm",
    ),  # Password Reset Confirm Page
    path("login/", Login.as_view(), name="login"),  # Login Page
]

My views.py is:

from django.contrib.auth import login, authenticate
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.forms import PasswordResetForm
from django.core.mail import send_mail, BadHeaderError
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.views import View

from .forms import LoginForm


class PasswordReset(View):
    def get(self, request: HttpRequest) -> HttpResponse:
        password_reset_form = PasswordResetForm()

        context = {
            "title": "Reset Your Password",
            "password_reset_form": password_reset_form
        }

        return render(request, "accounts/password-reset.html", context)

    def post(self, request):
        password_reset_form = PasswordResetForm(request.POST)
        
        if password_reset_form.is_valid():
            email_address = password_reset_form.cleaned_data['email']
            user = User.objects.get(email=email_address)
            
            if user:
                subject = "Password Reset Requested"
                email_template_name = "accounts/password-reset-email.txt"
                
                email_details = {
                    "email": email_address,
                    'domain': '127.0.0.1:8000',
                    'site_name': 'PricingMeister',
                    "uid": urlsafe_base64_encode(force_bytes(user.pk)),
                    "user": user,
                    'token': default_token_generator.make_token(user),
                    'protocol': 'http',
                }
                
                email = render_to_string(email_template_name, email_details)
                
                try:
                    send_mail(subject, email, "admin@pricingmeister.com" , [user.email], fail_silently=False)
                except BadHeaderError:
                    return HttpResponse('Invalid header found.')
                
                return redirect ("/accounts/password-reset/done/")
            else:
                return HttpResponse("You are not authorized to view this page.")
        else:
            print("Form is not valid")


class Login(View):
    def get(self, request: HttpRequest) -> HttpResponse:
        login_form = LoginForm()

        context = {"title": "Login Form", "login_form": login_form}

        return render(request, "accounts/login.html", context)

    def post(self, request: HttpRequest) -> HttpResponseRedirect:
        login_form = LoginForm(request, data=request.POST)

        if login_form.is_valid():
            username = login_form.cleaned_data.get("username")
            password = login_form.cleaned_data.get("password")
            user = authenticate(username=username, password=password)
            print("This is a", user)

            if user is not None:
                login(request, user)
                messages.info(request, f"You are now logged in as {username}.")

                return redirect("/accounts/success")
            else:
                messages.error(request, "Invalid username or password.")
                return redirect("/accounts/error")
        else:
            messages.error(request, "Invalid username or password.")
            return redirect("/accounts/error")

My password-reset.html template is:

<h2>Reset Password</h2>
<hr>
<p>Enter your email address below, and we will send an email containing instructions for resetting your password.</p>
<form method="POST">
    {% csrf_token %}
    {{ password_reset_form }}                    
    <button type="submit">Send email</button>
</form>

My password-reset-confirm.html emplate is:

<h2>Password Reset Confirm</h2>
<hr>
<p>Please enter your new password.</p>
<form method="POST">
    {% csrf_token %}
    {{ form }}                    
    <button type="submit">Reset</button>
</form>

My password-reset-email.txt template is:

{% autoescape off %}
Hi,

We received a request to reset the password for your account for this email address. To initiate the password reset process for your account, click the link below.

{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

This link can only be used once and wil expire after 1 hour. If you need to reset your password again, please visit {{ protocol }}://{{domain}} and request another reset.

If you did not make this request, you can simply ignore this email.

Sincerely,
WebTeam

{% endautoescape %}

Any one please help me why I am unable to use the newly reset password?

@KenWhitesell can you help me here?

First, I’m going to ask you to please stop appending these targeted requests to me on your questions. I generally see every message posted, and I answer the ones where I think my answer may assist the person asking. And, it may actually discourage someone else from seeing the question and providing an answer.

Now, having said all that, have you verified by visual inspection of your database that the password field is being set correctly?

Have you verified that the password works by checking it in the shell?
e.g.

from django.contrib.auth import authenticate
user = authenticate(username="some username", password="their password")
print(user)

If this prints the user, then your password reset function is working correctly. If it doesn’t, then that’s what you need to look at more closely.

Apologies for the tags, I will take care of it from now.

Secondly, I am using the default Django Authentication PasswordResetConfirmView and PasswordResetCompleteView. How come is it possible that there might be problem in password reset function?

When chasing down a problem of an unknown nature, I don’t assume anything I can’t see. If nothing is apparent from what I do see, then I start with the basics - is everything ok from my starting point?

In your Login view you have:

What gets printed at this line?

If it’s not the user trying to be authenticated, then there’s something wrong with the password being stored.

If you’re not seeing that line at all, then the form is failing the is_valid test. You might want to print all the errors that are set in that case to see why it’s failing.

1 Like

Actually nothing is printing at all. So you are right, may be form is not getting valid. Let me look into that. By the way, how do you have such a pro technical and analytical mind to catch where the problem might be? In other words, can you recommend me any natural mind exercises or any practice that makes me more obervant and technical?

It probably comes from having done this for more than 45 years, starting on equipment that wasn’t always reliable. Also, from working frequently at a level where you had to “think like the computer” and mentally walk through all the steps the computer was taking to figure out what was going on.
If I were to make any recommendations, it would be to find a way to learn assembly language programming on a primative CPU. Once you develop a knowledge base of what’s really happening at the lowest levels, you start to get an appreciation for the patterns that exist across all layers of the software stack.

2 Likes

I don’t know what happened, I did not even change anything and I just created a new user, reset his password and then tried to login with newly set password and I was able to log in, for the test purpose I created 2 more users, tried o log in them before resetting their passwords and they logged in and then I reset their password and tried to login them again and they did log in.

So what could have been the issue that earlier I was getting invalid form (I tested it by putting print("Invalid") statement before return statement in the else block of if form.is_valid() in the LoginView)?

Unfortunately, there’s no way for me to determine that from the information available to me.

Does your process work with the original users you had created? If not, then it would have been something in how those users were created. But at this point anything would be conjecture, and I’m not sure there’s a whole lot of value in trying to track it down at this point.

Yes, with the new users. But the new users are created the same way I created the old users i.e. from Django Administration Interface.

That’s why I was asking if your password reset process works now with those previously-created users.

If it doesn’t, then you are doing something different now than before.