I don't know why the password is not hashing using Bcrypt. I think I am doing it right. I initilized Bcrypt correctly and I am using mongoengine. Everytime I look at the database it still shows the unencrypyed passwrod in text.
users/models.py
from app import db, bcrypt
class User(db.Document):
username = db.StringField(required=True)
first_name = db.StringField(required=True)
last_name = db.StringField(required=True)
email = db.EmailField(required=True)
password = db.StringField(required=True)
meta = {'collection': 'users'}
#property
def hash_password(self):
return self.password
#hash_password.setter
def set_hash_password(self, password):
self.password = bcrypt.generate_password_hash(password)
def verify_password(self, password):
return bcrypt.check_password_hash(self.password, password)
users/views.py
#userV.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignUpForm()
if form.validate_on_submit():
user = User(
first_name=form.first_name.data,
last_name=form.last_name.data,
username=form.username.data,
email=form.email.data,
password=form.password.data
).save()
flash('You can now login')
return render_template('user.html', variable="You can now login " + user.username)
return render_template('signup.html', form=form)
users/auth/forms.py
class SignUpForm(Form):
username = StringField('Username', validators=[
InputRequired(message="Username is required"),
Regexp('^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]$', 0, 'Usernames must have only letters, numbers or underscores')
])
first_name = StringField('First Name', validators=[
InputRequired(message="First name is required")
])
last_name = StringField('Last Name', validators=[
InputRequired(message="Last name is required")
])
email = StringField('Email Address', validators=[
InputRequired(message="Email is required"),
Email(message="This is not a valid email")
])
password = PasswordField('Password', validators=[
InputRequired(message="Password is required"),
Length(min=6, message="The password is not long enough")
])
accept_tos = BooleanField('Accept Terms of Service', validators=[
InputRequired(message="You have to accept the Terms of Service in order to use this site")
])
submit = SubmitField('Signup')
def validate(self):
if not Form.validate(self):
return False
if User.objects(username=self.username.data).first():
raise ValidationError('Username already in use')
if User.objects(email=self.email.data).first():
raise ValidationError('Email already registered')
return True
This is the outcome when I search mongodb shell. The password is not hashed.
{ "_id" : ObjectId("555df97deddd5543c360888a"), "username" : "FullMetal", "first_name" : "Edward", "last_name" : "Elric", "email" : "fullmetalalchemist#gmail.com", "password" : "equalexchange" }
The property is called hash_password not password. I don't see where hash_password is getting assigned (that's when its setter gets called). Also your setter method should have exactly the same name as the property itself, in this case hash_password not (set_hash_password). You can then do
user = User(hash_password=form.password.data)
Unfortunately, due to the way mongoengine.Document.__init__ works, you wont be able to use your field this way. You have two options to make it work:
Option 1: First create the User object without the password, then set the hash_password, then save
user = User(first_name=form.first_name.data,
last_name=form.last_name.data,
username=form.username.data,
email=form.email.data)
user.hash_password = form.password.data
user.save()
Option 2: Requires overriding the __init__ method for User
class User(db.Document):
def __init__(self, *args, **kwargs):
if 'hash_password' in kwargs:
self.hash_password = kwargs.pop('hash_password')
super(User, self).__init__(*args, **kwargs)
Now you can use User as you initially wanted:
user = User(first_name=form.first_name.data, hash_password=form.password.data)
Python #property decorator doesn't work with old-style classes. I made this demo - note the class inheriting from object, which makes it a new-style class. Have a look and modify this to suit your need
class User(object):
def __init__(self, username, first_name, last_name, email, password):
print "Initializing"
self.username = username
self.first_name = first_name
self.last_name = last_name
self.email = email
self.password = password
#property
def password(self):
print "getting password"
return self._password
#password.setter
def password(self, password):
print "Setting password"
self._password = bcrypt.generate_password_hash(password)
def verify_password(self, password):
return bcrypt.check_password_hash(self.password, password)
As I mentioned earlier, if all else fails, I'd solve this by performing the logic in my view. That's what I'd have done in the first place, tbh. Python favors expressiveness.
I have omitted the other parts
user = User(password=bcrypt.generate_password_hash(form.password.data))
And just removing the #property setter and getter in the User class.
Here is what I do in the same circumstances:
class User(db.Document):
...
password = db.StringField(max_length=255, required=True)
...
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password)
...
Then, in views.py I do the following:
user = User(..)
user.set_password(form.password.data)
user.save()
This way your logic stays within your model, but can be called easily from outside.
Related
I am using DRF and I have these pieces of code as models, register view and serializer
But anytime I signup a user the password does not hashed and I can't see to figure out why.
models.py
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
if not email:
raise ValueError("Users must have an email")
email = self.normalize_email(email).lower()
user = self.model(email=email, **kwargs)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
if not password:
raise ValueError("Password is required")
user = self.create_user(email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
role = models.CharField(max_length=255)
department = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_verified = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["first_name", "last_name", "role", "department"]
def __str__(self):
return self.email
serializers.py
class RegisterSerializer(serializers.ModelSerializer):
email = serializers.CharField(max_length=255)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField(max_length=255)
last_name = serializers.CharField(max_length=255)
role = serializers.CharField(max_length=255)
department = serializers.CharField(max_length=255)
class Meta:
model = User
fields = ["email", "password", "first_name", "last_name", "role", "department"]
def create(self, validated_data):
return User.objects.create(**validated_data)
def validate_email(self, value):
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("This email already exists!")
return value
views.py
class RegisterView(APIView):
serializer_class = RegisterSerializer
def post(self, request, *args):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
user = User.objects.get(email=user_data.get("email"))
return Response(user_data, status=status.HTTP_201_CREATED)
for some reason, which I don't know anytime a user is created the password is save in clear text. It does not hash the passwords. The superuser's password is however hashed because I created it with the command line but the api doesn't hash the password. I some help to fix this.
Instead of passing plain password you should use make_password method provided by django.
from django.contrib.auth.hashers import make_password
make_password(password, salt=None, hasher='default')
Creates a hashed password in the format used by this application. It takes one mandatory argument: the password in plain-text (string or bytes). Optionally, you can provide a salt and a hashing algorithm to use, if you don’t want to use the defaults (first entry of PASSWORD_HASHERS setting). See Included hashers for the algorithm name of each hasher. If the password argument is None, an unusable password is returned (one that will never be accepted by check_password()).
You can try something like this
hashed_pass = make_password(your_password_here, salt=None, hasher='default')
user = self.create_user(email, hashed_pass)
Source: https://docs.djangoproject.com/en/4.1/topics/auth/passwords/
Try this:
def create(self, validated_data):
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
return user
I found why it wasn't working. In the create method in the RegisterSerializer I did a return User.objects.create(**validated_data)
which saves on the request data in the database, including the password without hashing it first. The correct way is to be calling the create_user method instead, like so return User.objects.create_user(**validated_data) since that method has the set_password mechanism to hash the password.
Basically what I have done so far is create a registration page where the user makes their username and password, then that password is stored in as a hashed password (md5 hasher). The problem I am having is logging in. The user inputs their username and password then the password is authenticated by using authenticate() method in django. The problem is that authenticate() is returning None instead of matching the user and password in the database. I dont know if this affects anything but I am using PostgreSQL.
models.py
class MyAccountManager(BaseUserManager):
def create_user(self, email,username,first_name,password= None):
if not email:
raise ValueError('User must have an email address')
if not username:
raise ValueError('User must have a username')
if not first_name:
raise ValueError('User must have a first name')
user= self.model(
email=self.normalize_email(email),
username= username,
first_name= first_name
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, first_name, password):
user= self.create_user(
email= self.normalize_email(email),
username=username,
first_name= first_name,
password= password,
)
user.is_admin= True
user.is_staff= True
user.is_superuser= True
user.save(using=self._db)
return user
class User(AbstractBaseUser, models.Model):
email = models.EmailField(verbose_name='email', max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
date_joined = models.DateTimeField(auto_now_add=True, verbose_name='date joined')
last_login = models.DateTimeField(verbose_name='last login', auto_now=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
USERNAME_FIELD= 'username'
REQUIRED_FIELDS= ['email','first_name']
objects= MyAccountManager()
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
forms.py
class LoginForm(forms.Form):
username = forms.CharField(initial='' ,label='Username:',max_length=30)
password = forms.CharField(max_length=20, widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username', 'password')
def clean(self):
cleaned_data = super(LoginForm, self).clean()
confirm_password = cleaned_data.get('password')
class SignUpForm(forms.ModelForm):
first_name = forms.CharField(required= True,initial='',max_length=20)
last_name = forms.CharField(required=True,max_length=30, initial='')
username = forms.CharField(max_length=30,initial='', required=True)
password = forms.CharField(max_length= 20, initial='', widget = forms.PasswordInput())
password2= forms.CharField(max_length=20, initial='',widget = forms.PasswordInput())
email = forms.EmailField(max_length=60, initial='',)
class Meta:
model = User
fields = ('first_name', 'last_name','username','password2','email')
def clean(self):
cleaned_data = super(SignUpForm,self).clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('password2')
if(password != confirm_password):
raise forms.ValidationError(
'Password and Confirm Password do not match.'
)
views.py
def signin_and_signup(request):
if request.method == 'POST':
logout(request)
sign_in = LoginForm(request.POST)
signup = SignUpForm(request.POST)
if 'sign-in-name' in request.POST:
if sign_in.is_valid():
username = request.POST.get('username')
password= request.POST.get('password')
user = authenticate(username= username, password= password)
if user:
return HttpResponse('success')
else:
return HttpResponse('fail')
elif 'sign-up-input-name' in request.POST:
if(signup.is_valid()):
user = signup.save(commit=False)
nonHashed = signup.cleaned_data['password']
varhash = make_password(nonHashed, None, 'md5')
user.set_password(varhash)
user.save()
else:
print("Ran3<------------")
signup = SignUpForm()
else:
sign_in = LoginForm()
signup = SignUpForm()
context = {'signin':sign_in, 'signup':signup}
return render(request, 'home.html', context)
Why are you substituting the user model that django provides if you have the same attributes?
This is done in case you want to extend or add new properties to the user, for example, license number, avatar, position.
Anyway, your authenticate() maybe doesn't work because you haven't registered your new model in settings.py.
AUTH_USER_MODEL = 'name_of_the_app.User'
I recommend that you take a look at the official documentation
https://docs.djangoproject.com/en/3.1/topics/auth/customizing/
Antoher thing it could be your authentication backend:
Try:
settings.py
AUTHENTICATION_BACKENDS = [
'name_of_the_app.admin.LoginBackend',
]
Where you want for example admin.py
from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q
class LoginBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try: # to allow authentication through phone number or any other field, modify the below statement
user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
UserModel().set_password(password)
except MultipleObjectsReturned:
return models.User.objects.filter(email=username).order_by('id').first()
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
user = UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
As you can see, you can also login with the email
I have a login() function in my form, I would like to return errors on specific fields in it, my questions is, how I could do something like that, this is my current function:
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
def validate_email(self, email):
user = models.User.query.filter_by(email=email.data).first()
if user is None:
raise ValidationError('Such user not found')
def login(self, user):
if check_password_hash(user.password, self.password.data):
flash('Logged in successfully!', category='success')
login_user(user, remember=self.remember.data)
return True
else:
raise LoginException('Wrong Password')
Should I just maybe flash the errors or should I remake it into a seperate validate password function? If there is a way I could for example return the LoginException so it gets displayed under my password field, I would be happy to know.
I think I have found a solution for you. Is that what you want?
def valid_credentials(form, field):
user = User.query.filter_by(email=form.email.data).first()
return user and user.verify_password(form.password.data)
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
def validate_email(self, field):
if not valid_credentials(self, field):
raise ValidationError('Invalid user credentials.')
def validate_password(self, field):
if not valid_credentials(self, field):
raise ValidationError('Invalid user credentials.')
class User(db.Model):
email = db.Column(db.String,
nullable=False, unique=True, index=True)
password_hash = db.Column(db.String,
nullable=False, unique=False, index=False)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
You can also move valid_credentials (form, field) into the form and/or only pass one validation function to the validators list.
Make sure that you do not tell the user which form field contains the error and that the user may be able to guess the access data more easily.
I am trying to do password validation on wtform like this:
email = StringField('Email', validators=[DataRequired(), Email()])
def validate_password(self, password):
print(self)
user = dbSession.execute(
"SELECT * FROM users WHERE email = :email",
{"email": self.email.data}
).fetchone()
print(user)
if not bcrypt.check_password_hash(user.password, password.data):
raise ValidationError('Incorrect Password.')
i want to get the email from other field, but i guess it's not working, i tried email.data but it isn't defined. also, it's not logging in console. in js, you log an object like this. I wanted to see the self's properties and the user, i want to log like this in python.
console.log('user =', user);
help?
This answer reformulates this little snippet
Sometimes you are in the situation where you need to validate a form with custom logic that can not necessarily be reduced to a validator on a single field. A good example are login forms where you have to make sure a user exists in the database and has a specific password.
class LoginForm(Form):
email = TextField('Email', [validators.InputRequired(), Email()])
password = PasswordField('Password', [validators.InputRequired()])
def __init__(self, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
self.user = None
def validate(self):
rv = Form.validate(self)
if not rv:
return False
user = User.query.filter_by(
email=self.email.data).first()
if user is None:
self.email.errors.append('Unknown email')
return False
if not bcrypt.check_password_hash(user.password, self.password.data):
self.password.errors.append('Invalid Password')
return False
self.user = user
return True
I'm building an authentication system for a website, I don't have prior test experience with Django. I have written some basic tests.
the model,
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=25, unique=True, error_messages={
'unique': 'The username is taken'
})
first_name = models.CharField(max_length=60, blank=True, null=True)
last_name = models.CharField(max_length=60, blank=True, null=True)
email = models.EmailField(unique=True, db_index=True, error_messages={
'unique': 'This email id is already registered!'
})
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username',]
objects = UserManager()
def get_full_name(self):
return ' '.join([self.first_name, self.last_name])
def get_short_name(self):
return self.email
def __unicode__(self):
return self.username
and model manager,
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
if not email:
raise ValueError('Enter Email address')
if not kwargs.get('username'):
raise ValueError('Enter Username')
account = self.model(
email=self.normalize_email(email), username=kwargs.get('username')
)
account.set_password(password)
account.save()
return account
def create_superuser(self, email, password, **kwargs):
account = self.create_user(email, password, **kwargs)
account.is_superuser = True
account.save()
return account
and my tests,
class SettingsTest(TestCase):
def test_account_is_configured(self):
self.assertTrue('accounts' in INSTALLED_APPS)
self.assertTrue('accounts.User' == AUTH_USER_MODEL)
class UserTest(TestCase):
def setUp(self):
self.username = "testuser"
self.email = "testuser#testbase.com"
self.first_name = "Test"
self.last_name = "User"
self.password = "z"
self.test_user = User.objects.create_user(
username=self.username,
email=self.email,
first_name=self.first_name,
last_name=self.last_name
)
def tearDown(self):
del self.username
del self.email
del self.first_name
del self.last_name
del self.password
def test_create_user(self):
self.assertIsInstance(self.test_user, User)
def test_default_user_is_active(self):
self.assertTrue(self.test_user.is_active)
def test_default_user_is_staff(self):
self.assertFalse(self.test_user.is_staff)
def test_default_user_is_superuser(self):
self.assertFalse(self.test_user.is_superuser)
def test_get_full_name(self):
self.assertEqual('Test User', self.test_user.get_full_name())
def test_get_short_name(self):
self.assertEqual(self.email, self.test_user.get_short_name())
def test_unicode(self):
self.assertEqual(self.username, self.test_user.__unicode__())
fortunately all the passes, and my question is,
are these tests overdone or underdone or normal? What should be tested in a model? is there any procedure missing? anything wrong with this tests?? how do I effectively write tests??
thank you for any insights.
That's quite enough. Just a couple of notes:
No need to delete properties in tearDown
You forgot to tests UserManager in lines raise ValueError using assertRaises.
You may also test that user created by create_user(from UserManager) can authenticate(from django.contrib.auth) by given password and email.
Use coverage package to detect which lines/classes/packages/statements were missed to test.