diff --git a/html/auth-guard.js b/html/auth-guard.js index fa2ff0f..f17e9fb 100644 --- a/html/auth-guard.js +++ b/html/auth-guard.js @@ -39,6 +39,63 @@ .then(function () { window.location.replace('login.html'); }); }; + // Change-password dialog (uses POST /api/auth/password, which requires the + // current password). Available from the top-right pill on any page. + window.wpChangePassword = function () { + if (document.getElementById('wp-pw-modal')) return; + var ov = document.createElement('div'); + ov.id = 'wp-pw-modal'; + ov.style.cssText = 'position:fixed;inset:0;background:rgba(20,30,50,.5);display:flex;align-items:center;' + + 'justify-content:center;z-index:10002;padding:20px;font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;'; + var inp = 'width:100%;padding:9px 10px;margin-bottom:12px;border:1px solid #8d8d8d;border-radius:4px;font-size:14px;'; + var lbl = 'display:block;font-size:12px;color:#525252;margin-bottom:4px;'; + ov.innerHTML = + '
'; + function close() { var m = document.getElementById('wp-pw-modal'); if (m) m.remove(); } + function msg(text, ok) { + var el = document.getElementById('wp-pw-msg'); + el.style.display = 'block'; el.textContent = text; + el.style.background = ok ? '#defbe6' : '#fff1f1'; el.style.color = ok ? '#0e6027' : '#da1e28'; + } + ov.addEventListener('click', function (e) { if (e.target === ov) close(); }); + document.body.appendChild(ov); + document.getElementById('wp-pw-cancel').onclick = close; + document.getElementById('wp-pw-cur').focus(); + document.getElementById('wp-pw-save').onclick = function () { + var cur = document.getElementById('wp-pw-cur').value; + var n1 = document.getElementById('wp-pw-new').value; + var n2 = document.getElementById('wp-pw-new2').value; + if (!cur || !n1) { msg('Please fill in every field.', false); return; } + if (n1.length < 8) { msg('New password must be at least 8 characters.', false); return; } + if (n1 !== n2) { msg('New passwords do not match.', false); return; } + fetch('/api/auth/password', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ current_password: cur, new_password: n1 }) + }) + .then(function (r) { return r.json().catch(function () { return null; }).then(function (j) { return { ok: r.ok, status: r.status, j: j }; }); }) + .then(function (res) { + if (res.ok) { msg('Password updated.', true); setTimeout(close, 1200); } + else { msg((res.j && res.j.detail) || ('Could not update (HTTP ' + res.status + ').'), false); } + }) + .catch(function () { msg('Could not reach the server.', false); }); + }; + }; + function addLogoutPill(user) { if (inIframe) return; // the parent page already shows it if (document.getElementById('wp-logout-pill')) return; @@ -63,6 +120,12 @@ pill.appendChild(sep()); pill.appendChild(adm); } + var pw = document.createElement('a'); + pw.href = '#'; pw.textContent = 'Password'; + pw.style.cssText = 'color:#0f62fe;text-decoration:none;font-weight:600;'; + pw.addEventListener('click', function (e) { e.preventDefault(); window.wpChangePassword(); }); + pill.appendChild(sep()); pill.appendChild(pw); + var out = document.createElement('a'); out.href = '#'; out.textContent = 'Sign out'; out.style.cssText = 'color:#0f62fe;text-decoration:none;font-weight:600;'; diff --git a/html/login.html b/html/login.html index e155089..2b901a2 100644 --- a/html/login.html +++ b/html/login.html @@ -94,6 +94,13 @@ ++ Forgot password? +
+ +Authorized use only ยท BTG / Pilot
diff --git a/html/login.js b/html/login.js index 7dfeccf..7255c28 100644 --- a/html/login.js +++ b/html/login.js @@ -25,6 +25,15 @@ errorBox.classList.add('show'); } + var forgot = document.getElementById('forgot-link'); + if (forgot) { + forgot.addEventListener('click', function (e) { + e.preventDefault(); + var m = document.getElementById('forgot-msg'); + if (m) m.style.display = 'block'; + }); + } + form.addEventListener('submit', function (e) { e.preventDefault(); errorBox.classList.remove('show');