Search code examples
pythonauthenticationflaskflask-loginflask-security

Adding a Flask user login requirement


I am using Flask-Security, which in turn uses Flask-Login. I have a site which requires that a user both confirm their email address AND subsequently be approved by an admin. Without both of these things, I don't want the user to be able to use the site.

I've turned on SECURITY_CONFIRMABLE and the email with the confirmation token is being sent correctly. I've set SECURITY_POST_CONFIRM_VIEW, and the user is directed to the right view once they click the confirmation link in the email. However, the user is logged in as part of the confirmation process. I don't want the user to have access until they are approved by an admin. (Interestingly, this line is not present in the develop branch.)

I can think of four ways to get around this, and none are particularly appealing:

  1. Allow the user to log in after confirmation, but decorate every login_required view with a function which checks the user's permissions and sends them to an error page if they aren't allowed. I already have 2 of these, and I'd prefer to avoid adding a third to every view. Plus, it feels wrong to allow login when the user can't use the site until they're approved.

  2. Turn off SECURITY_CONFIRMABLE and email/confirm the token myself. This will require copying a lot of code and the chances of an error are higher than I would like. And are there perhaps other ways a user can login which would get around this check? Say through the password reset flow? Or if a future admin turned on passwordless login with tokens?

  3. Monkey patch Flask-Security's login_user or Flask-Login's login_user to do my check. Seems quite hacky, and I don't know how to ensure that my patch is installed before any other code connects to the original function.

  4. Fork one of these libraries and insert a callback which I can implement in my code. Sledgehammer, meet nut. Plus maintainability issues. Unless perhaps I managed to get a PR accepted.

I hope there is another answer I'm overlooking. This can't be that uncommon a setup!


Solution

  • Fortunately, the solution was MUCH easier than I feared. UserMixin has an is_active property which gets consulted at the very beginning of Flask-Login's login_user. So my code simply looks like this:

    class User(UserMixin, Base):
        @property
        def is_active(self):
            return (super(UserMixin, self).is_active and not self.has_role('pending'))
    

    Now no user can sign in who has a 'pending' role.