Skip to content

Commit 24dbac8

Browse files
committed
Add link/code timeout to unified signin and two factor email templates.
- us_instructions, two_factor_instructions now include a message stating how long the link is valid - Add configuration variables to change the email template names for unified signin and two factor - Use template variable configs to improve testing
1 parent 2bcedcc commit 24dbac8

11 files changed

Lines changed: 113 additions & 47 deletions

File tree

docs/configuration.rst

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,15 +1353,15 @@ Configuration related to the two-factor authentication feature.
13531353
13541354
Specifies if Flask-Security should enable the two-factor login feature.
13551355

1356-
Default: ``False``.
1356+
Default: ``False``
13571357
.. py:data:: SECURITY_TWO_FACTOR_REQUIRED
13581358
13591359
If set to ``True`` then all users will be required to setup and use two-factor authentication.
13601360
Please see :py:meth:`.UserMixin.check_tf_required` and :ref:`two_factor_configurations:Fine-Grained Control of Two-Factor`
13611361
for ways the application can
13621362
more finely tune which users require two-factor authentication.
13631363

1364-
Default: ``False``.
1364+
Default: ``False``
13651365

13661366
.. versionchanged:: 5.8.0
13671367
Added overridable method that can alter this behavior.
@@ -1375,22 +1375,22 @@ Configuration related to the two-factor authentication feature.
13751375
13761376
Specifies the number of seconds access token is valid.
13771377

1378-
Default: ``120``.
1378+
Default: ``120``
13791379
.. py:data:: SECURITY_TWO_FACTOR_MAIL_VALIDITY
13801380
13811381
Specifies the number of seconds access token is valid.
13821382

1383-
Default: ``300``.
1383+
Default: ``300``
13841384
.. py:data:: SECURITY_TWO_FACTOR_SMS_VALIDITY
13851385
13861386
Specifies the number of seconds access token is valid.
13871387

1388-
Default: ``120``.
1388+
Default: ``120``
13891389
.. py:data:: SECURITY_TWO_FACTOR_SETUP_WITHIN
13901390
13911391
Specifies how long until the setup token expires.
13921392

1393-
Default: ``timedelta(minutes=30)``.
1393+
Default: ``timedelta(minutes=30)``
13941394

13951395
.. versionchanged:: 5.9.0
13961396
Accepted value changed to a timedelta
@@ -1401,7 +1401,7 @@ Configuration related to the two-factor authentication feature.
14011401
Specifies the email address users send mail to when they can't complete the
14021402
two-factor authentication login.
14031403

1404-
Default: ``"no-reply@localhost"``.
1404+
Default: ``"no-reply@localhost"``
14051405

14061406
.. py:data:: SECURITY_EMAIL_SUBJECT_TWO_FACTOR
14071407
@@ -1417,39 +1417,44 @@ Configuration related to the two-factor authentication feature.
14171417
14181418
Specifies the path to the template for the verify code page for the two-factor authentication process.
14191419

1420-
Default: ``"security/two_factor_verify_code.html"``.
1420+
Default: ``"security/two_factor_verify_code.html"``
14211421
.. py:data:: SECURITY_TWO_FACTOR_SETUP_TEMPLATE
14221422
14231423
Specifies the path to the template for the setup page for the two-factor authentication process.
14241424

1425-
Default: ``"security/two_factor_setup.html"``.
1425+
Default: ``"security/two_factor_setup.html"``
14261426

14271427
.. py:data:: SECURITY_TWO_FACTOR_SETUP_URL
14281428
14291429
Specifies the two-factor setup URL.
14301430

1431-
Default: ``"/tf-setup"``.
1431+
Default: ``"/tf-setup"``
14321432
.. py:data:: SECURITY_TWO_FACTOR_TOKEN_VALIDATION_URL
14331433
14341434
Specifies the two-factor token validation URL.
14351435

1436-
Default: ``"/tf-validate"``.
1437-
1436+
Default: ``"/tf-validate"``
14381437
.. py:data:: SECURITY_TWO_FACTOR_RESCUE_URL
14391438
14401439
Specifies the two-factor rescue URL.
14411440

1442-
Default: ``"/tf-rescue"``.
1443-
1441+
Default: ``"/tf-rescue"``
14441442
.. py:data:: SECURITY_TWO_FACTOR_SELECT_URL
14451443
14461444
Specifies the two-factor select URL. This is used when the user has
14471445
setup more than one second factor - see :ref:`webauthn:webauthn`.
14481446

