feat: optional 4-digit PIN asked in every new tab (not on reload), on-screen keypad for phones; set, change or remove it in the account; five misses sign out
This commit is contained in:
1 parent
29353e5e6a
commit
119b524825
14 files changed
+316
-1
No files matched your search
@@ -48,6 +48,13 @@
|
||||
' <input type="password" name="new" placeholder="новый пароль, от 8 символов" autocomplete="new-password" minlength="8" required>' +
|
||||
' <input type="password" name="again" placeholder="новый ещё раз" autocomplete="new-password" required>' +
|
||||
' <button type="submit">Сменить</button></form></details>' +
|
||||
' <details class="acct-pin"><summary>PIN-код <small class="acct-pinstate"></small></summary>' +
|
||||
' <form class="acct-pinform">' +
|
||||
' <input type="password" name="current" inputmode="numeric" maxlength="4" pattern="[0-9]{4}" placeholder="текущий PIN" autocomplete="off">' +
|
||||
' <input type="password" name="pin" inputmode="numeric" maxlength="4" pattern="[0-9]{4}" placeholder="новый PIN, 4 цифры" autocomplete="off">' +
|
||||
' <input type="password" name="again" inputmode="numeric" maxlength="4" pattern="[0-9]{4}" placeholder="новый ещё раз" autocomplete="off">' +
|
||||
' <div class="row"><button type="button" class="btn danger small acct-pindel">Убрать PIN</button><button type="submit" class="acct-pinsave">Сохранить</button></div>' +
|
||||
' </form><p class="acct-pinmsg" aria-live="polite"></p><p class="muted" style="font-size:12px;margin:8px 0 0">Спрашивается в каждой новой вкладке; при перезагрузке той же вкладки - нет.</p></details>' +
|
||||
' <form method="post" action="/logout" class="acct-out"><input type="hidden" name="csrf"><button type="submit" class="btn ghost">Выйти</button></form>' +
|
||||
'</div>';
|
||||
document.body.appendChild(dlg);
|
||||
@@ -64,6 +71,11 @@
|
||||
return fetch('/api/panel/account', {cache: 'no-store'}).then(function (r) { return r.json(); }).then(function (d) {
|
||||
$('.acct-name').textContent = d.username;
|
||||
dlg.querySelector('.acct-pwform [name=username]').value = d.username;
|
||||
const pf = dlg.querySelector('.acct-pinform');
|
||||
dlg.querySelector('.acct-pinstate').textContent = d.pin ? '· установлен' : '· не задан';
|
||||
pf.current.hidden = !d.pin;
|
||||
pf.querySelector('.acct-pindel').hidden = !d.pin;
|
||||
pf.querySelector('.acct-pinsave').textContent = d.pin ? 'Сменить' : 'Установить';
|
||||
$('.acct-ava').textContent = (d.username || '?').slice(0, 1).toUpperCase();
|
||||
$('.acct-mode').textContent = d.password_off ? 'вход только по ключу доступа' : d.keys.length ? 'вход по паролю или ключу' : 'вход по паролю';
|
||||
const sw = $('.acct-pw .sw');
|
||||
@@ -134,6 +146,35 @@
|
||||
.catch(function () { msg('ошибка сети', true); })
|
||||
.then(function () { f.querySelector('button').disabled = false; });
|
||||
};
|
||||
function pinMsg(text, bad) {
|
||||
$('.acct-pinmsg').textContent = text || '';
|
||||
$('.acct-pinmsg').className = 'acct-pinmsg' + (bad ? ' bad' : '');
|
||||
}
|
||||
function savePin(pin) {
|
||||
const f = $('.acct-pinform');
|
||||
return fetch('/api/panel/account/pin', {method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({pin: pin, current: f.current.value})})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (r) {
|
||||
if (!r.ok) { pinMsg(r.error, true); return; }
|
||||
f.reset();
|
||||
// This tab stays unlocked: the PIN is for tabs opened from now on.
|
||||
try { sessionStorage.setItem('lpra.pin.ok', '1'); } catch (e) { /* asked next load */ }
|
||||
pinMsg(pin ? 'PIN сохранён' : 'PIN убран');
|
||||
load();
|
||||
})
|
||||
.catch(function () { pinMsg('ошибка сети', true); });
|
||||
}
|
||||
$('.acct-pinform').onsubmit = function (ev) {
|
||||
ev.preventDefault();
|
||||
const f = ev.target;
|
||||
if (!/^[0-9]{4}$/.test(f.pin.value)) { pinMsg('PIN - ровно 4 цифры', true); return; }
|
||||
if (f.pin.value !== f.again.value) { pinMsg('новые PIN не совпадают', true); return; }
|
||||
savePin(f.pin.value);
|
||||
};
|
||||
$('.acct-pindel').onclick = function () {
|
||||
if (confirm('Убрать PIN-код?')) savePin('');
|
||||
};
|
||||
$('.acct-pw .sw').onchange = function (ev) {
|
||||
const off = !ev.target.checked;
|
||||
if (off && !confirm('Выключить вход по паролю? Войти можно будет только ключом доступа. Если ключи потеряются, их сможет сбросить другой администратор в «Настройках».')) {
|
||||
@@ -146,6 +187,7 @@
|
||||
};
|
||||
btn.onclick = function () {
|
||||
msg('');
|
||||
pinMsg('');
|
||||
load().then(function () { dlg.showModal(); dlg.focus(); });
|
||||
};
|
||||
})();
|
||||
Reference in new issue
Block a user