aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
diff options
context:
space:
mode:
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.js47
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}