import { html, useState, } from './vendor/htm-preact.js'; import { t } from './i18n.js'; import { hubFetch, navigate } from './hub-client.js'; import * as platform from './platform.js'; const PASSWORD_MIN_BITS = 60; const PASSWORD_MIN_LEN = 12; function passwordBits(pw) { if (!pw) return 0; let pool = 0; if (/[a-z]/.test(pw)) pool += 26; if (/[A-Z]/.test(pw)) pool += 26; if (/[0-9]/.test(pw)) pool += 10; if (/[^A-Za-z0-9]/.test(pw)) pool += 32; let bits = pw.length * Math.log2(pool || 1); const unique = new Set(pw).size; if (unique < pw.length / 2) bits *= 0.6; if (/^[0-9]+$/.test(pw)) bits *= 0.5; if (/(password|motdepasse|azerty|qwerty|123456|meshbay)/i.test(pw)) bits *= 0.3; return Math.round(bits); } export function FirstRunPage({ onSet }) { const [url, setUrl] = useState(''); const [error, setError] = useState(''); const [busy, setBusy] = useState(false); const submit = async (e) => { e.preventDefault(); setError(''); setBusy(true); try { await window.meshbay.setHubBase(url.trim()); onSet(); } catch (err) { setError(platform.bridgeMessage(err)); setBusy(false); } }; return html`

${t('firstrun.title')}

${t('firstrun.hint')}

setUrl(e.target.value)} />
${error && html`
${error}
`}

${t('firstrun.note')}

`; } export function LoginPage({ onLogin }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const onSubmit = async (e) => { e.preventDefault(); if (!username || !password) return; setError(''); setLoading(true); try { await onLogin(username, password); navigate('/'); } catch (err) { setError(err.message); } finally { setLoading(false); } }; return html`

${t('login.title')}

setUsername(e.target.value)} autocomplete="username" required autofocus /> setPassword(e.target.value)} autocomplete="current-password" required /> ${error && html`
${error}
`}
`; } export function RegisterPage() { const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirm, setConfirm] = useState(''); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); const [loading, setLoading] = useState(false); const onSubmit = async (e) => { e.preventDefault(); if (password !== confirm) { setError(t('register.err_mismatch')); return; } if (password.length < PASSWORD_MIN_LEN) { setError(t('register.err_min_len', { n: PASSWORD_MIN_LEN })); return; } if (passwordBits(password) < PASSWORD_MIN_BITS) { setError(t('register.err_too_weak')); return; } setError(''); setLoading(true); try { if (window.MeshBayKeys) { await window.MeshBayKeys.registerUser(username, email, password); } else { await hubFetch('/v1/users/register', { method: 'POST', body: { username, email, password, pk_user_ed25519: '', pk_user_x25519: '' }, }); } setSuccess(true); } catch (err) { setError(err.message); } finally { setLoading(false); } }; if (success) { return html`

${t('register.success_title')}

${t('register.success_msg')}

${t('register.go_login')}
`; } return html`

${t('register.title')}

setUsername(e.target.value)} autocomplete="username" required /> setEmail(e.target.value)} autocomplete="email" required /> setPassword(e.target.value)} autocomplete="new-password" required minlength="8" /> ${password && html`

${t('register.strength', { bits: passwordBits(password) })}

`} setConfirm(e.target.value)} autocomplete="new-password" required /> ${error && html`
${error}
`}
`; }