Flask-Login current_user inconsistent on flask app deployed on GAE

Viewed 16

I have a flask app deployed on GAE, and am having a problem where the current_user.is_authenticated boolean returns inconsistent results on the deployed version. It works consistently on a local server, but on GAE, after logging in, while the rest of the app behaves correctly, displaying @login_required routes, the navbar, which relies on the current_user to display different menus for logged in users and non-logged in users, sometimes shows the correct menus and sometimes the incorrect. Repeated refreshes will switch inconsistently between the logged in view and the logged out.

I have tried adding the --preload tag to the gunicorn call in the app.yaml, but it has had no effect.

My init.py invokes the flask-login manager:

login_manager = LoginManager()
login_manager.init_app(app)

@login_manager.user_loader
def load_user(user_id):
    return User.query.filter_by(id=user_id).first()

My base layout creates the navbar:

    {% if current_user.is_authenticated %}
        <li class="nav-item active ml-3">
            <span class="nav-link">User: <a href="/">{{ current_user.email }}</a></span></li>

        <li class="nav-item ml-3">
            <a class="btn btn-outline-secondary btn-sm mt-1 reg" href="{{ url_for('home.logout')}}">Logout <span class="sr-only">(current)</span></a></li>

    {% else %}
        <li class="nav-item active">
            <a class="nav-link" href="/">Register <span class="sr only">(current)</span></a></li>

        <li class="nav-item active">
            <a class="nav-link" href="{{ url_for('home.login') }}">Login <span class="sr-only">(current)</span></a></li>
    {% endif %}

app.yaml calls guinicorn:

runtime: python #for some reason this needs to be python-compat if you run with dev_appserver
env: flex
threadsafe: true
entrypoint: gunicorn -b :$PORT wsgi:app --preload --timeout 120

runtime_config:
  python_version: 3

handlers:
  - url: /.*
    script: main.app
    secure: always

I'm really stumped by this, anyone have any ideas?

EDIT --- Here is the User model:

class User(BaseModel, UserMixin):
    __tablename__ = "user"

    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name_last = db.Column(db.String, nullable=True)
    name_first = db.Column(db.String, nullable=True)
    email = db.Column(db.String, nullable=True)
    is_admin = db.Column(db.Boolean, default=False)
    opt_out = db.Column(db.Boolean, default=False)
    print_opt_in = db.Column(db.Boolean, default=False)
    date_created = db.Column(db.DateTime(), server_default=func.now())
    date_modified = db.Column(db.DateTime(), onupdate=func.now())

    
    organizations = db.relationship('Organization', secondary=user_organizations, lazy='subquery',
        backref=db.backref('users', lazy=True))

    code = db.Column(db.String, nullable=True)

    # need this to make many-to-many name show up in flask-admin
    def __str__(self):
        return self.name_last + ', ' + self.name_first

    def __repr__(self):
        return self.name_first + ' ' + self.name_last

    # return id to be used in storage
    def get_id(self):
        return self.id

The views are many and fairly complicated, and this issue seems to be global. But here at least is the login view, which is 2 separate routes, the first of which which emails a code to the user rather than using a password:


@home_blueprint.route('/', methods=['get','post'])
@home_blueprint.route('/login', methods=['get','post'])
def login():
    form = LoginForm()
    message = request.form.get('message')

    if request.method=="POST":
        if current_user:
            logout_user()
        user = User.query.filter_by(email=form.email.data.lower()).first()
        if not user:
            message = f'{form.email.data} not found.'
            return render_template('home/login.html', form=form, message=message)
        else:

            ### CREATE 4 DIGIT CODE
            ### SAVE TO USER TABLE
            ### EMAIL TO USER
            ### SEND TO 2nd LOGIN PAGE FOR EMAIL/CODE
            code = randrange(1000,9999)
            print(code)

            user.code = code
            db.session.add(user)
            db.session.commit()

            message = send_verification_email(form.email.data, code)

            payload = {'email':form.email.data}
            email_encoded = urlencode(payload)

            return redirect(url_for('home.login2', message=message, email_encoded=email_encoded))
    else:
        return render_template('home/login.html', form=form, message=message)


@home_blueprint.route('/login2', methods=['get','post'])
def login2():
    form = LoginForm2()
    message = request.form.get('message')
    email_encoded = request.args.get('email_encoded')
    parsed = parse_qsl(email_encoded)
    

    if request.method=="POST":
        if current_user:
            logout_user()
        user = User.query.filter_by(email=form.email.data.lower()).first()
        if not user:
            flash(f'{form.email.data} not found.')
            return render_template('home/login.html', form=form)
        else:
            code = request.form.get('code', type=str)
            if code == user.code:
                login_user(user)
                return redirect(url_for('jobs.job_admin', email=user.email))

            else:
                message = "Code and email do not match."
                form.email.data = parsed[0][1]
                return render_template('home/login2.html', form=form, message=message)
    else:
        form.email.data=parsed[0][1]
        return render_template('home/login2.html', form=form, message=message)


0 Answers
Related