Update API route naming, update setup page

* Rename /two-factor-auth/ => /2fa/
* Nest totp routes under /2fa/totp/
* Update ids and methods in panel to allow for different setup types
This commit is contained in:
Felix Spöttel 2020-09-02 19:41:06 +02:00
parent f205c48564
commit 8597646a12
3 changed files with 56 additions and 54 deletions

View File

@ -412,29 +412,31 @@ def ssl_provision_certs():
# Two Factor Auth # Two Factor Auth
@app.route('/two-factor-auth/status', methods=['GET']) @app.route('/2fa/status', methods=['GET'])
@authorized_personnel_only @authorized_personnel_only
def two_factor_auth_get_status(): def two_factor_auth_get_status():
email, privs = auth_service.authenticate(request, env) email, _ = auth_service.authenticate(request, env)
two_factor_secret, two_factor_token = get_two_factor_info(email, env) two_factor_secret, _ = get_two_factor_info(email, env)
if two_factor_secret != None: if two_factor_secret != None:
return json_response({ 'status': 'on' }) return json_response({
"type": 'totp'
})
secret = totp.get_secret() secret = totp.get_secret()
secret_url = totp.get_otp_uri(secret, email) secret_url = totp.get_otp_uri(secret, email)
secret_qr = totp.get_qr_code(secret_url) secret_qr = totp.get_qr_code(secret_url)
return json_response({ return json_response({
"status": 'off', "type": None,
"secret": secret, "totp_secret": secret,
"qr_code": secret_qr "totp_qr": secret_qr
}) })
@app.route('/two-factor-auth/setup', methods=['POST']) @app.route('/2fa/totp/enable', methods=['POST'])
@authorized_personnel_only @authorized_personnel_only
def two_factor_auth_post_setup(): def totp_post_enable():
email, privs = auth_service.authenticate(request, env) email, _ = auth_service.authenticate(request, env)
secret = request.form.get('secret') secret = request.form.get('secret')
token = request.form.get('token') token = request.form.get('token')
@ -448,10 +450,10 @@ def two_factor_auth_post_setup():
return json_response({ "error": 'token_mismatch' }, 400) return json_response({ "error": 'token_mismatch' }, 400)
@app.route('/two-factor-auth/disable', methods=['POST']) @app.route('/2fa/totp/disable', methods=['POST'])
@authorized_personnel_only @authorized_personnel_only
def two_factor_auth_post_disable(): def totp_post_disable():
email, privs = auth_service.authenticate(request, env) email, _ = auth_service.authenticate(request, env)
remove_two_factor_secret(email, env) remove_two_factor_secret(email, env)
return json_response({}) return json_response({})

View File

@ -136,7 +136,7 @@
{% include "two-factor-auth.html" %} {% include "two-factor-auth.html" %}
</div> </div>
<div id="panel_login" class="admin_panel"> <div id="panel_login" class="admin_panel">
{% include "login.html" %} {% include "login.html" %}
</div> </div>

View File

