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
Related
I'm not using auth, I've added a re_password field to my serializer, I think it only does a consistency check with the password field when a POST request comes in.
But the problem is that if re_password and password are write_only, then PUT and PATCH requests must also pass in these 2 fields.
I guess the consistency validation of re_password and password is reasonable for user registration, but it is not so necessary for updating user information.
What can I do to make re_password and password only required for POST requests?
POST: i need password and re_password field register new user account
PUT/PATCH: i don't need password and re_password as they are not suitable for updating user info
class UserSerializer(serializers.ModelSerializer):
re_password = serializers.CharField(write_only=True, min_length=6, max_length=20, error_messages={
"min_length": "Password at least 6 digits",
"max_length": "Password up to 20 characters",
})
class Meta:
exclude = ("is_delete",)
model = models.User
extra_kwargs = {**CommonSerializer.extra_kwargs, **{
"password": {
"write_only": True,
"min_length": 6,
"max_length": 20,
"error_messages": {
"min_length": "Password at least 6 digits",
"max_length": "Password up to 20 characters",
}
},
}}
def validate_password(self, data):
return hashlib.md5(data.encode("utf-8")).hexdigest()
def validate_re_password(self, data):
return hashlib.md5(data.encode("utf-8")).hexdigest()
def validate(self, validate_data):
if validate_data['password'] != validate_data.pop('re_password'):
raise exceptions.AuthenticationFailed("password not match")
return validate_data
def create(self, validate_data):
instance = models.User.objects.create(**validate_data)
return instance
def update(self, instance, validate_data):
password = validate_data.get("password")
validate_data["password"] = hashlib.md5(
password.encode("utf-8")).hexdigest()
for key, val in validate_data.items():
setattr(instance, key, val)
instance.save()
return instance
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 have been working on a small web app and I decided it would be a good idea if I used email verification. Well, when I attempted to add email verification I sort of broke my code... I keep getting a traceback telling me the following:
TypeError: <'peewee.CharField object at 0x7f46c6a7ba50> is not JSON serializable
I have come to the conclusion that I need to make my users model JSON serializable but I am not 100% sure that's what I need to do. Even if I am right, I wouldn't know how to do that.
Here is my user model:
class User(UserMixin, Model):
username = CharField(unique=True)
password = CharField(max_length=20)
email = CharField(unique=True)
confirmed = BooleanField(default=False)
confirmed_on = DateTimeField(default=datetime.datetime.now())
joined_at = DateTimeField(default=datetime.datetime.now)
class Meta:
database = db
order_by = ('-joined_at',)
#classmethod
def create_user(cls, username, email, password, confirmed):
try:
with db.transaction():
cls.create(
username=username,
email=email,
password=generate_password_hash(password),
confirmed=False)
except IntegrityError:
raise ValueError("User already exists")
as far as email verification goes here is what I tried:
def generate_serializer(secret_key=None):
return URLSafeTimedSerializer(app.config['SECRET_KEY'])
def generate_activation_url(user):
serializer = generate_serializer()
token = serializer.dumps(user.username)
return url_for('activate_user', token=token, _external=True)
#app.route('/', methods=('GET', 'POST'))
def index():
form = forms.RegisterForm()
if form.validate_on_submit():
models.User.create_user(
username=form.username.data,
email=form.email.data,
password=form.password.data,
confirmed=False
)
token = generate_activation_url(models.User)
msg =Message(render_template('activation_email.html', token=token),
recipients=[models.User.email])
mail.send(msg)
return render_template('activate.html', user=models.User)
return render_template('index.html', form=form)
#app.route('/activate/<token>')
def activate_user(token, expiration=3600):
serializer = generate_serializer()
try:
serializer.loads(token, salt=app.config[
'SECRET_KEY'], max_age=expiration)
except (BadSignature, SignatureExpired):
abort(404)
models.User.confirmed = True
flash('User activated')
return redirect(url_for('index'))
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.
I want to create my own form for user createion of django.contrib.auth.models.User in Django, but I cant find a good example. Can someone help me?
you want to create a form?
create a form say forms.py
from django.contrib.auth.models import User
from django import forms
class CreateUserForm(forms.Form):
required_css_class = 'required'
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
label="Username",
error_messages={'invalid': "This value may contain only letters, numbers and #/./+/-/_ characters."})
email = forms.EmailField(label="E-mail")
password1 = forms.CharField(widget=forms.PasswordInput,
label="Password")
password2 = forms.CharField(widget=forms.PasswordInput,
label="Password (again)")
def clean_username(self):
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError("A user with that username already exists.")
else:
return self.cleaned_data['username']
def clean_email(self):
#if you want unique email address. else delete this function
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError("This email address is already in use. Please supply a different email address.")
return self.cleaned_data['email']
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("The two password fields didn't match.")
return self.cleaned_data
create a view say views.py
def create_inactive_user(request):
if request.method=='POST':
frm=CreateUserForm(request.POST)
if frm.is_valid():
username, email, password = frm.cleaned_data['username'], frm.cleaned_data['email'], frm.cleaned_data['password1']
new_user = User.objects.create_user(username, email, password)
new_user.is_active = True # if you want to set active
new_user.save()
else:
frm=CreateUserForm()
return render(request,'<templatepath>',{'form'=frm})
it is better to use django-registration