Reorganize TOTP in the control panel templates to allow adding multiple devices and disabling individual devices

This commit is contained in:
Joshua Tauberer 2020-11-21 10:30:53 -05:00
parent 113b7bd827
commit 30f067bc72
3 changed files with 33 additions and 56 deletions

View File

@ -1740,7 +1740,7 @@ paths:
text/html: text/html:
schema: schema:
type: string type: string
/mfa/totp/enable: /mfa/enable/totp:
post: post:
tags: tags:
- MFA - MFA

View File

@ -468,7 +468,7 @@ def ssl_provision_certs():
def mfa_get_status(): def mfa_get_status():
# Anyone accessing this route is an admin, and we permit them to # Anyone accessing this route is an admin, and we permit them to
# see the MFA status for any user if they submit a 'user' form # see the MFA status for any user if they submit a 'user' form
# field. But we don't include provisioning info since a user can # field. But we don't always include provisioning info since a user can
# only provision for themselves. # only provision for themselves.
email = request.form.get('user', request.user_email) # user field if given, otherwise the user making the request email = request.form.get('user', request.user_email) # user field if given, otherwise the user making the request
try: try:
@ -485,7 +485,7 @@ def mfa_get_status():
return (str(e), 400) return (str(e), 400)
return json_response(resp) return json_response(resp)
@app.route('/mfa/totp/enable', methods=['POST']) @app.route('/mfa/enable/totp', methods=['POST'])
@authorized_personnel_only @authorized_personnel_only
def totp_post_enable(): def totp_post_enable():
secret = request.form.get('secret') secret = request.form.get('secret')

View File

