<style> .twofactor #setup-2fa, .twofactor #disable-2fa, .twofactor #output-2fa { display: none; } .twofactor.loaded .loading-indicator { display: none; } .twofactor.disabled #disable-2fa, .twofactor.enabled #setup-2fa { display: none; } .twofactor.disabled #setup-2fa, .twofactor.enabled #disable-2fa { display: block; } .twofactor #qr-2fa img { display: block; width: 256px; max-width: 100%; height: auto; } .twofactor #output-2fa.visible { display: block; } </style> <h2>Two Factor Authentication</h2> <div class="twofactor"> <div class="loading-indicator">Loading...</div> <form id="setup-2fa"> <div class="form-group"> <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>1. Scan the QR code or enter the secret into an authenticator app (e.g. Google Authenticator)</p> <div id="qr-2fa"></div> </div> <div class="form-group"> <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" /> </div> <input type="hidden" id="setup-secret" /> <div class="form-group"> <button id="setup-submit" disabled type="submit" class="btn">Enable two factor authentication</button> </div> </form> <form id="disable-2fa"> <div class="form-group"> <p>Two factor authentication is active.</p> </div> <button type="submit" class="btn btn-danger">Disable two factor authentication</button> </form> <div id="output-2fa" class="panel panel-danger"> <div class="panel-body"></div> </div> </div> <script> var el = { disableForm: document.getElementById('disable-2fa'), output: document.getElementById('output-2fa'), setupForm: document.getElementById('setup-2fa'), setupInputOtp: document.getElementById('setup-otp'), setupInputSecret: document.getElementById('setup-secret'), setupQr: document.getElementById('qr-2fa'), setupSubmit: document.querySelector('#setup-2fa button[type="submit"]'), wrapper: document.querySelector('.twofactor') } function update_setup_disabled(evt) { var val = evt.target.value.trim(); if ( typeof val !== 'string' || typeof el.setupInputSecret.value !== 'string' || val.length !== 6 || el.setupInputSecret.value.length !== 32 || !(/^\+?\d+$/.test(val)) ) { el.setupSubmit.setAttribute('disabled', ''); } else { el.setupSubmit.removeAttribute('disabled'); } } function render_setup(res) { function render_qr_code(encoded) { var img = document.createElement('img'); img.src = encoded; var code = document.createElement('div'); code.innerHTML = `Secret: ${res.secret}`; el.setupQr.appendChild(img); el.setupQr.appendChild(code); } el.setupInputOtp.addEventListener('input', update_setup_disabled); el.setupForm.addEventListener('submit', do_setup); el.setupInputSecret.setAttribute('value', res.secret); render_qr_code(res.qr_code); el.wrapper.classList.add('disabled'); } function render_disable() { el.disableForm.addEventListener('submit', do_disable); el.wrapper.classList.add('enabled'); } function hide_error() { el.output.querySelector('.panel-body').innerHTML = ''; el.output.classList.remove('visible'); } function render_error(msg) { el.output.querySelector('.panel-body').innerHTML = msg; el.output.classList.add('visible'); } function reset_view() { el.wrapper.classList.remove('loaded', 'disabled', 'enabled'); el.disableForm.removeEventListener('submit', do_disable); hide_error(); el.setupForm.reset(); el.setupForm.removeEventListener('submit', do_setup); el.setupInputSecret.setAttribute('value', ''); el.setupInputOtp.removeEventListener('input', update_setup_disabled); el.setupSubmit.setAttribute('disabled', ''); el.setupQr.innerHTML = ''; } function show_two_factor_auth() { reset_view(); api( '/two-factor-auth/status', 'GET', {}, function(res) { el.wrapper.classList.add('loaded'); var isEnabled = res.status === 'on' return isEnabled ? render_disable(res) : render_setup(res); } ); } function do_disable(evt) { evt.preventDefault(); hide_error(); api( '/two-factor-auth/disable', 'POST', {}, function() { show_two_factor_auth(); } ); return false; } function do_setup(evt) { evt.preventDefault(); hide_error(); api( '/two-factor-auth/setup', 'POST', { token: $(el.setupInputOtp).val(), secret: $(el.setupInputSecret).val() }, function(res) { show_two_factor_auth(); }, function(res) { var errorMessage = 'Something went wrong.'; var parsed; try { parsed = JSON.parse(res); } catch (err) { return render_error(errorMessage); } var error = parsed && parsed.error ? parsed.error : null; if (error === 'token_mismatch') { errorMessage = 'Code does not match.'; } else if (error === 'bad_input') { errorMessage = 'Received request with malformed data.'; } render_error(errorMessage); } ); return false; } </script>