diff --git a/docs/configuration.rst b/docs/configuration.rst
index e6874673..4ef2f86b 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -1557,7 +1557,9 @@ Username-Recovery
.. py:data:: SECURITY_USERNAME_RECOVERY
- Specifies whether username recovery is enabled.
+ Specifies whether username recovery is enabled. If set to ``True`` the UserModel
+ must contain a column ``"username"``. Note that this feature is independent
+ of the :py:data:`SECURITY_USERNAME_ENABLE` feature.
Default: ``False``.
@@ -1888,7 +1890,7 @@ Social Oauth
Feature Flags
-------------
-All feature flags. By default all are 'False'/not enabled.
+All feature flags. By default all are ``False``/not enabled.
* :py:data:`SECURITY_CHANGE_EMAIL`
* :py:data:`SECURITY_CONFIRMABLE`
@@ -1908,23 +1910,23 @@ URLs and Views
--------------
A list of all URLs and Views:
-* :py:data:`SECURITY_LOGIN_URL`
-* :py:data:`SECURITY_LOGOUT_URL`
-* :py:data:`SECURITY_VERIFY_URL`
-* :py:data:`SECURITY_REGISTER_URL`
-* :py:data:`SECURITY_CHANGE_EMAIL_URL`
-* :py:data:`SECURITY_CHANGE_EMAIL_CONFIRM_URL`
-* :py:data:`SECURITY_RESET_URL`
-* :py:data:`SECURITY_CHANGE_URL`
-* :py:data:`SECURITY_CONFIRM_URL`
-* :py:data:`SECURITY_MULTI_FACTOR_RECOVERY_CODES_URL`
-* :py:data:`SECURITY_MULTI_FACTOR_RECOVERY_URL`
-* :py:data:`SECURITY_OAUTH_START_URL`
-* :py:data:`SECURITY_OAUTH_RESPONSE_URL`
-* :py:data:`SECURITY_TWO_FACTOR_SELECT_URL`
-* :py:data:`SECURITY_TWO_FACTOR_SETUP_URL`
-* :py:data:`SECURITY_TWO_FACTOR_TOKEN_VALIDATION_URL`
-* :py:data:`SECURITY_TWO_FACTOR_RESCUE_URL`
+* :py:data:`SECURITY_LOGIN_URL` ``"/login"``
+* :py:data:`SECURITY_LOGOUT_URL` ``"/logout"``
+* :py:data:`SECURITY_VERIFY_URL` ``"/verify"``
+* :py:data:`SECURITY_REGISTER_URL` ``"/register"``
+* :py:data:`SECURITY_CHANGE_EMAIL_URL` ``"change-email"``
+* :py:data:`SECURITY_CHANGE_EMAIL_CONFIRM_URL` ``"/change-email-confirm"``
+* :py:data:`SECURITY_RESET_URL` ``"/reset"``
+* :py:data:`SECURITY_CHANGE_URL` ``"/change"``
+* :py:data:`SECURITY_CONFIRM_URL` ``"/confirm"``
+* :py:data:`SECURITY_MULTI_FACTOR_RECOVERY_CODES_URL` ``"/mf-recovery-codes"``
+* :py:data:`SECURITY_MULTI_FACTOR_RECOVERY_URL` ``"/mf-recovery"``
+* :py:data:`SECURITY_OAUTH_START_URL` ``"/login/oauthstart"``
+* :py:data:`SECURITY_OAUTH_RESPONSE_URL` ``"/login/oauthresponse"``
+* :py:data:`SECURITY_TWO_FACTOR_SELECT_URL` ``"/tf-select"``
+* :py:data:`SECURITY_TWO_FACTOR_SETUP_URL` ``"/tf-setup"``
+* :py:data:`SECURITY_TWO_FACTOR_TOKEN_VALIDATION_URL` ``"/tf-validate"``
+* :py:data:`SECURITY_TWO_FACTOR_RESCUE_URL` ``"/tf-rescue"``
* :py:data:`SECURITY_TWO_FACTOR_ERROR_VIEW`
* :py:data:`SECURITY_TWO_FACTOR_POST_SETUP_VIEW`
* :py:data:`SECURITY_POST_LOGIN_VIEW`
diff --git a/docs/customizing.rst b/docs/customizing.rst
index e579bd0b..3e833d19 100644
--- a/docs/customizing.rst
+++ b/docs/customizing.rst
@@ -478,6 +478,7 @@ welcome_existing SECURITY_SEND_REGISTER_EMAIL SECURITY_EM
welcome_existing_username SECURITY_SEND_REGISTER_EMAIL SECURITY_EMAIL_SUBJECT_REGISTER - email user_not_registered
SECURITY_RETURN_GENERIC_RESPONSES - username
username_recovery SECURITY_USERNAME_RECOVERY SECURITY_EMAIL_SUBJECT_USERNAME_RECOVERY - user username_recovery_email_sent
+ - username
============================= ================================== ============================================= ====================== ===============================
When sending an email, Flask-Security goes through the following steps:
diff --git a/docs/features.rst b/docs/features.rst
index 99ccc686..c0a82571 100644
--- a/docs/features.rst
+++ b/docs/features.rst
@@ -120,11 +120,20 @@ This view can be overridden if your registration process requires more fields.
User email is validated and normalized using the
`email_validator `_ package.
-The :py:data:`SECURITY_USERNAME_ENABLE` configuration option, when set to ``True``, will add
-support for the user to register a username in addition to an email. By default, the user will be
+Username Support
+-----------------
+Flask-Security supports configuring and using a ``username`` is addition to or instead of an email for
+authentication.
+
+If the :py:data:`SECURITY_USERNAME_ENABLE` configuration option is set to ``True``, ``username``
+will be added to the register and login forms.
+By default, the user will be
able to authenticate with EITHER email or username - however that can be changed via the
:py:data:`SECURITY_USER_IDENTITY_ATTRIBUTES`.
+The :py:data:`SECURITY_USERNAME_RECOVERY` option adds an endpoint that allows users
+to recover a forgotten username (via email).
+
Email Confirmation
------------------
If :ref:`configured`, your application
@@ -185,8 +194,6 @@ generated and downloaded one-time code (see :py:data:`SECURITY_MULTI_FACTOR_RECO
Unified Sign In
---------------
-**This feature is in Beta - mostly due to it being brand new and little to no production soak time**
-
If :ref:`configured`,
a generalized login endpoint is provided that takes an `identity`
and a `passcode`; where (based on configuration):
@@ -260,24 +267,10 @@ JSON/Ajax Support
-----------------
Flask-Security supports JSON/Ajax requests where appropriate. Please
look at :ref:`csrf_topic` for details on how to work with JSON and
-Single Page Applications. More specifically
-JSON is supported for the following operations:
-
-* Login requests
-* Unified sign in requests
-* Registration requests
-* Change password requests
-* Change email requests
-* Confirmation requests
-* Forgot password requests
-* Passwordless login requests
-* Two-factor login requests
-* Change two-factor method requests
-* WebAuthn registration and signin requests
-* Two-Factor recovery code requests
-
-In addition, Single-Page-Applications (like those built with Vue, Angular, and
-React) are supported via customizable redirect links.
+Single Page Applications.
+
+In addition, :ref:`spa:Working With Single Page Applications`
+(like those built with Vue, Angular, and React) are supported via customizable redirect links.
Note: All registration requests done through JSON/Ajax utilize the ``confirm_register_form``.
@@ -315,7 +308,7 @@ in the `examples` directory.
.. _Click: https://palletsprojects.com/p/click/
.. _Flask-Login: https://flask-login.readthedocs.org/en/latest/
-.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/1.0.x/csrf/
+.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/1.2.x/csrf/
.. _alternative token: https://flask-login.readthedocs.io/en/latest/#alternative-tokens
.. _Flask-Principal: https://pypi.org/project/Flask-Principal/
.. _documentation on this topic: http://packages.python.org/Flask-Principal/#granular-resource-protection
diff --git a/flask_security/core.py b/flask_security/core.py
index 2efd9ce1..0a0e99ae 100644
--- a/flask_security/core.py
+++ b/flask_security/core.py
@@ -1405,6 +1405,7 @@ def __init__(
self.two_factor: bool = False
self.unified_signin: bool = False
self.passwordless: bool = False
+ self.username_recovery: bool = False
self.webauthn: bool = False
self.support_mfa: bool = False
diff --git a/flask_security/views.py b/flask_security/views.py
index c280ee82..d0d1a7a0 100644
--- a/flask_security/views.py
+++ b/flask_security/views.py
@@ -1289,7 +1289,6 @@ def create_blueprint(app, state, import_name):
if state.recoverable:
reset_url = cv("RESET_URL", app=app)
- username_recovery_url = cv("USERNAME_RECOVERY_URL", app=app)
bp.route(reset_url, methods=["GET", "POST"], endpoint="forgot_password")(
forgot_password
)
@@ -1298,12 +1297,14 @@ def create_blueprint(app, state, import_name):
methods=["GET", "POST"],
endpoint="reset_password",
)(reset_password)
- if cv("USERNAME_RECOVERY", app=app):
- bp.route(
- username_recovery_url,
- methods=["GET", "POST"],
- endpoint="recover_username",
- )(recover_username)
+
+ if state.username_recovery:
+ username_recovery_url = cv("USERNAME_RECOVERY_URL", app=app)
+ bp.route(
+ username_recovery_url,
+ methods=["GET", "POST"],
+ endpoint="recover_username",
+ )(recover_username)
if state.changeable:
bp.route(