1449-
Default: ``"/tf-select"``.
1447+
Default: ``"/tf-select"``
14501448

14511449
.. versionadded:: 5.0.0
14521450

1451+
.. py:data:: SECURITY_TWO_FACTOR_EMAIL_TEMPLATE
1452+
1453+
Specifies the path to the template for the email providing the code for authentication.
1454+
1455+
Default: ``"two_factor_instructions"``
1456+
1457+
.. versionadded:: 5.9.0
14531458
.. py:data:: SECURITY_TWO_FACTOR_ERROR_VIEW
14541459
14551460
Specifies a URL or endpoint to redirect to if the system detects that
@@ -1475,7 +1480,7 @@ Configuration related to the two-factor authentication feature.
14751480
Specifies the path to the template for the select method page for the two-factor authentication process.
14761481
This is used when more than one two-factor method has been setup (e.g. SMS and Webauthn).
14771482

1478-
Default: ``"security/two_factor_select.html"``.
1483+
Default: ``"security/two_factor_select.html"``
14791484

14801485
.. versionadded:: 5.0.0
14811486

@@ -1486,12 +1491,12 @@ Configuration related to the two-factor authentication feature.
14861491
a code is required. Note that this is cookie based - so a new browser
14871492
session will always require a fresh two-factor code.
14881493

1489-
Default: ``True``.
1494+
Default: ``True``
14901495
.. py:data:: SECURITY_TWO_FACTOR_LOGIN_VALIDITY
14911496
14921497
Specifies the expiration of the two-factor validity cookie and verification of the token.
14931498

1494-
Default: ``timedelta(days=30)``.
1499+
Default: ``timedelta(days=30)``
14951500

14961501
.. versionchanged:: 5.9.0
14971502
Accepted value changed to a timedelta
@@ -1501,7 +1506,7 @@ Configuration related to the two-factor authentication feature.
15011506
15021507
Name for the two factor validity cookie.
15031508

1504-
Default: ``tf_validity``
1509+
Default: ``"tf_validity"``
15051510

15061511
.. versionadded:: 5.9.0
15071512

@@ -1510,7 +1515,7 @@ Configuration related to the two-factor authentication feature.
15101515
A dictionary containing the parameters of the two-factor validity cookie.
15111516
The complete set of parameters is described in Flask's `set_cookie`_ documentation.
15121517

1513-
Default: ``{'httponly': True, 'secure': True, 'samesite': "Strict"}``.
1518+
Default: ``{'httponly': True, 'secure': True, 'samesite': "Strict"}``
15141519

15151520

15161521
.. versionchanged:: 5.9.0
@@ -1613,6 +1618,13 @@ Unified Signin
16131618
16141619
Default: ``"security/us_verify.html"``
16151620

1621+
.. py:data:: SECURITY_US_EMAIL_TEMPLATE
1622+
1623+
Specifies the path to the template for the email providing the code for authentication.
1624+
1625+
Default: ``"us_instructions"``
1626+
1627+
.. versionadded:: 5.9.0
16161628
.. py:data:: SECURITY_US_ENABLED_METHODS
16171629
16181630
Specifies the default enabled methods for unified signin authentication.
@@ -1632,7 +1644,7 @@ Unified Signin
16321644
and :py:data:`SECURITY_TWO_FACTOR_REQUIRED`. Note that even with REQUIRED, only
16331645
methods listed here will trigger a two-factor cycle.
16341646

1635-
Default: ``["password", "email"]``.
1647+
Default: ``["password", "email"]``
16361648

16371649
.. py:data:: SECURITY_US_TOKEN_VALIDITY
16381650
@@ -1650,7 +1662,7 @@ Unified Signin
16501662
16511663
Specifies how long until the setup token expires.
16521664

1653-
Default: ``timedelta(minutes=30)``.
1665+
Default: ``timedelta(minutes=30)``
16541666

16551667
.. versionchanged:: 5.9.0
16561668
Accepted value changed to a timedelta
@@ -1721,13 +1733,13 @@ Change Username
17211733
This feature should be used in conjunction with
17221734
the :py:data:`SECURITY_USERNAME_ENABLE` feature.
17231735

1724-
Default: ``False``.
1736+
Default: ``False``
17251737

17261738
.. py:data:: SECURITY_CHANGE_USERNAME_URL
17271739
17281740
Specifies the change username URL.
17291741

1730-
Default: ``"/change-username"``.
1742+
Default: ``"/change-username"``
17311743

