You've already forked snikket-web-portal
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:
@@ -1,5 +1,6 @@
|
|||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
|
import itertools
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
@@ -9,10 +10,13 @@ from quart import (
|
|||||||
Quart, session, request, render_template, redirect, url_for, Response,
|
Quart, session, request, render_template, redirect, url_for, Response,
|
||||||
current_app,
|
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 .prosodyclient import client
|
||||||
|
|
||||||
from ._version import version, version_info
|
from ._version import version, version_info
|
||||||
@@ -27,6 +31,18 @@ client.default_login_redirect = "login"
|
|||||||
babel = Babel(app)
|
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
|
@babel.localeselector
|
||||||
def selected_locale():
|
def selected_locale():
|
||||||
return request.accept_languages.best_match(
|
return request.accept_languages.best_match(
|
||||||
@@ -39,14 +55,24 @@ async def login():
|
|||||||
if client.has_session and (await client.test_session()):
|
if client.has_session and (await client.test_session()):
|
||||||
return redirect(url_for('user.index'))
|
return redirect(url_for('user.index'))
|
||||||
|
|
||||||
if request.method == "POST":
|
form = LoginForm()
|
||||||
form = await request.form
|
if form.validate_on_submit():
|
||||||
jid = form["address"]
|
jid = form.address.data
|
||||||
password = form["password"]
|
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)
|
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 redirect(url_for('user.index'))
|
||||||
|
|
||||||
return await render_template("login.html")
|
return await render_template("login.html", form=form)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@@ -132,5 +158,12 @@ def proc():
|
|||||||
app.template_filter("repr")(repr)
|
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
|
from .user import user_bp # NOQA
|
||||||
app.register_blueprint(user_bp)
|
app.register_blueprint(user_bp)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% from "library.j2" import box %}
|
||||||
{% set body_id = "login" %}
|
{% set body_id = "login" %}
|
||||||
{% block head_lead %}
|
{% block head_lead %}
|
||||||
<title>{{ _("Snikket Login") }}</title>
|
<title>{{ _("Snikket Login") }}</title>
|
||||||
@@ -12,13 +13,19 @@
|
|||||||
<h1 class="form-title">{{ config["SNIKKET_DOMAIN"] }}</h1>
|
<h1 class="form-title">{{ config["SNIKKET_DOMAIN"] }}</h1>
|
||||||
<p class="form-desc">{{ _("Enter your Snikket address and password to manage your account.") }}</p>
|
<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 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">
|
<div class="f-ebox">
|
||||||
<label for="address" class="a11y-only">{{ _("Address") }}:</label>
|
{{ form.address.label(class="a11y-only") }}
|
||||||
<input type="text" name="address" id="address" required="required" placeholder="{{ _("Address") }}">
|
{{ form.address(placeholder=form.address.label.text) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="f-ebox">
|
<div class="f-ebox">
|
||||||
<label for="password" class="a11y-only">{{ _("Password") }}:</label>
|
{{ form.password.label(class="a11y-only") }}
|
||||||
<input type="password" name="password" id="password" required="required" placeholder="{{ _("Password") }}">
|
{{ form.password(placeholder=form.password.label.text) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="f-bbox">
|
<div class="f-bbox">
|
||||||
<button type="submit" class="primary">{{ _("Log in") }}</button>
|
<button type="submit" class="primary">{{ _("Log in") }}</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user