Admin login: ignore surrounding spaces/quotes in a pasted password; show-password toggle
This commit is contained in:
@@ -468,7 +468,10 @@
|
|||||||
<h2>Sign in</h2>
|
<h2>Sign in</h2>
|
||||||
<p class="muted">Admin password</p>
|
<p class="muted">Admin password</p>
|
||||||
<form id="loginForm" class="row">
|
<form id="loginForm" class="row">
|
||||||
<input class="field" type="password" id="pw" placeholder="Password" autocomplete="current-password" />
|
<span style="position:relative;flex:1;display:flex;min-width:0">
|
||||||
|
<input class="field" type="password" id="pw" placeholder="Password" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" style="flex:1;padding-right:48px" />
|
||||||
|
<button type="button" id="pwShow" aria-label="Show password" aria-pressed="false" style="position:absolute;right:4px;top:50%;transform:translateY(-50%);width:40px;height:40px;border:0;background:none;color:inherit;opacity:.7;cursor:pointer"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||||
|
</span>
|
||||||
<button class="btn pri" type="submit">Sign in</button>
|
<button class="btn pri" type="submit">Sign in</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="loginMsg" class="msg"></div>
|
<div id="loginMsg" class="msg"></div>
|
||||||
@@ -948,11 +951,20 @@
|
|||||||
$('loginForm').addEventListener('submit', async (e) => {
|
$('loginForm').addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('loginMsg').className = 'msg'; $('loginMsg').textContent = 'Signing in…';
|
$('loginMsg').className = 'msg'; $('loginMsg').textContent = 'Signing in…';
|
||||||
const j = await api('/api/admin/login', { method: 'POST', body: { password: $('pw').value } });
|
// Pasted from a chat or note: drop surrounding spaces and a wrapping pair of
|
||||||
|
// backticks or quotes, which are never part of the password.
|
||||||
|
const password = $('pw').value.trim().replace(/^([`'"\u2018\u201C])(.*)([`'"\u2019\u201D])$/s, '$2');
|
||||||
|
const j = await api('/api/admin/login', { method: 'POST', body: { password } });
|
||||||
if (!j.ok) { $('loginMsg').className = 'msg err'; $('loginMsg').textContent = j.error || 'failed'; return; }
|
if (!j.ok) { $('loginMsg').className = 'msg err'; $('loginMsg').textContent = j.error || 'failed'; return; }
|
||||||
$('pw').value = ''; $('loginMsg').textContent = '';
|
$('pw').value = ''; $('loginMsg').textContent = '';
|
||||||
boot();
|
boot();
|
||||||
});
|
});
|
||||||
|
$('pwShow').addEventListener('click', () => {
|
||||||
|
const show = $('pw').type === 'password';
|
||||||
|
$('pw').type = show ? 'text' : 'password';
|
||||||
|
$('pwShow').setAttribute('aria-pressed', String(show));
|
||||||
|
$('pwShow').setAttribute('aria-label', show ? 'Hide password' : 'Show password');
|
||||||
|
});
|
||||||
$('logoutBtn').addEventListener('click', async () => { await api('/api/admin/logout', { method: 'POST' }); showLogin(); });
|
$('logoutBtn').addEventListener('click', async () => { await api('/api/admin/logout', { method: 'POST' }); showLogin(); });
|
||||||
|
|
||||||
/* ---------------- server analytics / metadata collection ---------------- */
|
/* ---------------- server analytics / metadata collection ---------------- */
|
||||||
|
|||||||
@@ -628,8 +628,9 @@ export function registerNoteRoutes(app, deps) {
|
|||||||
if (fails.length >= 10) return c.json({ ok: false, error: 'too many attempts — try again later' }, 429);
|
if (fails.length >= 10) return c.json({ ok: false, error: 'too many attempts — try again later' }, 429);
|
||||||
let body = {};
|
let body = {};
|
||||||
try { body = await c.req.json(); } catch { /* treated as empty password */ }
|
try { body = await c.req.json(); } catch { /* treated as empty password */ }
|
||||||
const given = createHash('sha256').update(String(body.password || '')).digest();
|
// A pasted password often carries a stray space or line break.
|
||||||
const want = createHash('sha256').update(adminPassword).digest();
|
const given = createHash('sha256').update(String(body.password || '').trim()).digest();
|
||||||
|
const want = createHash('sha256').update(adminPassword.trim()).digest();
|
||||||
if (!timingSafeEqual(given, want)) {
|
if (!timingSafeEqual(given, want)) {
|
||||||
fails.push(now);
|
fails.push(now);
|
||||||
failedLogins.set(ip, fails);
|
failedLogins.set(ip, fails);
|
||||||
|
|||||||
Reference in New Issue
Block a user