在不丢失会话的情况下从用户获取双因素身份验证代码(Flask 服务器)

Get two-factor authentication code from user without losing session (Flask server)

我有一个 API 对第三方网站的请求,该请求在命令行中运行良好(来自 https://github.com/haochi/personalcapital):

pc = PersonalCapital()

try:
    pc.login(email, password)
except RequireTwoFactorException:
    pc.two_factor_challenge(TwoFactorVerificationModeEnum.SMS)
    pc.two_factor_authenticate(TwoFactorVerificationModeEnum.SMS, input('code: '))
    pc.authenticate_password(password)

accounts_response = pc.fetch('/newaccount/getAccounts')
accounts = accounts_response.json()['spData']

当我在命令行中 运行 上面的内容时,我得到了一个 JSON 正如预期的那样。

但是,我想在 Flask 服务器上的 Web 应用程序中使用它。因此,我需要删除用于短信确认的命令行 input('code: ')。我想我会通过 'POST' 使用表单来获取用户输入。

但是,如果我 redirect()render_template() 将用户发送到表单,它会中断我的 API 会话,并且我会收到来自 "session not authenticated" 的响应API.

服务器逻辑。有问题的路线是/update(首先是电子邮件和密码)和/authenticate(短信确认表):

@app.route("/update", methods=["GET", "POST"])
@login_required
def update():

    # Via post:
    if request.method == "POST":

        # Ensure userentered email
        if not request.form.get("pc_email"):
            return apology("Please enter username", 400)

        # Ensure user entered password
        elif not request.form.get("pc_password"):
            return apology("Please enter password", 400)

        # Save email & password
        email = request.form.get("pc_email")
        password = request.form.get("pc_password")

        # Try to log in
        try:
            pc.login(email, password)

        # If 2-factor is required, send sms & redirect
        except RequireTwoFactorException:
            pc.two_factor_challenge(TwoFactorVerificationModeEnum.SMS)
            return redirect("/authenticate")

        # Get data:
        else:
            # Get accounts data
            accounts_response = pc.fetch('/newaccount/getAccounts')
            accounts = accounts_response.json()['spData']

            # TODO - update database w/ data from accounts & transactions

            return redirect("/")


@app.route("/authenticate", methods=["GET","POST"])
@login_required
def authenticate():

        # Via POST:
        if request.method == "POST":

            # SMS authentication
            pc.two_factor_authenticate(TwoFactorVerificationModeEnum.SMS, \
                request.form.get(sms))
            pc.authenticate_password(password)

            # Get accounts data
            accounts_response = pc.fetch('/newaccount/getAccounts')
            accounts = accounts_response.json()

            # TODO - update database w/ data from accounts & transactions

            # Redirect to "/"
            return render_template("test.html", accounts=accounts)

        # Via GET:
        else:
            return render_template("authenticate.html")

项目的源代码在这里:https://github.com/bennett39/budget/blob/Whosebug/01/application.py

如何在等待用户用短信代码响应时阻止代码执行?或者,我应该用不同的方式解决这个问题吗?

您遇到的错误实际上是由于您尝试使用全局变量在请求之间保持状态的方式所致。您最初将密码定义为模块级变量,然后在更新函数中设置 password = request.form.get("pc_password")。由于 pythons 关于全局和局部变量的规则 https://docs.python.org/3/faq/programming.html#id9 这将创建一个包含密码值的新局部变量并保持模块级变量不变。然后,您在身份验证函数中访问原始全局密码变量,该函数失败,因为此密码变量仍设置为其原始值“”。快速修复是在更新函数的开头添加 global password ,但这忽略了这种持久状态方法的其他问题。您所有的全局变量都在使用您网站的每个人之间共享,因此如果多人登录,那么他们都将登录到同一个个人资本账户。最好使用会话对象来保存此数据,因为这样每个用户将只能访问他们自己的会话对象,并且不会有人们访问彼此帐户的风险。您对 PersonalCapital 对象的使用使事情变得有点复杂,因为它使用实例变量来保持状态,这适用于命令行应用程序,但不适用于 Web 应用程序。然而,它是一个非常简单的对象,只有 2 个实例变量。因此,提取这些值并将它们存储在更新函数结束时的会话中并使用这些值在身份验证函数开始时重建对象应该相当简单。