Passwordless sign-in: 6-digit email codes, feature-flagged on SendGrid key
/api/auth/email/start issues a 15-min code (60s resend guard, 6 tries); verify creates the account passwordless (sponsor cookie first-touch) and mints the session. UI swaps the password cards for the code flow when config.emailAuth is on; dev mode returns the code inline. Password flow remains until the key lands in the volume (data/sendgrid.key) or SENDGRID_KEY env. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,35 @@
|
||||
finally { btn.disabled = false; }
|
||||
};
|
||||
|
||||
// passwordless (feature-flagged on config.emailAuth): code replaces passwords
|
||||
(async () => {
|
||||
const cfg = await IAP.getConfig();
|
||||
if (!cfg.emailAuth) return;
|
||||
$('passCards').hidden = true;
|
||||
$('magicCard').hidden = false;
|
||||
const start = busy($('mcSendBtn'), async () => {
|
||||
const r = await api('/api/auth/email/start', { email: $('mcEmail').value });
|
||||
$('mcCodeRow').hidden = false;
|
||||
$('mcVerifyBtn').hidden = false;
|
||||
$('mcSendBtn').hidden = true;
|
||||
$('mcResend').hidden = false;
|
||||
if (r.devCode) { $('mcCode').value = r.devCode; IAP.status('Dev mode: code filled in for you.', 'ok'); }
|
||||
else IAP.status('Code sent. Check your inbox (and spam, the first time).', 'ok');
|
||||
$('mcCode').focus();
|
||||
});
|
||||
$('mcSendBtn').addEventListener('click', start);
|
||||
$('mcResend').addEventListener('click', busy($('mcResend'), async () => {
|
||||
const r = await api('/api/auth/email/start', { email: $('mcEmail').value });
|
||||
if (r.devCode) $('mcCode').value = r.devCode;
|
||||
IAP.status('Fresh code sent.', 'ok');
|
||||
}));
|
||||
$('mcVerifyBtn').addEventListener('click', busy($('mcVerifyBtn'), async () => {
|
||||
await api('/api/auth/email/verify', { email: $('mcEmail').value, code: $('mcCode').value });
|
||||
IAP.status('You are in.', 'ok');
|
||||
await render();
|
||||
}));
|
||||
})();
|
||||
|
||||
$('signupBtn').addEventListener('click', busy($('signupBtn'), async () => {
|
||||
await api('/api/signup', { email: $('suEmail').value, password: $('suPass').value });
|
||||
IAP.status('Welcome aboard. You are in.', 'ok');
|
||||
|
||||
+11
-1
@@ -15,7 +15,17 @@
|
||||
</section>
|
||||
|
||||
<div id="authArea">
|
||||
<div class="grid c2">
|
||||
<div class="card" id="magicCard" hidden>
|
||||
<h3>Sign in or join free</h3>
|
||||
<p class="muted small">Type your email and we send a 6-digit code. No password to invent,
|
||||
no password to forget. New emails get a free account automatically.</p>
|
||||
<p><input id="mcEmail" type="email" placeholder="Email" autocomplete="email" style="width:100%;max-width:420px"></p>
|
||||
<p id="mcCodeRow" hidden><input id="mcCode" inputmode="numeric" placeholder="6-digit code" style="width:100%;max-width:420px"></p>
|
||||
<button class="btn" id="mcSendBtn">Email me a code</button>
|
||||
<button class="btn" id="mcVerifyBtn" hidden>Sign in</button>
|
||||
<button class="btn sec small" id="mcResend" hidden>Send a fresh code</button>
|
||||
</div>
|
||||
<div class="grid c2" id="passCards">
|
||||
<div class="card">
|
||||
<h3>Create your free account</h3>
|
||||
<p class="muted small">Takes ten seconds. No wallet needed to join.</p>
|
||||
|
||||
Reference in New Issue
Block a user