From 7ceefa3d6a11e167bfc3d063424a6e4e64d44fe4 Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Thu, 15 Oct 2026 11:52:41 +0800 Subject: [PATCH] Admin login: ignore surrounding spaces/quotes in a pasted password; show-password toggle --- frontend/admin.html | 16 ++++++++++++++-- server/notes.js | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/admin.html b/frontend/admin.html index eb2ad53..0f1c511 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -468,7 +468,10 @@

Sign in

Admin password

- + + + +
@@ -948,11 +951,20 @@ $('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); $('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; } $('pw').value = ''; $('loginMsg').textContent = ''; 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(); }); /* ---------------- server analytics / metadata collection ---------------- */ diff --git a/server/notes.js b/server/notes.js index 035643e..9329e25 100644 --- a/server/notes.js +++ b/server/notes.js @@ -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); let body = {}; try { body = await c.req.json(); } catch { /* treated as empty password */ } - const given = createHash('sha256').update(String(body.password || '')).digest(); - const want = createHash('sha256').update(adminPassword).digest(); + // A pasted password often carries a stray space or line break. + const given = createHash('sha256').update(String(body.password || '').trim()).digest(); + const want = createHash('sha256').update(adminPassword.trim()).digest(); if (!timingSafeEqual(given, want)) { fails.push(now); failedLogins.set(ip, fails);