diff --git a/snikket_web/invite.py b/snikket_web/invite.py index 3aec470..4d988eb 100644 --- a/snikket_web/invite.py +++ b/snikket_web/invite.py @@ -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("//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("//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")) diff --git a/snikket_web/prosodyclient.py b/snikket_web/prosodyclient.py index 919cdd9..192559b 100644 --- a/snikket_web/prosodyclient.py +++ b/snikket_web/prosodyclient.py @@ -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"], ) diff --git a/snikket_web/templates/invite_reset.html b/snikket_web/templates/invite_reset.html new file mode 100644 index 0000000..8c052a5 --- /dev/null +++ b/snikket_web/templates/invite_reset.html @@ -0,0 +1,35 @@ +{% extends "unauth.html" %} +{% from "library.j2" import standard_button, render_errors %} +{% block style %} +{{ super() }} + +{% endblock %} +{% block head_lead %} +{{ super() }} +{% trans %}Reset your password | Snikket{% endtrans %} + +{% endblock %} +{% block content %} +
+ {{- form.csrf_token -}} +

{% trans %}Reset your password online{% endtrans %}

+

{% trans %}To reset your password online, fill out the fields below and confirm using the button.{% endtrans %}

+ {%- call render_errors(form.errors) %}{% endcall -%} +
+ {{ form.password.label }} + {{ form.password }} +
+
+ {{ form.password_confirm.label }} + {{ form.password_confirm }} +
+
+ {%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%} +
+
+ +{% endblock %} diff --git a/snikket_web/templates/invite_reset_success.html b/snikket_web/templates/invite_reset_success.html new file mode 100644 index 0000000..a077ac7 --- /dev/null +++ b/snikket_web/templates/invite_reset_success.html @@ -0,0 +1,14 @@ +{% extends "unauth.html" %} +{% from "library.j2" import standard_button %} +{% block head_lead %} +{{ super() }} +{% trans %}Password reset successful | Snikket{% endtrans %} +{% endblock %} +{% block content %} +

{% trans %}Password reset successful{% endtrans %}

+
+
{% trans %}Your password has been changed{% endtrans %}
+

{% trans %}You can now log in using your new password.{% endtrans %}

+

{% trans %}You can now safely close this page.{% endtrans %}

+
+{% endblock %} diff --git a/snikket_web/templates/invite_reset_view.html b/snikket_web/templates/invite_reset_view.html new file mode 100644 index 0000000..81a85e4 --- /dev/null +++ b/snikket_web/templates/invite_reset_view.html @@ -0,0 +1,36 @@ +{% extends "unauth.html" %} +{% from "library.j2" import standard_button %} +{% block style %} +{{ super() }} + +{% endblock %} +{% block head_lead %} +{{ super() }} +{% trans %}Reset your password | Snikket{% endtrans %} + +{% endblock %} +{% block content %} +

{% trans %}Reset your password{% endtrans %}

+

{% trans account_jid=account_jid %}This page allows you to reset the password of your account, {{ account_jid }}, once.{% endtrans %}

+
+

{% trans %}Using the app{% endtrans %}

+

{% trans %}To reset your password using the Snikket App, tap the button below.{% endtrans %}

+
+ {%- call standard_button("exit_to_app", invite.xmpp_uri, class="secondary") -%} + {% trans %}Open the app{% endtrans %} + {%- endcall -%} +
+ +

{% trans %}Alternatively, you can scan the below code with the Snikket App using the Scan button at the top.{% endtrans %}

+

{% 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 %}

+

{% trans %}You will then be prompted to enter a new password for your account.{% endtrans %}

+
+

{% trans %}Alternatives{% endtrans %}

+

{% trans reset_url=url_for(".reset", id_=invite_id) %}You can also reset your password online if the above button or scanning the QR code does not work for you.{% endtrans %}

+
+ +{% endblock %} diff --git a/snikket_web/templates/invite_success.html b/snikket_web/templates/invite_success.html index 066623b..201118b 100644 --- a/snikket_web/templates/invite_success.html +++ b/snikket_web/templates/invite_success.html @@ -10,7 +10,7 @@

{% trans site_name=config["SITE_NAME"] %}Successfully registered on {{ site_name }}{% endtrans %}

{% trans logo_url=url_for("static", filename="img/snikket-logo-text.svg") %}Powered by Snikket{% endtrans %}

{% trans site_name=config["SITE_NAME"], jid=jid %}Congratulations! You successfully registered on {{ site_name }} as {{ jid }}.{% endtrans %}

- + {%- call clipboard_button(jid, show_label=True) -%} {% trans %}Copy address{% endtrans %} {%- endcall -%} diff --git a/snikket_web/templates/invite_view.html b/snikket_web/templates/invite_view.html index 491d918..d0d35d1 100644 --- a/snikket_web/templates/invite_view.html +++ b/snikket_web/templates/invite_view.html @@ -20,7 +20,7 @@ {%- if apple_store_url -%}

{% trans %}Install the Snikket App on your Android or iOS device.{% endtrans %}

{%- else -%} -

{% trans ios_info_url="https://snikket.org/faq/#is-there-an-ios-app" %}Install the Snikket App on your Android device (iOS coming soon!).{% endtrans %}

+

{% trans ios_info_url="https://snikket.org/faq/#is-there-an-ios-app" %}Install the Snikket App on your Android device (iOS coming soon!).{% endtrans %}

{%- endif -%}
    diff --git a/snikket_web/translations/messages.pot b/snikket_web/translations/messages.pot index 922eb23..1b5259c 100644 --- a/snikket_web/translations/messages.pot +++ b/snikket_web/translations/messages.pot @@ -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 \n" "Language-Team: LANGUAGE \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, " +"%(account_jid)s, 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 reset your password online 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 (iOS coming soon!)." +"href=\"%(ios_info_url)s\" rel=\"noopener noreferrer\" " +"target=\"_blank\">iOS coming soon!)." 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 ""