221 lines
6.2 KiB
HTML
221 lines
6.2 KiB
HTML
|
<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>
|