@ -1,5 +1,5 @@
<style> <style>
.twofactor #setup-2fa, .twofactor #totp-setup,
.twofactor #disable-2fa, .twofactor #disable-2fa,
.twofactor #output-2fa { .twofactor #output-2fa {
display: none; display: none;
@ -10,16 +10,16 @@
} }
.twofactor.disabled #disable-2fa, .twofactor.disabled #disable-2fa,
.twofactor.enabled #setup-2fa { .twofactor.enabled #totp-setup {
display: none; display: none;
} }
.twofactor.disabled #setup-2fa, .twofactor.disabled #totp-setup,
.twofactor.enabled #disable-2fa { .twofactor.enabled #disable-2fa {
display: block; display: block;
} }
.twofactor #qr-2fa img { .twofactor #totp-setup-qr img {
display: block; display: block;
width: 256px; width: 256px;
max-width: 100%; max-width: 100%;
@ -36,23 +36,23 @@
<div class="twofactor"> <div class="twofactor">
<div class="loading-indicator">Loading...</div> <div class="loading-indicator">Loading...</div>
<form id="setup-2fa"> <form id="totp-setup">
<div class="form-group"> <div class="form-group">
<h3>Setup</h3> <h3>Setup</h3>
<p>After enabling two factor authentication, any login to the admin panel will require you to enter a time-limited 6-digit number from an authenticator app after entering your normal credentials.</p> <p>After enabling two factor authentication, any login to the admin panel will require you to enter a time-limited 6-digit number from an authenticator app after entering your normal credentials.</p>
<p>1. Scan the QR code or enter the secret into an authenticator app (e.g. Google Authenticator)</p> <p>1. Scan the QR code or enter the secret into an authenticator app (e.g. Google Authenticator)</p>
<div id="qr-2fa"></div> <div id="totp-setup-qr"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="otp">2. Enter the code displayed in the Authenticator app</label> <label for="otp">2. Enter the code displayed in the Authenticator app</label>
<input type="text" id="setup-otp" class="form-control" placeholder="6-digit code" /> <input type="text" id="totp-setup-token" class="form-control" placeholder="6-digit code" />
</div> </div>
<input type="hidden" id="setup-secret" /> <input type="hidden" id="totp-setup-secret" />
<div class="form-group"> <div class="form-group">
<button id="setup-submit" disabled type="submit" class="btn">Enable two factor authentication</button> <button id="totp-setup-submit" disabled type="submit" class="btn">Enable two factor authentication</button>
</div> </div>
</form> </form>
@ -73,11 +73,11 @@
var el = { var el = {
disableForm: document.getElementById('disable-2fa'), disableForm: document.getElementById('disable-2fa'),
output: document.getElementById('output-2fa'), output: document.getElementById('output-2fa'),
setupForm: document.getElementById('setup-2fa'), totpSetupForm: document.getElementById('totp-setup'),
setupInputOtp: document.getElementById('setup-otp'), totpSetupToken: document.getElementById('totp-setup-token'),
setupInputSecret: document.getElementById('setup-secret'), totpSetupSecret: document.getElementById('totp-setup-secret'),
setupQr: document.getElementById('qr-2fa'), totpQr: document.getElementById('totp-setup-qr'),
setupSubmit: document.querySelector('#setup-2fa button[type="submit"]'), totpSetupSubmit: document.querySelector('#totp-setup-submit'),
wrapper: document.querySelector('.twofactor') wrapper: document.querySelector('.twofactor')
} }
@ -86,35 +86,35 @@
if ( if (
typeof val !== 'string' || typeof val !== 'string' ||
typeof el.setupInputSecret.value !== 'string' || typeof el.totpSetupSecret.value !== 'string' ||
val.length !== 6 || val.length !== 6 ||
el.setupInputSecret.value.length !== 32 || el.totpSetupSecret.value.length !== 32 ||
!(/^\+?\d+$/.test(val)) !(/^\+?\d+$/.test(val))
) { ) {
el.setupSubmit.setAttribute('disabled', ''); el.totpSetupSubmit.setAttribute('disabled', '');
} else { } else {
el.setupSubmit.removeAttribute('disabled'); el.totpSetupSubmit.removeAttribute('disabled');
} }
} }
function render_setup(res) { function render_totp_setup(res) {
function render_qr_code(encoded) { function render_qr_code(encoded) {
var img = document.createElement('img'); var img = document.createElement('img');
img.src = encoded; img.src = encoded;
var code = document.createElement('div'); var code = document.createElement('div');
code.innerHTML = `Secret: ${res.secret}`; code.innerHTML = `Secret: ${res.totp_secret}`;
el.setupQr.appendChild(img); el.totpQr.appendChild(img);
el.setupQr.appendChild(code); el.totpQr.appendChild(code);
} }
el.setupInputOtp.addEventListener('input', update_setup_disabled); el.totpSetupToken.addEventListener('input', update_setup_disabled);
el.setupForm.addEventListener('submit', do_setup); el.totpSetupForm.addEventListener('submit', do_enable_totp);
el.setupInputSecret.setAttribute('value', res.secret); el.totpSetupSecret.setAttribute('value', res.totp_secret);
render_qr_code(res.totp_qr);
render_qr_code(res.qr_code);
el.wrapper.classList.add('disabled'); el.wrapper.classList.add('disabled');
} }
@ -140,27 +140,27 @@
hide_error(); hide_error();
el.setupForm.reset(); el.totpSetupForm.reset();
el.setupForm.removeEventListener('submit', do_setup); el.totpSetupForm.removeEventListener('submit', do_enable_totp);
el.setupInputSecret.setAttribute('value', ''); el.totpSetupSecret.setAttribute('value', '');
el.setupInputOtp.removeEventListener('input', update_setup_disabled); el.totpSetupToken.removeEventListener('input', update_setup_disabled);
el.setupSubmit.setAttribute('disabled', ''); el.totpSetupSubmit.setAttribute('disabled', '');
el.setupQr.innerHTML = ''; el.totpQr.innerHTML = '';
} }
function show_two_factor_auth() { function show_two_factor_auth() {
reset_view(); reset_view();
api( api(
'/two-factor-auth/status', '/2fa/status',
'GET', 'GET',
{}, {},
function(res) { function(res) {
el.wrapper.classList.add('loaded'); el.wrapper.classList.add('loaded');
var isEnabled = res.status === 'on' var isTotpEnabled = res.type === 'totp'
return isEnabled ? render_disable(res) : render_setup(res); return isTotpEnabled ? render_disable(res) : render_totp_setup(res);
} }
); );
} }
@ -170,7 +170,7 @@
hide_error(); hide_error();
api( api(
'/two-factor-auth/disable', '/2fa/totp/disable',
'POST', 'POST',
{}, {},
function() { show_two_factor_auth(); } function() { show_two_factor_auth(); }
@ -179,16 +179,16 @@
return false; return false;
} }
function do_setup(evt) { function do_enable_totp(evt) {
evt.preventDefault(); evt.preventDefault();
hide_error(); hide_error();
api( api(
'/two-factor-auth/setup', '/2fa/totp/enable',
'POST', 'POST',
{ {
token: $(el.setupInputOtp).val(), token: $(el.totpSetupToken).val(),
secret: $(el.setupInputSecret).val() secret: $(el.totpSetupSecret).val()
}, },
function(res) { show_two_factor_auth(); }, function(res) { show_two_factor_auth(); },
function(res) { function(res) {