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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% 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 %}
+
+
+
+
+
+
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")