API - display password reset link expiration delay in email

This commit is contained in:
Sam 2020-07-11 19:35:20 +02:00
parent b351f3d42c
commit 075f98e6e5
11 changed files with 49 additions and 15 deletions

View File

@ -194,7 +194,7 @@
<![endif]-->
</head>
<body>
<span class="preheader">Use this link to reset your password. The link is only valid for 24 hours.</span>
<span class="preheader">Use this link to reset your password. The link is only valid for {{ expiration_delay }}.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
@ -214,7 +214,7 @@
<div class="f-fallback">
<h1>Hi {{username}},</h1>
<p>You recently requested to reset your password for your account. Use the button below to reset it.
<strong>This password reset is only valid for the next 24 hours.</strong>
<strong>This password reset link is only valid for {{ expiration_delay }}.</strong>
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>

View File

@ -1,6 +1,6 @@
Hi {{username}},
You recently requested to reset your password for your FitTrackee account. Use the button below to reset it. This password reset is only valid for the next 24 hours.
You recently requested to reset your password for your FitTrackee account. Use the button below to reset it. This password reset link is only valid for {{ expiration_delay }}.
Reset your password ( {{ password_reset_url }} )

View File

@ -194,8 +194,7 @@
<![endif]-->
</head>
<body>
<span class="preheader">Use this link to reset your password. The link is only valid for 24 hours.</span>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant 1 heure.</span>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant {{ expiration_delay }}.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
@ -216,7 +215,7 @@
<h1>Bonjour {{username}},</h1>
<p>Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour le réinitialiser.
<strong>Cette réinitialisation n'est valide que pendant 1 heure.</strong>
<strong>Cette réinitialisation n'est valide que pendant {{ expiration_delay }}.</strong>
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>

View File

@ -1,7 +1,7 @@
Bonjour {{username}},
Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant 1 heure.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant {{ expiration_delay }}.
Réinitialiser le mot de passe: ( {{ password_reset_url }} )

View File

@ -2,7 +2,7 @@
expected_en_text_body = """Hi test,
You recently requested to reset your password for your FitTrackee account. Use the button below to reset it. This password reset is only valid for the next 24 hours.
You recently requested to reset your password for your FitTrackee account. Use the button below to reset it. This password reset link is only valid for 3 seconds.
Reset your password ( http://localhost/password-reset?token=xxx )
@ -14,7 +14,7 @@ The FitTrackee Team"""
expected_fr_text_body = """Bonjour test,
Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant 1 heure.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant 3 secondes.
Réinitialiser le mot de passe: ( http://localhost/password-reset?token=xxx )
@ -25,7 +25,7 @@ Merci,
L'équipe FitTrackee"""
expected_en_html_body = """ <body>
<span class="preheader">Use this link to reset your password. The link is only valid for 24 hours.</span>
<span class="preheader">Use this link to reset your password. The link is only valid for 3 seconds.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
@ -45,7 +45,7 @@ expected_en_html_body = """ <body>
<div class="f-fallback">
<h1>Hi test,</h1>
<p>You recently requested to reset your password for your account. Use the button below to reset it.
<strong>This password reset is only valid for the next 24 hours.</strong>
<strong>This password reset link is only valid for 3 seconds.</strong>
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
@ -99,8 +99,7 @@ expected_en_html_body = """ <body>
</html>"""
expected_fr_html_body = """ <body>
<span class="preheader">Use this link to reset your password. The link is only valid for 24 hours.</span>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant 1 heure.</span>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant 3 secondes.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
@ -121,7 +120,7 @@ expected_fr_html_body = """ <body>
<h1>Bonjour test,</h1>
<p>Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour le réinitialiser.
<strong>Cette réinitialisation n'est valide que pendant 1 heure.</strong>
<strong>Cette réinitialisation n'est valide que pendant 3 secondes.</strong>
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>

View File

