diff --git a/canaille/__init__.py b/canaille/__init__.py index 85f2be07..70fccdbd 100644 --- a/canaille/__init__.py +++ b/canaille/__init__.py @@ -3,9 +3,10 @@ import os import toml import canaille.admin -import canaille.admin.tokens import canaille.admin.authorizations import canaille.admin.clients +import canaille.admin.mail +import canaille.admin.tokens import canaille.consents import canaille.commands import canaille.oauth @@ -141,6 +142,7 @@ def setup_app(app): canaille.admin.authorizations.bp, url_prefix="/admin/authorization" ) app.register_blueprint(canaille.admin.clients.bp, url_prefix="/admin/client") + app.register_blueprint(canaille.admin.mail.bp, url_prefix="/admin/mail") babel = Babel(app) diff --git a/canaille/account.py b/canaille/account.py index b80fcd37..ac4b5361 100644 --- a/canaille/account.py +++ b/canaille/account.py @@ -104,20 +104,32 @@ def forgotten(): recipient = user.mail base_url = current_app.config.get("URL") or request.url_root - url = base_url + url_for( + reset_url = base_url + url_for( "canaille.account.reset", uid=user.uid[0], hash=profile_hash(user.uid[0], user.userPassword[0]), )[1:] subject = _("Password reset on {website_name}").format( - website_name=current_app.config.get("NAME", url) + website_name=current_app.config.get("NAME", reset_url) + ) + + text_body = render_template( + "mail/reset.txt", + site_name=current_app.config.get("NAME", reset_url), + site_url=current_app.config.get("URL", base_url), + reset_url=reset_url, + ) + html_body = render_template( + "mail/reset.html", + site_name=current_app.config.get("NAME", reset_url), + site_url=current_app.config.get("URL", base_url), + reset_url=reset_url, + logo=current_app.config.get("LOGO"), ) - text_body = _( - "To reset your password on {website_name}, visit the following link :\n{url}" - ).format(website_name=current_app.config.get("NAME", url), url=url) msg = email.message.EmailMessage() msg.set_content(text_body) + msg.add_alternative(html_body, subtype="html") msg["Subject"] = subject msg["From"] = current_app.config["SMTP"]["FROM_ADDR"] msg["To"] = recipient diff --git a/canaille/admin/mail.py b/canaille/admin/mail.py new file mode 100644 index 00000000..a70459d6 --- /dev/null +++ b/canaille/admin/mail.py @@ -0,0 +1,43 @@ +from flask import Blueprint, render_template, current_app, request, url_for +from canaille.flaskutils import admin_needed +from canaille.account import profile_hash + + +bp = Blueprint(__name__, "clients") + + +@bp.route("/reset.html") +@admin_needed() +def reset_html(user): + base_url = current_app.config.get("URL") or request.url_root + reset_url = base_url + url_for( + "canaille.account.reset", + uid=user.uid[0], + hash=profile_hash(user.uid[0], user.userPassword[0]), + )[1:] + + return render_template( + "mail/reset.html", + site_name=current_app.config.get("NAME", reset_url), + site_url=current_app.config.get("URL", base_url), + reset_url=reset_url, + logo=current_app.config.get("LOGO"), + ) + + +@bp.route("/reset.txt") +@admin_needed() +def reset_txt(user): + base_url = current_app.config.get("URL") or request.url_root + reset_url = base_url + url_for( + "canaille.account.reset", + uid=user.uid[0], + hash=profile_hash(user.uid[0], user.userPassword[0]), + )[1:] + + return render_template( + "mail/reset.txt", + site_name=current_app.config.get("NAME", reset_url), + site_url=current_app.config.get("URL", base_url), + reset_url=reset_url, + ) diff --git a/canaille/templates/mail/reset.html b/canaille/templates/mail/reset.html new file mode 100644 index 00000000..b9cd3bc4 --- /dev/null +++ b/canaille/templates/mail/reset.html @@ -0,0 +1,44 @@ + + + + + + + + + +
+

+ {% if logo %} + {{ site_name }} + {% endif %} +
+ {% trans %}Password reinitialisation{% endtrans %} +
+

+ +
+ {% trans %} + Someone, probably you, asked for a password reinitialization link at {{ site_name }}. If you did not asked for this email, please ignore it. I you need to reset your password, please click on the blue button below and follow the instructions. + {% endtrans %} +
+ +
+ {{ site_name }} + {% trans %}Reset password{% endtrans %} +
+
+ + diff --git a/canaille/templates/mail/reset.txt b/canaille/templates/mail/reset.txt new file mode 100644 index 00000000..ff75e2c5 --- /dev/null +++ b/canaille/templates/mail/reset.txt @@ -0,0 +1,6 @@ +# {% trans %}Password reinitialisation{% endtrans %} + +{% trans %}Someone, probably you, asked for a password reinitialization link at {{ site_name }}. If you did not asked for this email, please ignore it. I you need to reset your password, please click on the link below and follow the instructions.{% endtrans %} + +{% trans %}Reset password{% endtrans %}: {{ reset_url }} +{{ site_name }}: {{ site_url }} diff --git a/canaille/translations/babel.cfg b/canaille/translations/babel.cfg index f0234b32..3d53e270 100644 --- a/canaille/translations/babel.cfg +++ b/canaille/translations/babel.cfg @@ -1,3 +1,4 @@ [python: **.py] [jinja2: **/templates/**.html] +[jinja2: **/templates/**.txt] extensions=jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/canaille/translations/fr/LC_MESSAGES/messages.mo b/canaille/translations/fr/LC_MESSAGES/messages.mo index 35bbe806..1c0d6537 100644 Binary files a/canaille/translations/fr/LC_MESSAGES/messages.mo and b/canaille/translations/fr/LC_MESSAGES/messages.mo differ diff --git a/canaille/translations/fr/LC_MESSAGES/messages.po b/canaille/translations/fr/LC_MESSAGES/messages.po index 79615d2f..5e49ffcd 100644 --- a/canaille/translations/fr/LC_MESSAGES/messages.po +++ b/canaille/translations/fr/LC_MESSAGES/messages.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: contact@yaal.fr\n" -"POT-Creation-Date: 2020-10-29 08:31+0100\n" -"PO-Revision-Date: 2020-10-29 08:32+0100\n" +"POT-Creation-Date: 2020-10-29 11:50+0100\n" +"PO-Revision-Date: 2020-10-29 11:50+0100\n" "Last-Translator: Éloi Rivard \n" "Language: fr_FR\n" "Language-Team: French - France \n" @@ -36,7 +36,7 @@ msgstr "Le profil a été mis à jour avec succès." msgid "Could not send the password reset link." msgstr "Impossible d'envoyer le lien de réinitialisation." -#: canaille/account.py:101 canaille/account.py:149 +#: canaille/account.py:101 canaille/account.py:161 msgid "A password reset link has been sent at your email address." msgstr "Un lien de réinitialisation vous a été envoyé à votre adresse." @@ -44,31 +44,23 @@ msgstr "Un lien de réinitialisation vous a été envoyé à votre adresse." msgid "Password reset on {website_name}" msgstr "Réinitialisation du mot de passe sur {website_name}" -#: canaille/account.py:115 -msgid "" -"To reset your password on {website_name}, visit the following link :\n" -"{url}" -msgstr "" -"Pour réinitialiser votre mot de passe sur {website_name}, veuillez vous " -"rendre sur le lien suivant : {url}" - -#: canaille/account.py:144 +#: canaille/account.py:156 msgid "Could not reset your password" msgstr "Impossible de réinitialiser votre mot de passe" -#: canaille/account.py:162 +#: canaille/account.py:174 msgid "The password reset link that brought you here was invalid." msgstr "Le lien de réinitialisation qui vous a amené ici est invalide." -#: canaille/account.py:171 +#: canaille/account.py:183 msgid "Your password has been updated successfuly" msgstr "Votre mot de passe a correctement été mis à jour." -#: canaille/consents.py:28 canaille/tokens.py:29 +#: canaille/consents.py:28 msgid "Could not delete this access" msgstr "Impossible de supprimer cet accès." -#: canaille/consents.py:32 canaille/tokens.py:34 +#: canaille/consents.py:32 msgid "The access has been revoked" msgstr "L'accès a été révoqué." @@ -238,37 +230,31 @@ msgstr "Mon profil" msgid "My consents" msgstr "Mes autorisations" -#: canaille/templates/base.html:47 canaille/templates/token_list.html:18 -msgid "My tokens" -msgstr "Mes jetons" - -#: canaille/templates/base.html:56 +#: canaille/templates/base.html:51 msgid "Clients" msgstr "Clients" -#: canaille/templates/base.html:60 +#: canaille/templates/base.html:55 msgid "Tokens" msgstr "Jetons" -#: canaille/templates/base.html:64 +#: canaille/templates/base.html:59 msgid "Codes" msgstr "Codes" -#: canaille/templates/base.html:68 +#: canaille/templates/base.html:63 msgid "Consents" msgstr "Autorisations" -#: canaille/templates/base.html:75 +#: canaille/templates/base.html:70 msgid "Log out" msgstr "Déconnexion" #: canaille/templates/consent_list.html:21 -#: canaille/templates/token_list.html:21 msgid "Consult and revoke the authorization you gave to websites." msgstr "Consultez et révoquez les autorisation que vous avez données." #: canaille/templates/consent_list.html:47 -#: canaille/templates/token_list.html:47 msgid "From:" msgstr "À partir de :" @@ -277,7 +263,6 @@ msgid "Revoked:" msgstr "Révoqué le :" #: canaille/templates/consent_list.html:52 -#: canaille/templates/token_list.html:51 msgid "Has access to:" msgstr "A accès à :" @@ -286,12 +271,10 @@ msgid "Remove access" msgstr "Supprimer l'accès" #: canaille/templates/consent_list.html:72 -#: canaille/templates/token_list.html:71 msgid "Nothing here" msgstr "Rien ici" #: canaille/templates/consent_list.html:73 -#: canaille/templates/token_list.html:72 msgid "You did not authorize applications yet." msgstr "" "Vous n'avez pas encore autorisé d'application à accéder à votre profil." @@ -368,14 +351,6 @@ msgstr "Éditer" msgid "Password reset" msgstr "Réinitialisation du mot de passe" -#: canaille/templates/token_list.html:48 -msgid "Until:" -msgstr "Jusqu'à :" - -#: canaille/templates/token_list.html:61 -msgid "Remove token" -msgstr "Supprimer le jeton" - #: canaille/templates/admin/authorization_list.html:18 #: canaille/templates/admin/token_list.html:18 msgid "Token" @@ -438,6 +413,46 @@ msgstr "URL" msgid "View a token" msgstr "Voir un jeton" +#: canaille/templates/mail/reset.html:28 canaille/templates/mail/reset.txt:1 +msgid "Password reinitialisation" +msgstr "Réinitialisation du mot de passe" + +#: canaille/templates/mail/reset.html:33 +#, python-format +msgid "" +"\n" +" Someone, probably you, asked for a password reinitialization " +"link at %(site_name)s. If you did not asked for this email, please ignore " +"it. I you need to reset your password, please click on the blue button below " +"and follow the instructions.\n" +" " +msgstr "" +"\n" +" Quelqu'un, probablement vous, a demandé un lien de " +"réinitialisation de votre mot de passe pour le site %(site_name)s. Si vous " +"n'êtes pas à l'origine de ce message, veuillez l'ignorer. Si vous voulez " +"réinitialiser votre mot de passe, veuillez cliquer sur le bouton bleu ci-" +"dessous, et suivre les instructions qui vous seront soumises.\n" +" " + +#: canaille/templates/mail/reset.html:40 canaille/templates/mail/reset.txt:5 +msgid "Reset password" +msgstr "Réinitialiser votre mot de passe" + +#: canaille/templates/mail/reset.txt:3 +#, python-format +msgid "" +"Someone, probably you, asked for a password reinitialization link at " +"%(site_name)s. If you did not asked for this email, please ignore it. I you " +"need to reset your password, please click on the link below and follow the " +"instructions." +msgstr "" +"Quelqu'un, probablement vous, a demandé un lien de réinitialisation de votre " +"mot de passe pour le site %(site_name)s. Si vous n'êtes pas à l'origine de " +"ce message, veuillez l'ignorer. Si vous voulez réinitialiser votre mot de " +"passe, veuillez cliquer sur le lien ci-dessous, et suivre les instructions " +"qui vous seront soumises." + #~ msgid "Logged in as" #~ msgstr "Connecté en tant que" @@ -449,3 +464,19 @@ msgstr "Voir un jeton" #~ msgid "OpenID Connect LDAP Bridge" #~ msgstr "OpendID Connect LDAP Bridge" + +#~ msgid "" +#~ "To reset your password on {website_name}, visit the following link :\n" +#~ "{url}" +#~ msgstr "" +#~ "Pour réinitialiser votre mot de passe sur {website_name}, veuillez vous " +#~ "rendre sur le lien suivant : {url}" + +#~ msgid "My tokens" +#~ msgstr "Mes jetons" + +#~ msgid "Until:" +#~ msgstr "Jusqu'à :" + +#~ msgid "Remove token" +#~ msgstr "Supprimer le jeton" diff --git a/canaille/translations/messages.pot b/canaille/translations/messages.pot index 5cb9fd3f..1af40388 100644 --- a/canaille/translations/messages.pot +++ b/canaille/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: 2020-10-29 08:31+0100\n" +"POT-Creation-Date: 2020-10-29 11:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -33,7 +33,7 @@ msgstr "" msgid "Could not send the password reset link." msgstr "" -#: canaille/account.py:101 canaille/account.py:149 +#: canaille/account.py:101 canaille/account.py:161 msgid "A password reset link has been sent at your email address." msgstr "" @@ -41,29 +41,23 @@ msgstr "" msgid "Password reset on {website_name}" msgstr "" -#: canaille/account.py:115 -msgid "" -"To reset your password on {website_name}, visit the following link :\n" -"{url}" -msgstr "" - -#: canaille/account.py:144 +#: canaille/account.py:156 msgid "Could not reset your password" msgstr "" -#: canaille/account.py:162 +#: canaille/account.py:174 msgid "The password reset link that brought you here was invalid." msgstr "" -#: canaille/account.py:171 +#: canaille/account.py:183 msgid "Your password has been updated successfuly" msgstr "" -#: canaille/consents.py:28 canaille/tokens.py:29 +#: canaille/consents.py:28 msgid "Could not delete this access" msgstr "" -#: canaille/consents.py:32 canaille/tokens.py:34 +#: canaille/consents.py:32 msgid "The access has been revoked" msgstr "" @@ -233,37 +227,31 @@ msgstr "" msgid "My consents" msgstr "" -#: canaille/templates/base.html:47 canaille/templates/token_list.html:18 -msgid "My tokens" -msgstr "" - -#: canaille/templates/base.html:56 +#: canaille/templates/base.html:51 msgid "Clients" msgstr "" -#: canaille/templates/base.html:60 +#: canaille/templates/base.html:55 msgid "Tokens" msgstr "" -#: canaille/templates/base.html:64 +#: canaille/templates/base.html:59 msgid "Codes" msgstr "" -#: canaille/templates/base.html:68 +#: canaille/templates/base.html:63 msgid "Consents" msgstr "" -#: canaille/templates/base.html:75 +#: canaille/templates/base.html:70 msgid "Log out" msgstr "" #: canaille/templates/consent_list.html:21 -#: canaille/templates/token_list.html:21 msgid "Consult and revoke the authorization you gave to websites." msgstr "" #: canaille/templates/consent_list.html:47 -#: canaille/templates/token_list.html:47 msgid "From:" msgstr "" @@ -272,7 +260,6 @@ msgid "Revoked:" msgstr "" #: canaille/templates/consent_list.html:52 -#: canaille/templates/token_list.html:51 msgid "Has access to:" msgstr "" @@ -281,12 +268,10 @@ msgid "Remove access" msgstr "" #: canaille/templates/consent_list.html:72 -#: canaille/templates/token_list.html:71 msgid "Nothing here" msgstr "" #: canaille/templates/consent_list.html:73 -#: canaille/templates/token_list.html:72 msgid "You did not authorize applications yet." msgstr "" @@ -356,14 +341,6 @@ msgstr "" msgid "Password reset" msgstr "" -#: canaille/templates/token_list.html:48 -msgid "Until:" -msgstr "" - -#: canaille/templates/token_list.html:61 -msgid "Remove token" -msgstr "" - #: canaille/templates/admin/authorization_list.html:18 #: canaille/templates/admin/token_list.html:18 msgid "Token" @@ -426,3 +403,31 @@ msgstr "" msgid "View a token" msgstr "" +#: canaille/templates/mail/reset.html:28 canaille/templates/mail/reset.txt:1 +msgid "Password reinitialisation" +msgstr "" + +#: canaille/templates/mail/reset.html:33 +#, python-format +msgid "" +"\n" +" Someone, probably you, asked for a password " +"reinitialization link at %(site_name)s. If you did not asked for this " +"email, please ignore it. I you need to reset your password, please click " +"on the blue button below and follow the instructions.\n" +" " +msgstr "" + +#: canaille/templates/mail/reset.html:40 canaille/templates/mail/reset.txt:5 +msgid "Reset password" +msgstr "" + +#: canaille/templates/mail/reset.txt:3 +#, python-format +msgid "" +"Someone, probably you, asked for a password reinitialization link at " +"%(site_name)s. If you did not asked for this email, please ignore it. I " +"you need to reset your password, please click on the link below and " +"follow the instructions." +msgstr "" + diff --git a/tests/test_mail_admin.py b/tests/test_mail_admin.py new file mode 100644 index 00000000..f0fbd68b --- /dev/null +++ b/tests/test_mail_admin.py @@ -0,0 +1,5 @@ +def test_reset_html(testclient, logged_admin): + testclient.get("/admin/mail/reset.html") + +def test_reset_txt(testclient, logged_admin): + testclient.get("/admin/mail/reset.txt")