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?