Convert login page to proper form

- free CSRF protection
- free "empty field" early out
- easier passing on of errors to the view
This commit is contained in:
Jonas Schäfer
2020-03-08 10:59:39 +01:00
parent 280e630b6c
commit 095970adb4
2 changed files with 53 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
import base64
import binascii
import itertools
from datetime import datetime, timedelta
@@ -9,10 +10,13 @@ from quart import (
Quart, session, request, render_template, redirect, url_for, Response,
current_app,
)
import quart.exceptions
from flask_babel import Babel
from flask_wtf import FlaskForm
import wtforms
from flask_babel import Babel, _, lazy_gettext as _l
from . import colour
from . import colour, xmpputil
from .prosodyclient import client
from ._version import version, version_info
@@ -27,6 +31,18 @@ client.default_login_redirect = "login"
babel = Babel(app)
class LoginForm(FlaskForm):
address = wtforms.TextField(
_l("Address"),
validators=[wtforms.validators.InputRequired()],
)
password = wtforms.PasswordField(
_l("Password"),
validators=[wtforms.validators.InputRequired()],
)
@babel.localeselector
def selected_locale():
return request.accept_languages.best_match(
@@ -39,14 +55,24 @@ async def login():
if client.has_session and (await client.test_session()):
return redirect(url_for('user.index'))
if request.method == "POST":
form = await request.form
jid = form["address"]
password = form["password"]
await client.login(jid, password)
return redirect(url_for('user.index'))
form = LoginForm()
if form.validate_on_submit():
jid = form.address.data
localpart, domain, resource = xmpputil.split_jid(jid)
if not localpart:
localpart, domain = domain, current_app.config["SNIKKET_DOMAIN"]
jid = "{}@{}".format(localpart, domain)
password = form.password.data
try:
await client.login(jid, password)
except quart.exceptions.Unauthorized:
form.errors.setdefault("", []).append(
_("Invalid user name or password.")
)
else:
return redirect(url_for('user.index'))
return await render_template("login.html")
return await render_template("login.html", form=form)
@app.route("/")
@@ -132,5 +158,12 @@ def proc():
app.template_filter("repr")(repr)
@app.template_filter("flatten")
def flatten(a, levels=1):
for i in range(levels):
a = itertools.chain(*a)
return a
from .user import user_bp # NOQA
app.register_blueprint(user_bp)

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %}
{% from "library.j2" import box %}
{% set body_id = "login" %}
{% block head_lead %}
<title>{{ _("Snikket Login") }}</title>
@@ -12,13 +13,19 @@
<h1 class="form-title">{{ config["SNIKKET_DOMAIN"] }}</h1>
<p class="form-desc">{{ _("Enter your Snikket address and password to manage your account.") }}</p>
<form method="POST" action="{{ url_for('login') }}" name="login">
{{ form.csrf_token }}
{% if form.errors %}
{% call box("alert", _("Login failed")) %}
<p>{{ form.errors.values() | flatten | join(", ")}}</p>
{% endcall %}
{% endif %}
<div class="f-ebox">
<label for="address" class="a11y-only">{{ _("Address") }}:</label>
<input type="text" name="address" id="address" required="required" placeholder="{{ _("Address") }}">
{{ form.address.label(class="a11y-only") }}
{{ form.address(placeholder=form.address.label.text) }}
</div>
<div class="f-ebox">
<label for="password" class="a11y-only">{{ _("Password") }}:</label>
<input type="password" name="password" id="password" required="required" placeholder="{{ _("Password") }}">
{{ form.password.label(class="a11y-only") }}
{{ form.password(placeholder=form.password.label.text) }}
</div>
<div class="f-bbox">
<button type="submit" class="primary">{{ _("Log in") }}</button>