58 lines
2.5 KiB
HTML
58 lines
2.5 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Sign in · Moon Household Budget</title>
|
|
<script src="https://unpkg.com/@simplewebauthn/browser@9.0.1/dist/bundle/index.umd.min.js"></script>
|
|
<style>
|
|
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background:#FAF7F2;
|
|
display:flex; min-height:100vh; align-items:center; justify-content:center; margin:0; color:#3A3330; }
|
|
.card { background:#fff; border:1px solid #EDE8E2; border-radius:16px; padding:2rem; max-width:380px;
|
|
width:90%; text-align:center; box-shadow:0 4px 16px rgba(80,60,50,0.08); }
|
|
h1 { font-size:1.3rem; margin:0 0 .25rem; }
|
|
p.sub { color:#7A6E68; margin:0 0 1.5rem; }
|
|
button { background:#4338ca; color:#fff; border:0; border-radius:10px; padding:.8rem 1.2rem;
|
|
font:inherit; font-weight:600; cursor:pointer; width:100%; }
|
|
button:disabled { opacity:.5; }
|
|
.msg { min-height:1.2rem; margin-top:.9rem; font-size:.9rem; color:#C57A6E; }
|
|
a { color:#4338ca; font-size:.85rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<h1>🌙 Moon Household Budget</h1>
|
|
<p class="sub">Sign in with your passkey</p>
|
|
<button id="login">🔑 Sign in with passkey</button>
|
|
<div class="msg" id="msg"></div>
|
|
<p style="margin-top:1.25rem;"><a href="/enroll">Set up a new device</a></p>
|
|
</div>
|
|
<script>
|
|
const { startAuthentication } = SimpleWebAuthnBrowser;
|
|
const msg = document.getElementById('msg');
|
|
const btn = document.getElementById('login');
|
|
btn.onclick = async () => {
|
|
btn.disabled = true; msg.textContent = 'Waiting for your passkey…';
|
|
try {
|
|
const opts = await (await fetch('/auth/login/begin', { method: 'POST' })).json();
|
|
const assertion = await startAuthentication(opts);
|
|
const res = await fetch('/auth/login/complete', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(assertion),
|
|
});
|
|
const data = await res.json();
|
|
if (data.ok) {
|
|
window.location = new URLSearchParams(location.search).get('next') || '/';
|
|
} else {
|
|
msg.textContent = 'Sign-in failed: ' + (data.error || 'try again');
|
|
btn.disabled = false;
|
|
}
|
|
} catch (e) {
|
|
msg.textContent = 'Sign-in failed: ' + e.message;
|
|
btn.disabled = false;
|
|
}
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|