Получите код двухфакторной аутентификации от пользователя без потери сеанса (Flask server) - PullRequest
0 голосов
/ 25 октября 2018

У меня есть запрос 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. Поэтому мне нужно удалить командную строку input('code: ') для подтверждения по SMS.Я буду использовать форму через 'POST' для получения пользовательского ввода.

Однако, если я redirect() или render_template() для отправки пользователя на форму, это прерывает мой сеанс API, и я получаю обратно«не аутентифицирован сеанс» ответ от API.

Логика сервера. Речь идет о /update (сначала адрес электронной почты и пароль) и /authenticate (форма подтверждения SMS):

@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/stackoverflow/01/application.py

Как заблокировать выполнение кода в ожидании ответа пользователя с помощью своего кода SMS? Или я должен решить эту проблему по-другому?

1 Ответ

0 голосов
/ 26 октября 2018

Ошибка, с которой вы столкнулись, на самом деле связана с тем, как вы пытаетесь использовать глобальные переменные для сохранения состояния между запросами.Сначала вы определяете пароль как переменную уровня модуля, а затем устанавливаете password = request.form.get("pc_password") в своей функции обновления.Из-за правил питонов относительно глобальных и локальных переменных https://docs.python.org/3/faq/programming.html#id9 это создает новую локальную переменную, содержащую значение пароля, и оставляет переменную уровня модуля без изменений.Затем вы получаете доступ к исходной глобальной переменной пароля в вашей функции проверки подлинности, которая завершается ошибкой, поскольку для этой переменной пароля все еще задано исходное значение ''.Быстрое исправление заключается в добавлении global password в начале вашей функции обновления, но это игнорирует другие проблемы с этим методом постоянного состояния.Все ваши глобальные переменные распределяются между всеми, кто использует ваш сайт, поэтому, если в систему вошли несколько человек, все они будут зарегистрированы в одной учетной записи личного капитала.Было бы предпочтительнее использовать объект сеанса для сохранения этих данных, поскольку тогда каждый пользователь сможет получить доступ только к своему собственному объекту сеанса, и не будет риска доступа людей к учетным записям друг друга.Использование вами объекта PersonalCapital немного усложняет ситуацию, поскольку при этом используются переменные экземпляра для сохранения состояния, что подходит для приложения командной строки, но в меньшей степени для веб-приложения.Однако это очень простой объект, имеющий только две переменные экземпляра.Поэтому было бы довольно просто извлечь их и сохранить их в сеансе в конце вашей функции обновления и использовать эти значения для перестройки объекта в начале вашей функции аутентификации.

...