Search code examples
flaskflask-admin

Flask Admin - Automatically create password hash when creating a new user?


Say I have a user model like:

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(255))
    last_name = db.Column(db.String(255))
    email = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255))
    registered_on = db.Column(db.DateTime, nullable=True)
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

And an admin view:

class UserView(MyModelView):

    form_columns = (
        'roles',
        'first_name',
        'last_name',
        'email',
        'password',
        'registered_on',
    )

    form_args = dict(
                registered_on=dict(default=datetime.now())
            )

When I create a new user how to I automatically generate a password hash with something like bcrypt?


Solution

  • I think by automatic you mean that it should automatically convert a manually-supplied plain-text password into a hash, but I am not certain. I am answering as if this assumption is correct.

    We don't expose our password fields in Flask-Admin, but we handle the basic question of how and when passwords get hashed by defining password as a property with a setter responsible for computing the hash itself and tucking it away in user._password.

    A naive swing at just trying to add password as a column in the admin view didn't work, but if you use sqlalchemy's hybrid_property instead of property, it looks like this works fine with "password" in your User view's list of form_columns (as you already have):

    # models.py
    from sqlalchemy.ext.hybrid import hybrid_property
    
    class User(sql.Model):
        # ...
    
        _password = sql.Column(sql.Binary)
        _salt = sql.Column(sql.Binary, default=lambda: os.urandom(512))
    
        # ... 
    
        @hybrid_property
        def password(self):
            """Return the hashed user password."""
            return self._password
    
        @password.setter
        def password(self, new_pass):
            """Salt/Hash and save the user's new password."""
            new_password_hash = compute_new_password_hash(new_pass, self._salt)
            self._password = new_password_hash
    
    # admin.py
    class UserView(MyModelView):
        # ...
    
        form_columns = ("email", "password", ...)
    

    The editing experience isn't exactly polished--you'd probably need to override the field to refine it. Here are some screenshots:

    Creating a new user with a pre-set password:

    Creating a new user with password

    The edit view after creation:

    User created, password hashed

    Updating the user's password:

    Saving a new password

    The edit view after saving the new password:

    User updated with new hash