17321744
.. py:data:: SECURITY_POST_CHANGE_USERNAME_VIEW
17331745
@@ -1736,26 +1748,26 @@ Change Username
17361748
If this value is ``None``, the user is redirected to the
17371749
value of :data:`SECURITY_POST_LOGIN_VIEW`.
17381750

1739-
Default: ``None``.
1751+
Default: ``None``
17401752

17411753
.. py:data:: SECURITY_SEND_USERNAME_CHANGE_EMAIL
17421754
17431755
If ``True`` then an email will be sent to the registered user upon
17441756
successful change of their username.
17451757

1746-
Default: ``True``.
1758+
Default: ``True``
17471759

17481760
.. py:data:: SECURITY_EMAIL_SUBJECT_USERNAME_CHANGE_NOTICE
17491761
17501762
Sets subject for the change username email.
17511763

1752-
Default: ``_(""Your username has been changed"")``.
1764+
Default: ``_("Your username has been changed")``
17531765

17541766
.. py:data:: SECURITY_CHANGE_USERNAME_TEMPLATE
17551767
17561768
Specifies the path to the template for the change username page.
17571769

1758-
Default: ``"security/change_username.html"``.
1770+
Default: ``"security/change_username.html"``
17591771

17601772
Additional relevant configuration variables:
17611773

@@ -1891,7 +1903,7 @@ WebAuthn
18911903
18921904
Specifies how long until the passkey registration token expires.
18931905

1894-
Default: ``timedelta(minutes=30)``.
1906+
Default: ``timedelta(minutes=30)``
18951907

18961908
.. versionchanged:: 5.9.0
18971909
Accepted value changed to a timedelta
@@ -1907,7 +1919,7 @@ WebAuthn
19071919
19081920
Specifies how long until the passkey signin token expires.
19091921

1910-
Default: ``timedelta(minutes=1)``.
1922+
Default: ``timedelta(minutes=1)``
19111923

19121924
.. versionchanged:: 5.9.0
19131925
Accepted value changed to a timedelta
@@ -2124,13 +2136,13 @@ Refresh Tokens
21242136
21252137
Specifies if Flask-Security should enable refresh token support.
21262138

2127-
Default: ``False``.
2139+
Default: ``False``
21282140
.. py:data:: SECURITY_REFRESH_TOKEN_URL
21292141
21302142
Specifies the URL used to get new auth_tokens using an existing
21312143
refresh token
21322144

2133-
Default: ``"/refresh-token"``.
2145+
Default: ``"/refresh-token"``
21342146
.. py:data:: SECURITY_REFRESH_TOKEN_MAX_AGE
21352147
21362148
Specifies a timedelta used to compute the refresh tokens expiration date.
@@ -2159,7 +2171,7 @@ Refresh Tokens
21592171
If set then refresh tokens will be returned in a cookie rather than as
21602172
part of the JSON response. Upon logout, the cookie will be deleted.
21612173

2162-
Default: ``fs_refresh``
2174+
Default: ``"fs_refresh"``
21632175

21642176
.. py:data:: SECURITY_REFRESH_TOKEN_COOKIE
21652177
@@ -2247,7 +2259,7 @@ A list of all URLs and Views:
22472259

22482260
Template Paths
22492261
--------------
2250-
A list of all templates:
2262+
The list of all views templates:
22512263

22522264
* :py:data:`SECURITY_FORGOT_PASSWORD_TEMPLATE`
22532265
* :py:data:`SECURITY_LOGIN_USER_TEMPLATE`
@@ -2273,6 +2285,14 @@ A list of all templates:
22732285
* :py:data:`SECURITY_WAN_SIGNIN_TEMPLATE`
22742286
* :py:data:`SECURITY_WAN_VERIFY_TEMPLATE`
22752287

2288+
The list of all email templates:
2289+
2290+
* :py:data:`SECURITY_CHANGE_EMAIL_EMAIL_TEMPLATE` ``"change_email_instructions"``
2291+
* :py:data:`SECURITY_EMAIL_TEMPLATE_CONFIRM` ``"confirmation_instructions"``
2292+
* :py:data:`SECURITY_EMAIL_TEMPLATE_PASSWORD_RESET` ``"reset_instructions"``
2293+
* :py:data:`SECURITY_TWO_FACTOR_EMAIL_TEMPLATE` ``"two_factor_instructions"``
2294+
* :py:data:`SECURITY_US_EMAIL_TEMPLATE` ``"us_instructions"``
2295+
22762296
Messages
22772297
-------------
22782298

