diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/auth-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/auth-page.js | 47 |
1 files changed, 38 insertions, 9 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js index 8958d44..f0af187 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js @@ -14,6 +14,35 @@ const PASSWORD_MIN_LEN = 12; // before a passphrase derivation rather than after it. const USERNAME_MIN_LEN = 8; +// ── Passphrase field ───────────────────────────────────────────────────────── +// +// A passphrase this long is mistyped often enough that checking it is worth a +// control, and the alternative people reach for otherwise is typing it into +// the username box to read it back. The button is out of the tab order +// (`tabindex="-1"`): everyone who is not reaching for it would pay a keystroke +// between the passphrase and the submit button, and it does nothing a keyboard +// user cannot do by other means. `type="button"` matters — a bare button in a +// form submits it, which here would try to sign in on the first click. +function PasswordInput({ + value, onInput, placeholder, autocomplete, + minlength = null, autofocus = false, +}) { + const [shown, setShown] = useState(false); + const label = t(shown ? 'login.hide_password' : 'login.show_password'); + return html` + <div class="pw-field"> + <input type=${shown ? 'text' : 'password'} placeholder=${placeholder} + value=${value} onInput=${onInput} autocomplete=${autocomplete} + required minlength=${minlength} autofocus=${autofocus} /> + <button type="button" class="pw-toggle" tabindex="-1" + title=${label} aria-label=${label} aria-pressed=${shown} + onClick=${() => setShown(v => !v)}> + <${Icon} name=${shown ? 'eyeoff' : 'eye'} /> + </button> + </div> + `; +} + // ── reCAPTCHA v2 helper ────────────────────────────────────────────────────── let _captchaSiteKey = null; @@ -183,9 +212,9 @@ export function LoginPage({ onLogin }) { <input type="text" placeholder="${t('login.username')}" value=${username} onInput=${e => setUsername(e.target.value)} autocomplete="username" required autofocus /> - <input type="password" placeholder="${t('login.password')}" value=${password} - onInput=${e => setPassword(e.target.value)} - autocomplete="current-password" required /> + <${PasswordInput} placeholder=${t('login.password')} value=${password} + onInput=${e => setPassword(e.target.value)} + autocomplete="current-password" /> ${error && html`<div class="error-msg">${error}</div>`} ${pendingVerif && html` <div class="error-msg" style="background:var(--bg-secondary);border-left:3px solid var(--yellow, #f59e0b)"> @@ -474,9 +503,9 @@ export function RegisterPage() { <input type="email" placeholder="${t('register.email')}" value=${email} onInput=${e => setEmail(e.target.value)} autocomplete="email" required /> - <input type="password" placeholder="${t('register.password')}" value=${password} - onInput=${e => setPassword(e.target.value)} - autocomplete="new-password" required minlength="8" /> + <${PasswordInput} placeholder=${t('register.password')} value=${password} + onInput=${e => setPassword(e.target.value)} + autocomplete="new-password" minlength="8" /> <div style=${`margin:-4px 0 10px;${password ? '' : 'visibility:hidden;height:0;margin:0;overflow:hidden'}`}> <div style="height:4px;background:var(--border);border-radius:2px;overflow:hidden"> <div style=${`height:100%;width:${Math.min(100, passwordBits(password) / 100 * 100)}%; @@ -487,9 +516,9 @@ export function RegisterPage() { ${t('register.strength', { bits: passwordBits(password) })} </p> </div> - <input type="password" placeholder="${t('register.confirm')}" value=${confirm} - onInput=${e => setConfirm(e.target.value)} - autocomplete="new-password" required /> + <${PasswordInput} placeholder=${t('register.confirm')} value=${confirm} + onInput=${e => setConfirm(e.target.value)} + autocomplete="new-password" /> <label style="display:flex; gap:8px; align-items:flex-start; margin:4px 0 2px; font-size:0.88em; color:var(--text-secondary)"> <input type="checkbox" checked=${emailRecovery} |