0

Since updating my Flask app's dependencies, my Sentry is logging errors I don't understand. Changes to Flask-Login or Flask-Security-Too have thrown me off.

My user model has always used an Integer as its primary_key

class User(db.Model, UserMixin):

    id = db.Column(db.Integer, primary_key=True)

I'm getting the following error:

(psycopg2.errors.InvalidTextRepresentation) invalid input syntax for integer: "5818f9aad20447b1b6a37c51f2c2xxxx"
LINE 3: WHERE "user".id = '5818f9aad20447b1b6a37c51f2c2xxxx' 

Which I assume stems from Flask-Security-Too's datastore user lookup:

def find_user(self, **kwargs):
    return self.user_model.query.filter_by(**kwargs).first()

But maybe the problem goes bath further to Flask-Login as it's getting this random ID from the session:

    # Load user from Flask Session
    user_id = session.get("_user_id")
    if user_id is not None and self._user_callback is not None:
        user = self._user_callback(user_id)

Is Flask-Security-Too's fs_uniquifier in my session? Is that what's stepping into the place of my integer ID? I'm hoping I can avoid refactoring my entire code and database and retain my user.id as an int. Any thoughts are appreciated.

Here is the full trace that has occurred 15 times since upgrading, all without halting users from accessing or submitting data:

InvalidTextRepresentation: invalid input syntax for integer: "63b93cd01b8f4e65a7daa83becc3xxxx"
LINE 3: WHERE "user".id = '63b93cd01b8f4e65a7daa83becc3xxxx' 
                          ^

  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
DataError: (psycopg2.errors.InvalidTextRepresentation) invalid input syntax for integer: "63b93cd01b8f4e65a7daa83becc3xxxx"
LINE 3: WHERE "user".id = '63b93cd01b8f4e65a7daa83becc3xxxx' 
                          ^

