0
votes

I am working on my login route for a small flask app and am testing the part where I try to log in a good email but bad password. I want the generic "invalid username/pw combo" message so they know they made a mistake but hackers don't know what the mistake is. I get to the part where I can't believe "even this doesn't work" because all I want to do is rerender the form. I was trying to add an error message but just want to get the page to work first now.

views.py:

@application.route('/login', methods=['GET', 'POST'])
def login(message=""):
    form = LoginForm()
    if request.method == 'POST' and form.validate_on_submit():
        email = form.email.data
        password = form.password.data
        user = User.query.filter_by(email=email, password=password).first()
        print(user)
        try:
            login_user(user)
        except AttributeError:
            print('in the except')
            form = LoginForm()
            # even this doesn't work?
            return render_template('login.html', form=form)

        flash('Logged in successfully.')
        next_url = request.args.get('next')
        return redirect(next_url or url_for('main'))

    return render_template('login.html', form=form)

forms.py:

from flask.ext.wtf import Form
from wtforms import StringField, BooleanField, TextField, PasswordField, validators
from wtforms.validators import Required


class LoginForm(Form):
    email = StringField('email', [Required()])
    password = PasswordField('password', [Required()])

the template:

{% extends "base.html" %}

{% block title %}Login{% endblock %}

{% block content %}
    <h2> Welcome to the status page</h2>
    <br>
    <form action="{{ url_for('login') }}" method="post" name="login_user">
        {{ form.hidden_tag() }}
        <p>Please log in:<br>
            Email:<br>
            {{ form.email }}<br>
            {% for error in form.email.errors %}
                <p><span style="color: red">[{{ error }}]</span></p>
            {% endfor %}
            Password:<br>
            {{ form.password }}<br>
            {% for error in form.password.errors %}
                <p><span style="color: red">[{{ error }}]</span></p>
            {% endfor %}
        </p><br>
        <p><input type="submit" value="Submit"></p>
    </form>
    <p><a href="{{ url_for('register') }}">I need to register</a></p>

<script type="text/javascript"> 

</script>

{% endblock %}

Worst part is I get this antisocial error message:

127.0.0.1 - - [02/May/2016 11:47:41] "POST /login HTTP/1.1" 400 -

My dog, with vastly differing vocal cords, communicates much better than flask does with debug=True. I'm getting those all over anywhere I try to handle a form the way I want and it raises 400. Errors besides 400 have a handy stacktrace.

Right now, GET loads the page and shows an empty form (good). A POST with the correct email/pw works and logs the user in. The bad path is a correct email that exists, but bad pw that doesn't match that user's pw

Going into the shell works past the print('in the except') and form = LoginForm() but fails on returning render_template:

BadRequestKeyError: 400: Bad Request

That's all it says

I do have a view that works by taking GET or POST and handling the error. Both render the same template, only difference is if the form has errors or not:

form:

class WebsiteCheckForm(Form):
    name = StringField('name')
    url = StringField('url')

view:

def handle_admin_form(form):
    if form.name.data:
        if validators.url(form.url.data):
            Website.create(name=form.name.data, url=form.url.data)
        else:
            form.url.errors = ("Please enter a valid URL",)
    else:
        manual_url = form.url.data.replace('http://', '')
        if manual_url:
            form.name.errors = ("Name is required if you want to add a website",)

@application.route('/admin/', methods=['GET', 'POST'])
def admin():
    if current_user.is_authenticated and not current_user.approved:
        return redirect(url_for('awaiting_approval'))

    form = WebsiteCheckForm()
    if request.method == 'GET':
        form.url.data = 'http://'
    db = get_db()
    if request.method == 'POST':
        handle_admin_form(form)
        try:
            delete_websites(request)
            approve_users(request)
        except:
            redirect(url_for('admin'))
        form.url.data = 'http://'
        form.name.data = ''
    websites = Website.query.all()
    users = User.query.all()
    users_needing_approval = [user for user in users if not user.approved]
    return render_template('admin.html', form=form, websites=websites, users_needing_approval=users_needing_approval)

How can I make flask render template pages when I handle a form, and have it say what the error is? Thank you

ANSWER:

This poor behavior can be fixed by this answer exception for non existing parameter in FLASK

#!/usr/bin/env python

from app import application

application.config['TRAP_BAD_REQUEST_ERRORS'] = True

if __name__ == '__main__':
    application.run(debug=True)

which took me to bad logic in the

@login_manager.request_loader
def request_loader(request):

portion of my flask-login setup. A ticket is already open to fix this (the laconic 400 error)

1
Does a GET on /login work correctly?John Gordon
yes, I meant to update it but we went to lunch. I'll put it in the questioncodyc4321
I don't know if this is related to your problem at all, but generally you shouldn't render content in response to a POST request; instead you want to return a redirect to the desired location (in this case, redirect the user to a GET on /login.) See en.wikipedia.org/wiki/Post/Redirect/Get for more details.John Gordon
this allows the site to function, but then how do you render errors on the form? I also don't know why it won't render this time, I have another view that does the same thing (it takes GET or POST, and if the POST fails it adds errors to the form then renders template at the end of the view)codyc4321
The error points out a missing key, a good way to start is by 1. printing out your request and 2. checking the url the post method is using, quite silly but sometimes typos show up. here you can find a bit moreuser5507598

1 Answers

1
votes

I had similar issue during validation of single boolean field.

I solved it by using

remember = form.rememberme.data instead of request.form['remember_me']

ُThis link gave me the idea