You've already forked snikket-web-portal
Implement password reset flow
This commit is contained in:
@@ -57,6 +57,14 @@ async def view(id_: str) -> str:
|
||||
return await render_template("invite_invalid.html")
|
||||
raise
|
||||
|
||||
if invite.reset_localpart is not None:
|
||||
return await render_template(
|
||||
"invite_reset_view.html",
|
||||
invite=invite,
|
||||
invite_id=id_,
|
||||
account_jid="{}@{}".format(invite.reset_localpart, invite.domain)
|
||||
)
|
||||
|
||||
play_store_url = (
|
||||
"https://play.google.com/store/apps/details?" +
|
||||
urllib.parse.urlencode(
|
||||
@@ -105,7 +113,14 @@ class RegisterForm(flask_wtf.FlaskForm): # type:ignore
|
||||
|
||||
@bp.route("/<id_>/register", methods=["GET", "POST"])
|
||||
async def register(id_: str) -> typing.Union[str, quart.Response]:
|
||||
invite = await client.get_public_invite_by_id(id_)
|
||||
try:
|
||||
invite = await client.get_public_invite_by_id(id_)
|
||||
except aiohttp.ClientResponseError as exc:
|
||||
if exc.status == 404:
|
||||
return redirect(url_for(".view", id_=id_))
|
||||
|
||||
if invite.reset_localpart is not None:
|
||||
return redirect(url_for(".reset", id_=id_))
|
||||
form = RegisterForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
@@ -144,6 +159,66 @@ async def register(id_: str) -> typing.Union[str, quart.Response]:
|
||||
)
|
||||
|
||||
|
||||
class ResetForm(flask_wtf.FlaskForm): # type:ignore
|
||||
password = wtforms.PasswordField(
|
||||
_l("Password"),
|
||||
)
|
||||
|
||||
password_confirm = wtforms.PasswordField(
|
||||
_l("Confirm password"),
|
||||
validators=[wtforms.validators.InputRequired(),
|
||||
wtforms.validators.EqualTo(
|
||||
"password",
|
||||
_l("The passwords must match")
|
||||
)]
|
||||
)
|
||||
|
||||
action_reset = wtforms.SubmitField(
|
||||
_l("Change password")
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<id_>/reset", methods=["GET", "POST"])
|
||||
async def reset(id_: str) -> typing.Union[str, quart.Response]:
|
||||
try:
|
||||
invite = await client.get_public_invite_by_id(id_)
|
||||
except aiohttp.ClientResponseError as exc:
|
||||
if exc.status == 404:
|
||||
return redirect(url_for(".view", id_=id_))
|
||||
|
||||
if invite.reset_localpart is None:
|
||||
return redirect(url_for(".register", id_=id_))
|
||||
|
||||
form = ResetForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
# log the user in? show a guide? no idea.
|
||||
try:
|
||||
jid = await client.register_with_token(
|
||||
username=invite.reset_localpart,
|
||||
password=form.password.data,
|
||||
token=id_,
|
||||
)
|
||||
except aiohttp.ClientResponseError as exc:
|
||||
if exc.status == 403:
|
||||
form.localpart.errors.append(
|
||||
_l("Registration was declined for unknown reasons")
|
||||
)
|
||||
elif exc.status == 404:
|
||||
return redirect(url_for(".view", id_=id_))
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
http_session[INVITE_SESSION_JID] = jid
|
||||
return redirect(url_for(".reset_success"))
|
||||
|
||||
return await render_template(
|
||||
"invite_reset.html",
|
||||
invite=invite,
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/success", methods=["GET", "POST"])
|
||||
async def success() -> str:
|
||||
return await render_template(
|
||||
@@ -152,6 +227,14 @@ async def success() -> str:
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/success/reset", methods=["GET", "POST"])
|
||||
async def reset_success() -> str:
|
||||
return await render_template(
|
||||
"invite_reset_success.html",
|
||||
jid=http_session.get(INVITE_SESSION_JID, ""),
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/-")
|
||||
async def index() -> quart.Response:
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@@ -121,6 +121,8 @@ class AdminGroupInfo:
|
||||
class PublicInviteInfo:
|
||||
inviter: typing.Optional[str]
|
||||
xmpp_uri: str
|
||||
reset_localpart: typing.Optional[str]
|
||||
domain: str
|
||||
|
||||
@classmethod
|
||||
def from_api_response(
|
||||
@@ -130,6 +132,8 @@ class PublicInviteInfo:
|
||||
return cls(
|
||||
inviter=data.get("inviter") or None,
|
||||
xmpp_uri=data["uri"],
|
||||
reset_localpart=data.get("reset", None),
|
||||
domain=data["domain"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
35
snikket_web/templates/invite_reset.html
Normal file
35
snikket_web/templates/invite_reset.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% extends "unauth.html" %}
|
||||
{% from "library.j2" import standard_button, render_errors %}
|
||||
{% block style %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for("static", filename="css/invite.css") }}">
|
||||
{% endblock %}
|
||||
{% block head_lead %}
|
||||
{{ super() }}
|
||||
<title>{% trans %}Reset your password | Snikket{% endtrans %}</title>
|
||||
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<form method="POST"><div class="form layout-expanded">
|
||||
{{- form.csrf_token -}}
|
||||
<h1 class="form-title">{% trans %}Reset your password online{% endtrans %}</h1>
|
||||
<p class="form-desc weak">{% trans %}To reset your password online, fill out the fields below and confirm using the button.{% endtrans %}</p>
|
||||
{%- call render_errors(form.errors) %}{% endcall -%}
|
||||
<div class="f-ebox">
|
||||
{{ form.password.label }}
|
||||
{{ form.password }}
|
||||
</div>
|
||||
<div class="f-ebox">
|
||||
{{ form.password_confirm.label }}
|
||||
{{ form.password_confirm }}
|
||||
</div>
|
||||
<div class="f-bbox">
|
||||
{%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%}
|
||||
</div>
|
||||
</div></form>
|
||||
<script type="text/javascript">
|
||||
var onload = function() {
|
||||
apply_qr_code(document.getElementById("qr-uri"));
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
14
snikket_web/templates/invite_reset_success.html
Normal file
14
snikket_web/templates/invite_reset_success.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "unauth.html" %}
|
||||
{% from "library.j2" import standard_button %}
|
||||
{% block head_lead %}
|
||||
{{ super() }}
|
||||
<title>{% trans %}Password reset successful | Snikket{% endtrans %}</title>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Password reset successful{% endtrans %}</h1>
|
||||
<div class="box success">
|
||||
<header>{% trans %}Your password has been changed{% endtrans %}</header>
|
||||
<p>{% trans %}You can now log in using your new password.{% endtrans %}</p>
|
||||
<p>{% trans %}You can now safely close this page.{% endtrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
36
snikket_web/templates/invite_reset_view.html
Normal file
36
snikket_web/templates/invite_reset_view.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends "unauth.html" %}
|
||||
{% from "library.j2" import standard_button %}
|
||||
{% block style %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for("static", filename="css/invite.css") }}">
|
||||
{% endblock %}
|
||||
{% block head_lead %}
|
||||
{{ super() }}
|
||||
<title>{% trans %}Reset your password | Snikket{% endtrans %}</title>
|
||||
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Reset your password{% endtrans %}</h1>
|
||||
<p>{% trans account_jid=account_jid %}This page allows you to reset the password of your account, <strong>{{ account_jid }}</strong>, once.{% endtrans %}</p>
|
||||
<div class="elevated el-2">
|
||||
<h2>{% trans %}Using the app{% endtrans %}</h2>
|
||||
<p>{% trans %}To reset your password using the Snikket App, tap the button below.{% endtrans %}</p>
|
||||
<div>
|
||||
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="secondary") -%}
|
||||
{% trans %}Open the app{% endtrans %}
|
||||
{%- endcall -%}
|
||||
</div>
|
||||
<img class="float-right" id="tutorial-scan" aria-hidden="true" alt="" src="{{ url_for("static", filename="img/tutorial-scan.png") }}">
|
||||
<p>{% trans %}Alternatively, you can scan the below code with the Snikket App using the Scan button at the top.{% endtrans %}</p>
|
||||
<p>{% trans %}Your camera will turn on. Point it at the square code below until it is within the highlighted square on your screen, and wait until the app recognises it.{% endtrans %}</p>
|
||||
<p>{% trans %}You will then be prompted to enter a new password for your account.{% endtrans %}</p>
|
||||
<div id="qr-uri" data-qrdata="{{ invite.xmpp_uri }}" class="qr"></div>
|
||||
<h2>{% trans %}Alternatives{% endtrans %}</h2>
|
||||
<p>{% trans reset_url=url_for(".reset", id_=invite_id) %}You can also <a href="{{ reset_url }}">reset your password online</a> if the above button or scanning the QR code does not work for you.{% endtrans %}</p>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var onload = function() {
|
||||
apply_qr_code(document.getElementById("qr-uri"));
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -10,7 +10,7 @@
|
||||
<h1>{% trans site_name=config["SITE_NAME"] %}Successfully registered on {{ site_name }}{% endtrans %}</h1>
|
||||
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo-text.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
|
||||
<p>{% trans site_name=config["SITE_NAME"], jid=jid %}Congratulations! You successfully registered on {{ site_name }} as {{ jid }}.{% endtrans %}</p>
|
||||
<input type="text" readonly="readonly" value="{{ jid }}">
|
||||
<label for="address" class="a11y-only">{% trans %}Your address{% endtrans %}</label><input type="text" readonly="readonly" value="{{ jid }}" id="address">
|
||||
{%- call clipboard_button(jid, show_label=True) -%}
|
||||
{% trans %}Copy address{% endtrans %}
|
||||
{%- endcall -%}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{%- if apple_store_url -%}
|
||||
<p>{% trans %}Install the Snikket App on your Android or iOS device.{% endtrans %}</p>
|
||||
{%- else -%}
|
||||
<p>{% trans ios_info_url="https://snikket.org/faq/#is-there-an-ios-app" %}Install the Snikket App on your Android device (<a href="{{ ios_info_url }}" target="_blank" rel="noopener noreferrer">iOS coming soon!</a>).{% endtrans %}</p>
|
||||
<p>{% trans ios_info_url="https://snikket.org/faq/#is-there-an-ios-app" %}Install the Snikket App on your Android device (<a href="{{ ios_info_url }}" rel="noopener noreferrer" target="_blank">iOS coming soon!</a>).{% endtrans %}</p>
|
||||
{%- endif -%}
|
||||
<div class="install-buttons">
|
||||
<ul>
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-29 15:58+0100\n"
|
||||
"POT-Creation-Date: 2021-01-30 10:47+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -101,38 +101,43 @@ msgstr ""
|
||||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:85
|
||||
#: snikket_web/invite.py:93
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:89 snikket_web/main.py:41
|
||||
#: snikket_web/invite.py:97 snikket_web/invite.py:164 snikket_web/main.py:41
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:93
|
||||
#: snikket_web/invite.py:101 snikket_web/invite.py:168
|
||||
msgid "Confirm password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:97
|
||||
#: snikket_web/invite.py:105 snikket_web/invite.py:172
|
||||
msgid "The passwords must match"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:102
|
||||
#: snikket_web/invite.py:110
|
||||
msgid "Create account"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:122
|
||||
#: snikket_web/invite.py:137
|
||||
msgid "That username is already taken"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:126
|
||||
#: snikket_web/invite.py:141 snikket_web/invite.py:205
|
||||
msgid "Registration was declined for unknown reasons"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:130
|
||||
#: snikket_web/invite.py:145
|
||||
msgid "The username is not valid"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:177 snikket_web/templates/user_home.html:32
|
||||
#: snikket_web/templates/user_passwd.html:32
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:36
|
||||
msgid "Address"
|
||||
msgstr ""
|
||||
@@ -717,6 +722,7 @@ msgid "App already installed?"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:16
|
||||
#: snikket_web/templates/invite_reset_view.html:20
|
||||
#: snikket_web/templates/invite_view.html:39
|
||||
msgid "Open the app"
|
||||
msgstr ""
|
||||
@@ -746,6 +752,91 @@ msgstr ""
|
||||
msgid "Enter a secure password that you do not use anywhere else."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset.html:9
|
||||
#: snikket_web/templates/invite_reset_view.html:9
|
||||
msgid "Reset your password | Snikket"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset.html:15
|
||||
msgid "Reset your password online"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset.html:16
|
||||
msgid ""
|
||||
"To reset your password online, fill out the fields below and confirm "
|
||||
"using the button."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_success.html:5
|
||||
msgid "Password reset successful | Snikket"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_success.html:8
|
||||
msgid "Password reset successful"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_success.html:10
|
||||
msgid "Your password has been changed"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_success.html:11
|
||||
msgid "You can now log in using your new password."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_success.html:12
|
||||
#: snikket_web/templates/invite_success.html:18
|
||||
msgid "You can now safely close this page."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:13
|
||||
msgid "Reset your password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:14
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This page allows you to reset the password of your account, "
|
||||
"<strong>%(account_jid)s</strong>, once."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:16
|
||||
msgid "Using the app"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:17
|
||||
msgid "To reset your password using the Snikket App, tap the button below."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:24
|
||||
msgid ""
|
||||
"Alternatively, you can scan the below code with the Snikket App using the"
|
||||
" Scan button at the top."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:25
|
||||
#: snikket_web/templates/invite_view.html:75
|
||||
msgid ""
|
||||
"Your camera will turn on. Point it at the square code below until it is "
|
||||
"within the highlighted square on your screen, and wait until the app "
|
||||
"recognises it."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:26
|
||||
msgid "You will then be prompted to enter a new password for your account."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:28
|
||||
#: snikket_web/templates/invite_view.html:43
|
||||
msgid "Alternatives"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_reset_view.html:29
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You can also <a href=\"%(reset_url)s\">reset your password online</a> if "
|
||||
"the above button or scanning the QR code does not work for you."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:5
|
||||
#, python-format
|
||||
msgid "Successfully registered on %(site_name)s | Snikket"
|
||||
@@ -761,16 +852,16 @@ msgstr ""
|
||||
msgid "Congratulations! You successfully registered on %(site_name)s as %(jid)s."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:13
|
||||
msgid "Your address"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:17
|
||||
msgid ""
|
||||
"You can now set up your legacy XMPP client with the above address and the"
|
||||
" password you chose during registration."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:18
|
||||
msgid "You can now safely close this page."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:6
|
||||
#, python-format
|
||||
msgid "Invite to %(site_name)s | Snikket"
|
||||
@@ -802,8 +893,8 @@ msgstr ""
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Install the Snikket App on your Android device (<a "
|
||||
"href=\"%(ios_info_url)s\" target=\"_blank\" rel=\"noopener "
|
||||
"noreferrer\">iOS coming soon!</a>)."
|
||||
"href=\"%(ios_info_url)s\" rel=\"noopener noreferrer\" "
|
||||
"target=\"_blank\">iOS coming soon!</a>)."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:27
|
||||
@@ -824,10 +915,6 @@ msgid ""
|
||||
"create an account. If not, simply click the button below."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:43
|
||||
msgid "Alternatives"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:44
|
||||
#, python-format
|
||||
msgid ""
|
||||
@@ -872,13 +959,6 @@ msgid ""
|
||||
"'Scan' button at the top."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:75
|
||||
msgid ""
|
||||
"Your camera will turn on. Point it at the square code below until it is "
|
||||
"within the highlighted square on your screen, and wait until the app "
|
||||
"recognises it."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/library.j2:18
|
||||
msgid "Copy link"
|
||||
msgstr ""
|
||||
@@ -932,11 +1012,6 @@ msgstr ""
|
||||
msgid "Edit profile"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_home.html:32
|
||||
#: snikket_web/templates/user_passwd.html:32
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_home.html:38
|
||||
msgid "Your Snikket"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user