Reputation: 29767
I have the following model for storing previously used hashed passwords:
class PasswordHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
password = models.CharField(max_length=128, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
In the change password form I want check if the new password the user is changing to has not been used the past 5 times.
Here is my form validation:
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
user_id = None
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
hashed_password = make_password(password1)
password_histories = PasswordHistory.objects.filter(
user=user,
password_hashed_password
)
if password_histories.exists():
raise forms.ValidationError('That password has already been used')
return password2
The problem is that the passwords are different every time, even when I attempt the same plain text password over and over again. Therefore:
if password_histories.exists():
Never returns true.
How can I compare past passwords if they are always different due to salt? Thanks
Upvotes: 2
Views: 2736
Reputation: 476584
The .set_password
function indeed does not return anything, it simply sets the password. Like you say however, the hashing is based on a (random) salt, and thus the hash will be different each time. Therefore you should use the .check_password(…)
function [Django-doc], to verify if it somehow matches a hashed variant:
from django.contrib.auth.hashers import check_password
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
password_histories = PasswordHistory.objects.filter(
user=user
)
for pw in password_histories:
if check_password(password2, pw.password):
raise forms.ValidationError('That password has already been used')
return password2
So if we found a hashed password that matches the given raw password, we can return the password
. If by the end of the for
loop, we did not find any such password, we can return password2
, otherwise we raise an errro.
Upvotes: 4