I wanted to have the field in User
model that through it the user logs in as username
instead of email
I defined:app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'
But I'm still getting:
user_datastore.add_role_to_user(name, 'mgmt')
File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 105, in add_role_to_user
user, role = self._prepare_role_modify_args(user, role)
File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 72, in _prepare_role_modify_args
user = self.find_user(email=user)
File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 203, in find_user
return self.user_model.query.filter_by(**kwargs).first()
File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 1333, in filter_by
for key, value in kwargs.items()]
File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/sqlalchemy/orm/base.py", line 383, in _entity_descriptor
(description, key)
InvalidRequestError: Entity '<class 'flask_app.models.User'>' has no property 'email'
It seems that email is hardcoded into flask-security...
Can I change it?
edit: The User Model (as requested in the comment):
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True, index=True)
password = db.Column(db.String(255))
token = db.Column(db.String(255), unique=True, index=True)
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
At the same time, you can use Flask-login API to do some configurations in case that you want to use its functionality. When you want to check whether the user has logged in manually rather than use the Flask-login API, then check the value of session['logged_in'] .
To login with a username instead of an email address (using Flask-Security 1.7.0 or higher), you can replace the email
field with a username
field in the User
model
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True, index=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
and update the app
configuration.
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'
Next, to allow users to login using a username instead of an email, we will use the fact that the LoginForm validation method assumes the user identity attribute is in the email
form field.
from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired
class ExtendedLoginForm(LoginForm):
email = StringField('Username', [InputRequired()])
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
login_form=ExtendedLoginForm)
This way, we can login using a username without rewriting the validation method or the login template. Of course, this is a hack and the more correct approach would be to add a custom validate
method, which checks a username
form field, to the ExtendedLoginForm
class and to update the login template accordingly.
However, the approach above makes it easy to login with a username or an email address. To do this, define a user model with both a username and email field.
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
username = db.Column(db.String(255), unique=True, index=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
and update the app
configuration.
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')
Finally, create the custom login form.
from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired
class ExtendedLoginForm(LoginForm):
email = StringField('Username or Email Address', [InputRequired()])
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
login_form=ExtendedLoginForm)
Now, when logging in, Flask-Security will accept an email or username in the email form field.
I managed to implement login using either username or password by overwriting the login form:
class ExtendedLoginForm(LoginForm):
email = StringField('Username or Email Address')
username = StringField("Username")
def validate(self):
from flask_security.utils import (
_datastore,
get_message,
hash_password,
)
from flask_security.confirmable import requires_confirmation
if not super(LoginForm, self).validate():
return False
# try login using email
self.user = _datastore.get_user(self.email.data)
if self.user is None:
self.user = _datastore.get_user(self.username.data)
if self.user is None:
self.email.errors.append(get_message("USER_DOES_NOT_EXIST")[0])
# Reduce timing variation between existing and non-existing users
hash_password(self.password.data)
return False
if not self.user.password:
self.password.errors.append(get_message("PASSWORD_NOT_SET")[0])
# Reduce timing variation between existing and non-existing users
hash_password(self.password.data)
return False
if not self.user.verify_and_update_password(self.password.data):
self.password.errors.append(get_message("INVALID_PASSWORD")[0])
return False
if requires_confirmation(self.user):
self.email.errors.append(get_message("CONFIRMATION_REQUIRED")[0])
return False
if not self.user.is_active:
self.email.errors.append(get_message("DISABLED_ACCOUNT")[0])
return False
return True
and register it as described in other posts:
# Setup Flask-Security
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
login_form=ExtendedLoginForm)
Since email and username are optional now one of them can be used to login. But make sure that both fields are set unique in the DB model.
From https://pythonhosted.org/Flask-Security/models.html
Fields id, email, password, active
is essential.So add
email = db.Column(db.String(255), unique=True)
Just add your custom username
field along this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With