From cf4b191144e4dcd3268835356a39eef5821f2f0c Mon Sep 17 00:00:00 2001
From: Darrel O'Pry
Date: Thu, 5 May 2022 15:13:50 -0400
Subject: [PATCH] feat: show secret_key with QR code
- streamline the setup experience for users working with
password managers that cannot read QR codes easily.
Co-authored-by: Claude Paroz
---
CHANGELOG.md | 5 +++++
tests/test_views_setup.py | 6 ++++++
two_factor/templates/two_factor/core/setup.html | 8 ++++++--
two_factor/views/core.py | 8 ++++++++
4 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bde04afb2..ecd8b4ce1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,11 @@
### Removed
- Django 2.2, 3.0, and 3.1 support
+### Changed
+
+- display the TOTP secret key alongside the QR code to streamline setup for
+ password managers without QR support.
+
## 1.14.0
### Added
diff --git a/tests/test_views_setup.py b/tests/test_views_setup.py
index f6573c45f..06b9bb3f5 100644
--- a/tests/test_views_setup.py
+++ b/tests/test_views_setup.py
@@ -38,6 +38,12 @@ def test_setup_only_generator_available(self):
# test if secret key is valid base32 and has the correct number of bytes
secret_key = response.context_data['secret_key']
self.assertEqual(len(b32decode(secret_key)), 20)
+ self.assertEqual(
+ response.context_data['otpauth_url'],
+ f'otpauth://totp/testserver%3A%20bouke%40example.com?secret={secret_key}&digits=6&issuer=testserver'
+ )
+ self.assertEqual(response.context_data['issuer'], 'testserver')
+ self.assertEqual(response.context_data['totp_digits'], 6)
response = self.client.post(
reverse('two_factor:setup'),
diff --git a/two_factor/templates/two_factor/core/setup.html b/two_factor/templates/two_factor/core/setup.html
index 222039094..1596fee00 100644
--- a/two_factor/templates/two_factor/core/setup.html
+++ b/two_factor/templates/two_factor/core/setup.html
@@ -17,9 +17,13 @@ {% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %
{% elif wizard.steps.current == 'generator' %}
{% blocktrans trimmed %}To start using a token generator, please use your
smartphone to scan the QR code below. For example, use Google
- Authenticator. Then, enter the token generated by the app.
- {% endblocktrans %}
+ Authenticator.{% endblocktrans %}
+ {% blocktrans trimmed %}Alternatively you can use the following secret to
+ setup TOTP in your authenticator or password manager manually.{% endblocktrans %}
+ {% translate "TOTP Secret:" %} {{ secret_key }}
+ {% blocktrans %}Then, enter the token generated by the app.{% endblocktrans %}
+
{% elif wizard.steps.current == 'sms' %}
{% blocktrans trimmed %}Please enter the phone number you wish to receive the
text messages on. This number will be validated in the next step.
diff --git a/two_factor/views/core.py b/two_factor/views/core.py
index 1a3ca96c4..d347e8273 100644
--- a/two_factor/views/core.py
+++ b/two_factor/views/core.py
@@ -596,10 +596,18 @@ def get_context_data(self, form, **kwargs):
key = self.get_key('generator')
rawkey = unhexlify(key.encode('ascii'))
b32key = b32encode(rawkey).decode('utf-8')
+ issuer = get_current_site(self.request).name
+ username = self.request.user.get_username()
+ otpauth_url = get_otpauth_url(username, b32key, issuer)
self.request.session[self.session_key_name] = b32key
context.update({
+ # used in default template
+ 'otpauth_url': otpauth_url,
'QR_URL': reverse(self.qrcode_url),
'secret_key': b32key,
+ # available for custom templates
+ 'issuer': issuer,
+ 'totp_digits': totp_digits(),
})
elif self.steps.current == 'validation':
context['device'] = self.get_device()