1

I've started getting a type error recently, and I've tried many things to fix but to no avail.

I believe the issue happened when I moved to the Heroku stack 22 from stack 18 (which if I recall correctly meant a Python update from 3.6 to 3.10).

I'm not sure when it started happening because this is an issue with the login feature, and before it was working both on desktop and mobile, presumably because the issue wasn't raised when a cookie is present. Once I cleared the cookies on my mobile phone, I discovered the issue, and then I tried to replicate on desktop.

This issue is never raised on localhost, but it's always raised on the Heroku deployment. I decided to start again, where I updated to Python 3.10 in desktop and created a clean environment where I installed only the critical libraries for my app to work (see requirements.txt).

Here's the error I get:


    ERROR in app:   Exception on /login
    Traceback (most recent call last):
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask/app.py",   line 2447, in wsgi_app
    response = self.full_dispatch_request()
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask/app.py",   line 1952, in full_dispatch_request
    rv   = self.handle_user_exception(e)
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask/app.py",   line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask/_compat.py",   line 39, in reraise
    raise value
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask/app.py",   line 1950, in full_dispatch_request
    rv   = self.dispatch_request()
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask/app.py",   line 1936, in dispatch_request
    return   self.view_functions[rule.endpoint](**req.view_args)
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask_security/decorators.py",   line 230, in wrapper
    return f(*args, **kwargs)
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask_security/views.py",   line 77, in login
    if   form.validate_on_submit():
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask_wtf/form.py",   line 100, in validate_on_submit
    return self.is_submitted() and   self.validate()
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask_security/forms.py",   line 241, in validate
    if   not verify_and_update_password(self.password.data, self.user):
    File   "/app/.heroku/python/lib/python3.10/site-packages/flask_security/utils.py",   line 156, in verify_and_update_password
    verified =   _pwd_context.verify(get_hmac(password), user.password)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/context.py",   line 2342, in verify
    return record.verify(secret, hash, **kwds)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 793, in verify
    return consteq(self._calc_checksum(secret),   chk)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/handlers/bcrypt.py",   line 533, in _calc_checksum
    self._stub_requires_backend()
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 2255, in _stub_requires_backend
    cls.set_backend()
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 2157, in set_backend
    return owner.set_backend(name,   dryrun=dryrun)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 2164, in set_backend
    return cls.set_backend(name, dryrun=dryrun)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 2189, in set_backend
    cls._set_backend(name, dryrun)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 2312, in _set_backend
    super(SubclassBackendMixin,   cls)._set_backend(name, dryrun)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 2225, in _set_backend
    ok   = loader(**kwds)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/handlers/bcrypt.py",   line 686, in _load_backend_mixin
    return   mixin_cls._finalize_backend_mixin(name, dryrun)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/handlers/bcrypt.py",   line 377, in _finalize_backend_mixin
    result = safe_verify("test",   test_hash_20)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/handlers/bcrypt.py",   line 296, in safe_verify
    return verify(secret, hash)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/utils/handlers.py",   line 793, in verify
    return consteq(self._calc_checksum(secret),   chk)
    File   "/app/.heroku/python/lib/python3.10/site-packages/passlib/handlers/bcrypt.py",   line 703, in _calc_checksum_raw
    hash = _pybcrypt.hashpw(secret, config)
    File   "/app/.heroku/python/lib/python3.10/site-packages/bcrypt/__init__.py",   line 80, in hashpw
    raise TypeError("Unicode-objects must   be encoded before hashing")
    TypeError: Unicode-objects must be encoded before hashing

Here's the most relevant code:


    # to manage the website
    from flask import (Flask,
                       url_for,
                       redirect,
                       render_template,
                       request
                       #, g, session
                       )
    
    # flask bootstrap
    from flask_bootstrap import Bootstrap
    
    # flask sqlalchemy
    from flask_sqlalchemy import SQLAlchemy
    
    # flask security
    from flask_security import (Security,
                                SQLAlchemyUserDatastore,
                                UserMixin,
                                RoleMixin,
                                login_required,
                                current_user
                                )
    
    (...) 
    
    # Set up SQLAlchemy for integration with flask-security
    db = SQLAlchemy(app)
    
    # Define flask_security models
    roles_users = db.Table('roles_users',
            db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
            db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
    
    class Role(db.Model, RoleMixin):
        id = db.Column(db.Integer(), primary_key=True)
        name = db.Column(db.String(80), unique=True)
        description = db.Column(db.String(255))
    
    class User(db.Model, UserMixin):
        id = db.Column(db.Integer, primary_key=True)
        email = db.Column(db.String(255), unique=True)
        password = db.Column(db.String(255))
        # from String(255) to Binary(60) because of the issue below
        # https://stackoverflow.com/questions/51052632/bcrypt-hash-returns-typeerrorunicode-objects-must-be-encoded-before-hashing
    
        active = db.Column(db.Boolean())
        # confirmed_at = db.Column(db.DateTime())
        roles = db.relationship('Role', secondary=roles_users,
                                backref=db.backref('users', lazy='dynamic'))
    
    # Set up flask-security
    # https://pythonhosted.org/Flask-Security/quickstart.html#id1
    # https://stackoverflow.com/questions/52190989/how-to-encrypt-password-using-python-flask-security-using-bcrypt?r=SearchResults&s=12|92.0218
    user_datastore = SQLAlchemyUserDatastore(db, User, Role)
    security = Security(app, user_datastore)

This is all the code I use, besides telling sqlalchemy where to read the database and then using the appropriate login_required decorators. It's been working well for 2 years before this.

It's reading well the user and password from a SQL database. I've changed the user and password and it works well in localhost, so it's reading from there correctly.

I've tried changing the user and password from a String to Binary, both on the file and on the SQL database (and the many combinations possible of just doing one or the other), but that just throws other errors. See Bcrypt Hash Returns TypeError("Unicode-objects must be encoded before hashing") and Invalid Salt

The solution seems to be quite simple, from what I gather from many similar StackOverflow threads, e.g. bcrypt.checkpw returns TypeError: Unicode-objects must be encoded before checking But all solutions I found are from people calling bcrypt themselves.

I am not calling bcrypt on my own, so I think this is not an issue with my own code. This seems to be driven by the flask-security library.

  • What would you recommend? I have two ideas, in case someone is familiar with the issue:

    • Learn how to create a "custom library" and correct the required file. I'm a newbie and I don't know how to do that. Currently I'm just giving Heroku my list of library dependencies.

    • Create a ticket in the Flask-Security team's queue.

  • I know I'm supposed to provide minimal code so that you can replicate, but how do I actually do that? In this example, besides files (Python, HTML, CSS...) you even need a working postgresql database.

Thanks a lot!

  • Are you running the same database version on your local machine? – SargeATM Sep 03 '22 at 02:00
  • @SargeATM Thanks! Yes, the database is the same. It's also hosted on Heroku, and I call it as well on localhost and connect to it using DBeaver on desktop too. It has 20 other tables besides the login one and it always worked well so far. – YouWereOnceABeginner Sep 03 '22 at 12:05
  • 2
    It looks like you are using the 'older' Flask-Security which last released in 2017. There is a maintained fork at Flask-Security-Too which is tested with python 3.10. For questions like these (was working, updated, now not working) adding versions of packages is really useful (Flask, Flask-Security, Werkzeug, Flask-SQLAlchemy, etc). – jwag Sep 03 '22 at 16:13
  • @jwag Thank you very much, I didn't know about that package, I'll check it and come back. Also I provided all the packages and versions in the requirements.txt doc. – YouWereOnceABeginner Sep 03 '22 at 16:35

0 Answers0