@ -32,6 +32,7 @@ class TestEmailMessage:
class TestEmailSending:
email_data = {
'expiration_delay': '3 seconds',
'username': 'test',
'password_reset_url': f'http://localhost/password-reset?token=xxx',
'operating_system': 'Linux',

View File

@ -33,6 +33,7 @@ class TestEmailTemplateForPasswordRequest:
def test_it_gets_text_body(self, app, lang, expected_text_body):
email_template = EmailTemplate(app.config.get('TEMPLATES_FOLDER'))
email_data = {
'expiration_delay': '3 seconds' if lang == 'en' else '3 secondes',
'username': 'test',
'password_reset_url': f'http://localhost/password-reset?token=xxx',
'operating_system': 'Linux',
@ -48,6 +49,7 @@ class TestEmailTemplateForPasswordRequest:
def test_it_gets_en_html_body(self, app):
email_template = EmailTemplate(app.config.get('TEMPLATES_FOLDER'))
email_data = {
'expiration_delay': '3 seconds',
'username': 'test',
'password_reset_url': f'http://localhost/password-reset?token=xxx',
'operating_system': 'Linux',
@ -63,6 +65,7 @@ class TestEmailTemplateForPasswordRequest:
def test_it_gets_fr_html_body(self, app):
email_template = EmailTemplate(app.config.get('TEMPLATES_FOLDER'))
email_data = {
'expiration_delay': '3 secondes',
'username': 'test',
'password_reset_url': f'http://localhost/password-reset?token=xxx',
'operating_system': 'Linux',

View File

@ -14,6 +14,7 @@ from .utils import (
authenticate,
check_passwords,
display_readable_file_size,
get_readable_duration,
register_controls,
verify_extension_and_size,
)
@ -700,6 +701,10 @@ def request_password_reset():
password_reset_token = user.encode_password_reset_token(user.id)
ui_url = current_app.config['UI_URL']
email_data = {
'expiration_delay': get_readable_duration(
current_app.config.get('PASSWORD_TOKEN_EXPIRATION_SECONDS'),
'en' if user.language is None else user.language,
),
'username': user.username,
'password_reset_url': (
f'{ui_url}/password-reset?token={password_reset_token.decode()}' # noqa

View File

@ -1,6 +1,8 @@
import re
from datetime import timedelta
from functools import wraps
import humanize
from flask import current_app, jsonify, request
from .models import User
@ -148,3 +150,12 @@ def display_readable_file_size(size_in_bytes):
return f"{size_in_bytes:3.1f}{unit}"
size_in_bytes /= 1024.0
return f"{size_in_bytes} bytes"
def get_readable_duration(duration, locale='en'):
if locale != 'en':
_t = humanize.i18n.activate(locale) # noqa
readable_duration = humanize.naturaldelta(timedelta(seconds=duration))
if locale != 'en':
humanize.i18n.deactivate()
return readable_duration

View File

@ -322,6 +322,17 @@ gevent = ["gevent (>=0.13)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
[[package]]
category = "main"
description = "Python humanize utilities"
name = "humanize"
optional = false
python-versions = ">=3.5"
version = "2.5.0"
[package.extras]
tests = ["freezegun", "pytest", "pytest-cov"]
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
@ -995,7 +1006,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
content-hash = "4199f7c3f0fe738bf7d846017b57e9903aff0b4697a8570be54a9ed63bd20306"
content-hash = "344f1311eea15fb17b78dea1fe646a994498814a5ff76374d78f37d0643de282"
python-versions = "^3.7"
[metadata.files]
@ -1192,6 +1203,10 @@ gunicorn = [
{file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"},
{file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"},
]
humanize = [
{file = "humanize-2.5.0-py3-none-any.whl", hash = "sha256:89062c6db8601693b7d223443d0d7529aa9577df43a1387ddd4b9c273abb4a51"},
{file = "humanize-2.5.0.tar.gz", hash = "sha256:8a68bd9bccb899fd9bfb1e6d96c1e84e4475551cc9a5b5bdbd69b9b1cfd19c80"},
]
idna = [
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},

View File

@ -18,6 +18,7 @@ pytz = "^2020.1"
python-forecastio = "^1.4"
gunicorn = "^20.0"
tqdm = "^4.42"
humanize = "^2.5.0"
[tool.poetry.dev-dependencies]
pytest = "^5.3"