flask_security/core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@
258258
"POST_CHANGE_USERNAME_VIEW": None,
259259
"SEND_USERNAME_CHANGE_EMAIL": True,
260260
"TWO_FACTOR_AUTHENTICATOR_VALIDITY": 120,
261+
"TWO_FACTOR_EMAIL_TEMPLATE": "two_factor_instructions",
261262
"TWO_FACTOR_MAIL_VALIDITY": 300,
262263
"TWO_FACTOR_SMS_VALIDITY": 120,
263264
"TWO_FACTOR_ALWAYS_VALIDATE": True,
@@ -375,6 +376,7 @@
375376
"US_MFA_REQUIRED": ["password", "email"],
376377
"US_TOKEN_VALIDITY": 120,
377378
"US_EMAIL_SUBJECT": _("Verification Code"),
379+
"US_EMAIL_TEMPLATE": "us_instructions",
378380
"US_SETUP_WITHIN": timedelta(minutes=30),
379381
"US_SIGNIN_REPLACES_LOGIN": False,
380382
"CACHE_CONTROL": {"private": True, "no-store": True},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<p>{{ _fsdomain("Welcome %(username)s!", username=username) }}</p>
22
<p>{{ _fsdomain("You can log into your account using the following code: %(token)s", token=token) }}</p>
3+
<p>{{ _fsdomain('This code will expire in %(within)s.', within=within) }}</p>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{{ _fsdomain("Welcome %(username)s!", username=username) }}
22

33
{{ _fsdomain("You can log into your account using the following code: %(token)s", token=token) }}
4+
5+
{{ _fsdomain('This code will expire in %(within)s.', within=within) }}

flask_security/templates/security/email/us_instructions.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
construct arbitrary URLs for redirecting.
55
user - the entire user model object
66
username - username
7+
within - how long the link/code is valid
78
security - the Flask-Security configuration
89
#}
910
<p>{{ _fsdomain("Welcome %(username)s!", username=username) }}</p>
10-
<p>{{ _fsdomain("You can sign in to your account using the following code: %(token)s", token=token) }}</p>
11+
<p>{{ _fsdomain("You can sign in to your account using the following code: %(code)s", code=login_token) }}</p>
1112
{% if login_link %}
1213
<p>{{ _fsdomain('Or use this link: <a href="%(login_link)s">Sign in</a>', login_link=login_link)|safe }}</p>
1314
{% endif %}
15+
<p>{{ _fsdomain('This code/link will expire in %(within)s.', within=within) }}</p>

flask_security/templates/security/email/us_instructions.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
construct arbitrary URLs for redirecting.
55
user - the entire user model object
66
username - username
7+
within - how long the link/code is valid
78
security - the Flask-Security configuration
89
#}
910
{{ _fsdomain("Welcome %(username)s!", username=username) }}
10-
{{ _fsdomain("You can sign in to your account using the following code: %(token)s.", token=token) }}
11+
{{ _fsdomain("You can sign in to your account using the following code: %(code)s", code=login_token) }}
1112
{% if login_link %}
1213
{{ _fsdomain("Or use this link: %(login_link)s", login_link=login_link) }}
1314
{% endif %}
15+
{{ _fsdomain('This code/link will expire in %(within)s.', within=within) }}

flask_security/twofactor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from __future__ import annotations
1212

1313
import typing as t
14+
from datetime import timedelta
1415

1516
from flask import current_app, redirect, request, session
1617

@@ -33,6 +34,7 @@
3334
json_error_response,
3435
send_mail,
3536
url_for_security,
37+
td_format,
3638
)
3739
from .signals import (
3840
tf_code_confirmed,
@@ -67,10 +69,11 @@ def tf_send_security_token(user, method, totp_secret, phone_number):
6769
send_mail(
6870
cv("EMAIL_SUBJECT_TWO_FACTOR"),
6971
user.email,
70-
"two_factor_instructions",
72+
cv("TWO_FACTOR_EMAIL_TEMPLATE"),
7173
user=user,
7274
token=token_to_be_sent,
7375
username=user.calc_username(),
76+
within=td_format(timedelta(seconds=cv("TWO_FACTOR_MAIL_VALIDITY"))),
7477
)
7578
elif method == "sms":
7679
m, c = get_message("USE_CODE", code=token_to_be_sent)

0 commit comments

Comments
 (0)