Show multifactor setup key in addition to QR code
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index c1689be..aa558c0 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -19,6 +19,7 @@
from __future__ import absolute_import
import logging
import os
+from base64 import b32encode
from datetime import datetime
import re
import warnings
@@ -765,12 +766,14 @@
totp = totp_service.Totp(key)
qr = totp_service.get_qr_code(totp, c.user)
+ key_b32 = b32encode(totp.key).decode('ascii')
h.auditlog_user('Visited multifactor new TOTP page')
provider = plugin.AuthenticationProvider.get(request)
return dict(
menu=provider.account_navigation(),
qr=qr,
+ key_b32=key_b32,
setup=True,
)
@@ -784,11 +787,13 @@
totp_service = TotpService.get()
totp = totp_service.get_totp(c.user)
qr = totp_service.get_qr_code(totp, c.user)
+ key_b32 = b32encode(totp.key).decode('ascii')
h.auditlog_user('Viewed multifactor TOTP config page')
provider = plugin.AuthenticationProvider.get(request)
return dict(
qr=qr,
+ key_b32=key_b32,
setup=False,
menu=provider.account_navigation(),
)
diff --git a/Allura/allura/templates/user_totp.html b/Allura/allura/templates/user_totp.html
index 6ec9bee..ce4ae72 100644
--- a/Allura/allura/templates/user_totp.html
+++ b/Allura/allura/templates/user_totp.html
@@ -45,18 +45,21 @@
</form>
{% endif %}
- <h2>Scan this barcode with your app</h2>
+ <h2>Scan this with your app:</h2>
<img class="qrcode" src="{{ h.base64uri(qr) }}"/>
+ <p style="margin-left:1rem">
+ Or enter setup key: {{ key_b32 }}
+ </p>
{% if setup %}
<h2>Enter the code</h2>
<form method="POST" action="totp_set" id="totp_set">
<p>
- Enter the {{ config['auth.multifactor.totp.length'] }}-digit code to confirm it is set up correctly:<br>
+ Enter the {{ config['auth.multifactor.totp.length'] }}-digit code from the app, to confirm it is set up correctly:<br>
{% if c.form_errors['code'] %}
<span class="fielderror">{{ c.form_errors['code'] }}</span><br>
{% endif %}
- <input type="text" name="code" autofocus autocomplete="off"/>
+ <input type="text" name="code" autofocus autocomplete="off" maxlength="{{ config['auth.multifactor.totp.length']|int + 1 }}"/>
{{ lib.csrf_token() }}
<br>
<input type="submit" value="Submit">
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 05e11e2..3be1355 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -19,6 +19,7 @@
from __future__ import print_function
from __future__ import absolute_import
import calendar
+from base64 import b32encode
from datetime import datetime, time, timedelta
from time import time as time_time
import json
@@ -2389,6 +2390,7 @@
class TestTwoFactor(TestController):
sample_key = b'\x00K\xda\xbfv\xc2B\xaa\x1a\xbe\xa5\x96b\xb2\xa0Z:\xc9\xcf\x8a'
+ sample_b32 = 'ABF5VP3WYJBKUGV6UWLGFMVALI5MTT4K'
def _init_totp(self, username='test-admin'):
user = M.User.query.get(username=username)
@@ -2468,7 +2470,8 @@
with audits('Visited multifactor new TOTP page', user=True):
r.form['password'] = 'foo'
r = r.form.submit()
- assert_in('Scan this barcode', r)
+ assert_in('Scan this', r)
+ assert_in('Or enter setup key: ', r)
first_key_shown = r.session['totp_new_key']
@@ -2477,6 +2480,7 @@
form['code'] = ''
r = form.submit()
assert_in('Invalid', r)
+ assert_in(f'Or enter setup key: {b32encode(first_key_shown).decode()}', r)
assert_equal(first_key_shown, r.session['totp_new_key']) # different keys on each pageload would be bad!
new_totp = TotpService().Totp(r.session['totp_new_key'])
@@ -2513,7 +2517,8 @@
r = r.form.submit()
# confirm warning message, and key is not changed yet
- assert_in('Scan this barcode', r)
+ assert_in('Scan this', r)
+ assert_in('Or enter setup key: ', r)
assert_in('this will invalidate your previous', r)
current_key = TotpService.get().get_secret_key(M.User.query.get(username='test-admin'))
assert_equal(self.sample_key, current_key)
@@ -2760,7 +2765,8 @@
with audits('Viewed multifactor TOTP config page', user=True):
r.form['password'] = 'foo'
r = r.form.submit()
- assert_in('Scan this barcode', r)
+ assert_in('Scan this', r)
+ assert_in(f'Or enter setup key: {self.sample_b32}', r)
def test_view_recovery_codes_and_regen(self):
self._init_totp()