@ -1,34 +1,10 @@
<style> <style>
.twofactor #totp-setup,
.twofactor #disable-2fa,
.twofactor #output-2fa {
display: none;
}
.twofactor.loaded .loading-indicator {
display: none;
}
.twofactor.disabled #disable-2fa,
.twofactor.enabled #totp-setup {
display: none;
}
.twofactor.disabled #totp-setup,
.twofactor.enabled #disable-2fa {
display: block;
}
.twofactor #totp-setup-qr img { .twofactor #totp-setup-qr img {
display: block; display: block;
width: 256px; width: 256px;
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.twofactor #output-2fa.visible {
display: block;
}
</style> </style>
<h2>Two-Factor Authentication</h2> <h2>Two-Factor Authentication</h2>
@ -51,10 +27,11 @@ and ensure every administrator account for this control panel does the same.</st
</div> </div>
<div class="twofactor"> <div class="twofactor">
<div class="loading-indicator">Loading...</div> <div id="mfa-devices">
</div>
<form id="totp-setup"> <form id="totp-setup" style="display: none">
<h3>Setup Instructions</h3> <h3>Add a TOTP Device</h3>
<div class="form-group"> <div class="form-group">
<p>1. Install <a href="https://freeotp.github.io/">FreeOTP</a> or <a href="https://www.pcworld.com/article/3225913/what-is-two-factor-authentication-and-which-2fa-apps-are-best.html">any <p>1. Install <a href="https://freeotp.github.io/">FreeOTP</a> or <a href="https://www.pcworld.com/article/3225913/what-is-two-factor-authentication-and-which-2fa-apps-are-best.html">any
@ -85,24 +62,24 @@ and ensure every administrator account for this control panel does the same.</st
</div> </div>
</form> </form>
<form id="disable-2fa">
<div class="form-group">
<p>Two-factor authentication is active for your account<span id="mfa-device-label"></span>.</p>
<p>You will have to log into the admin panel again after disabling two-factor authentication.</p>
</div>
<div class="form-group">
<button type="submit" class="btn btn-danger">Disable Two-Factor Authentication</button>
</div>
</form>
<div id="output-2fa" class="panel panel-danger"> <div id="output-2fa" class="panel panel-danger hidden">
<div class="panel-body"></div> <div class="panel-body"></div>
</div> </div>
<div id="mfa-device-templates" style="display: none">
<form class="totp" style="margin: 1em 0; border: 1px solid #AAA; padding: 10px;">
<p>Two-factor authentication is active for your account<span class="mfa-device-label"></span>.</p>
<div class="form-group">
<button type="submit" class="btn btn-danger">Disable TOTP Device</button>
</div>
<p style="margin-bottom: 0">You will have to log into the admin panel again after disabling two-factor authentication.</p>
</form>
</div>
</div> </div>
<script> <script>
var el = { var el = {
disableForm: document.getElementById('disable-2fa'),
output: document.getElementById('output-2fa'), output: document.getElementById('output-2fa'),
totpSetupForm: document.getElementById('totp-setup'), totpSetupForm: document.getElementById('totp-setup'),
totpSetupToken: document.getElementById('totp-setup-token'), totpSetupToken: document.getElementById('totp-setup-token'),
@ -130,6 +107,8 @@ and ensure every administrator account for this control panel does the same.</st
} }
function render_totp_setup(provisioned_totp) { function render_totp_setup(provisioned_totp) {
$(el.totpSetupForm).show();
var img = document.createElement('img'); var img = document.createElement('img');
img.src = "data:image/png;base64," + provisioned_totp.qr_code_base64; img.src = "data:image/png;base64," + provisioned_totp.qr_code_base64;
@ -148,35 +127,35 @@ and ensure every administrator account for this control panel does the same.</st
} }
function render_disable(mfa) { function render_disable(mfa) {
el.disableForm.addEventListener('submit', do_disable); var panel = $('#mfa-device-templates .' + mfa.type).clone();
el.wrapper.classList.add('enabled'); $('#mfa-devices').append(panel);
panel.attr('data-mfa-id', mfa.id);
panel.on('submit', do_disable);
if (mfa.label) if (mfa.label)
$("#mfa-device-label").text(" on device '" + mfa.label + "'"); panel.find(".mfa-device-label").text(" on device '" + mfa.label + "'");
} }
function hide_error() { function hide_error() {
el.output.querySelector('.panel-body').innerHTML = ''; el.output.querySelector('.panel-body').innerHTML = '';
el.output.classList.remove('visible'); el.output.classList.add('hidden');
} }
function render_error(msg) { function render_error(msg) {
el.output.querySelector('.panel-body').innerHTML = msg; el.output.querySelector('.panel-body').innerHTML = msg;
el.output.classList.add('visible'); el.output.classList.remove('hidden');
} }
function reset_view() { function reset_view() {
el.wrapper.classList.remove('loaded', 'disabled', 'enabled'); el.wrapper.classList.remove('loaded', 'disabled', 'enabled');
$('#mfa-devices > *').remove();
el.disableForm.removeEventListener('submit', do_disable);
hide_error(); hide_error();
$(el.totpSetupForm).hide();
el.totpSetupForm.reset(); el.totpSetupForm.reset();
el.totpSetupForm.removeEventListener('submit', do_enable_totp); el.totpSetupForm.removeEventListener('submit', do_enable_totp);
el.totpSetupSecret.setAttribute('value', ''); el.totpSetupSecret.setAttribute('value', '');
el.totpSetupToken.removeEventListener('input', update_setup_disabled); el.totpSetupToken.removeEventListener('input', update_setup_disabled);
el.totpSetupSubmit.setAttribute('disabled', ''); el.totpSetupSubmit.setAttribute('disabled', '');
el.totpQr.innerHTML = ''; el.totpQr.innerHTML = '';
} }
@ -191,16 +170,14 @@ and ensure every administrator account for this control panel does the same.</st
function(res) { function(res) {
el.wrapper.classList.add('loaded'); el.wrapper.classList.add('loaded');
var has_mfa = false;
res.enabled_mfa.forEach(function(mfa) { res.enabled_mfa.forEach(function(mfa) {
if (mfa.type == "totp") { if (mfa.type == "totp") {
render_disable(mfa); render_disable(mfa);
has_mfa = true;
} }
}); });
if (!has_mfa) if (res.new_mfa.totp)
render_totp_setup(res.new_mfa.totp); render_totp_setup(res.new_mfa.totp);
} }
); );
} }
@ -212,7 +189,7 @@ and ensure every administrator account for this control panel does the same.</st
api( api(
'/mfa/disable', '/mfa/disable',
'POST', 'POST',
{ type: 'totp' }, { id: $(this).attr('data-mfa-id') },
function() { function() {
do_logout(); do_logout();
} }
@ -226,7 +203,7 @@ and ensure every administrator account for this control panel does the same.</st
hide_error(); hide_error();
api( api(
'/mfa/totp/enable', '/mfa/enable/totp',
'POST', 'POST',
{ {
token: $(el.totpSetupToken).val(), token: $(el.totpSetupToken).val(),