[SQL: SELECT "user".id AS user_id, "user".school_id AS user_school_id, "user".location_id AS user_location_id, "user".first_name AS user_first_name, "user".last_name AS user_last_name, "user".display_name AS user_display_name, "user".image AS user_image, "user".email AS user_email, "user".password AS use...
  File "flask/app.py", line 2463, in wsgi_app
    response = self.full_dispatch_request()
  File "flask/app.py", line 1760, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "__init__.py", line 271, in error_router
    return original_handler(e)
  File "flask/app.py", line 1756, in full_dispatch_request
    rv = self.preprocess_request()
  File "flask/app.py", line 2247, in preprocess_request
    rv = self.ensure_sync(before_func)()
  File "flask_principal.py", line 477, in _on_before_request
    identity = loader()
  File "flask_security/core.py", line 245, in _identity_loader
    if not isinstance(current_user._get_current_object(), AnonymousUserMixin):
  File "werkzeug/local.py", line 516, in _get_current_object
    return get_name(local())  # type: ignore
  File "flask_login/utils.py", line 25, in <lambda>
    current_user = LocalProxy(lambda: _get_user())
  File "flask_login/utils.py", line 372, in _get_user
    current_app.login_manager._load_user()
  File "flask_login/login_manager.py", line 364, in _load_user
    user = self._user_callback(user_id)
  File "flask_security/core.py", line 221, in _user_loader
    return _security.datastore.find_user(id=user_id)
  File "flask_security/datastore.py", line 254, in find_user
    return self.user_model.query.filter_by(**kwargs).first()
  File "sqlalchemy/orm/query.py", line 2819, in first
    return self.limit(1)._iter().first()
  File "sqlalchemy/orm/query.py", line 2903, in _iter
    result = self.session.execute(
  File "sqlalchemy/orm/session.py", line 1712, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "sqlalchemy/engine/base.py", line 1631, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "sqlalchemy/sql/elements.py", line 332, in _execute_on_connection
    return connection._execute_clauseelement(
  File "sqlalchemy/engine/base.py", line 1498, in _execute_clauseelement
    ret = self._execute_context(
  File "sqlalchemy/engine/base.py", line 1862, in _execute_context
    self._handle_dbapi_exception(
  File "sqlalchemy/engine/base.py", line 2043, in _handle_dbapi_exception
    util.raise_(
  File "sqlalchemy/util/compat.py", line 208, in raise_
    raise exception
  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block

  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
InternalError: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block

[SQL: SELECT "user".id AS user_id, "user".school_id AS user_school_id, "user".location_id AS user_location_id, "user".first_name AS user_first_name, "user".last_name AS user_last_name, "user".display_name AS user_display_name, "user".image AS user_image, "user".email AS user_email, "user".password AS user_password, "user".fs_uniquifier AS user_fs_uniquifier, "user".phone AS user_phone...
  File "flask/app.py", line 2486, in __call__
    return self.wsgi_app(environ, start_response)
  File "flask/app.py", line 2466, in wsgi_app
    response = self.handle_exception(e)
  File "__init__.py", line 271, in error_router
    return original_handler(e)
  File "flask/app.py", line 1662, in handle_exception
    server_error = self.ensure_sync(handler)(server_error)
  File "__init__.py", line 21, in crash_page
    return render_template('error_pages/500.html'), 500
  File "flask/templating.py", line 147, in render_template
    return _render(app, template, context)
  File "flask/templating.py", line 128, in _render
    app.update_template_context(context)
  File "flask/app.py", line 932, in update_template_context
    context.update(func())
  File "flask_login/utils.py", line 407, in _user_context_processor
    return dict(current_user=_get_user())
  File "flask_login/utils.py", line 372, in _get_user
    current_app.login_manager._load_user()
  File "flask_login/login_manager.py", line 364, in _load_user
    user = self._user_callback(user_id)
  File "flask_security/core.py", line 221, in _user_loader
    return _security.datastore.find_user(id=user_id)
  File "flask_security/datastore.py", line 254, in find_user
    return self.user_model.query.filter_by(**kwargs).first()
  File "sqlalchemy/orm/query.py", line 2819, in first
    return self.limit(1)._iter().first()
  File "sqlalchemy/orm/query.py", line 2903, in _iter
    result = self.session.execute(
  File "sqlalchemy/orm/session.py", line 1712, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "sqlalchemy/engine/base.py", line 1631, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "sqlalchemy/sql/elements.py", line 332, in _execute_on_connection
    return connection._execute_clauseelement(
  File "sqlalchemy/engine/base.py", line 1498, in _execute_clauseelement
    ret = self._execute_context(
  File "sqlalchemy/engine/base.py", line 1862, in _execute_context
    self._handle_dbapi_exception(
  File "sqlalchemy/engine/base.py", line 2043, in _handle_dbapi_exception
    util.raise_(
  File "sqlalchemy/util/compat.py", line 208, in raise_
    raise exception
  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
dadiletta
  • 299
  • 3
  • 17
  • It's so strange. While errors are being recorded, the site appears to still be working. Looks like Flask-Login is pulling an encrypted string from the user's cookie which freaks Postgres out as the user.id can't be a string. – dadiletta Aug 09 '22 at 13:49

1 Answers1

0

Some additional info - as of FS2 3.4.0 it no longer references user.id at all - so that can be whatever you want. FS2 overrides flask_login's UserMixin::get_id - and returns the value of fs_uniquifier. And yes - that is what is stored in the session cookie - and no - nothing in the session cookie is encrypted (it is signed). So: 1) are you overriding get_id()? 2) any chance that since it seems to be 'working' that you have some post-processing - maybe in a signal or something and it is calling the find_user or some other DB query?

jwag
  • 662
  • 5
  • 6
  • Thanks for the help. I've edited my message to include the raw traceback. I have not overridden `get_id`. – dadiletta Aug 10 '22 at 13:12
  • You appear to be using a very old Flask-Security-Too - 3.0.1! Yes, that still uses your DB model 'id' column (and that is stored in the session cookie). – jwag Aug 10 '22 at 23:26
  • I was on Flask-Security-Too v. 4.1.5. However, I found I still had Flask-Security v. 3.0.1. When I removed that, my app broke: `ImportError: cannot import name 'SQLAlchemySessionUserDatastore' from 'flask_security' (unknown location)` Studying up on that now. Thanks for your continued support ✊ – dadiletta Aug 11 '22 at 14:14
  • Does Flask-Security need to be installed to run FS2? Are there perhaps any examples of Flask-Security-Too that I might reference? I'm afraid I've gotten myself quite turned around. – dadiletta Aug 15 '22 at 15:52
  • You need to uninstall Flask-Security - if you still see a backtrace with find_user(id=user_id) then you still have something wrong. FS2 is a replacement - it has the same package name Flask-Security. I would pip uninstall flask_security flask_security_too and then re-install pip install flask_security_too – jwag Aug 15 '22 at 20:49
  • Ugh! That was it. Thank you for the support. – dadiletta Aug 17 '22 at 18:16