diff --git a/public/assets/my.js b/public/assets/my.js index d1b850e..123ef64 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -467,9 +467,13 @@ async function render() { const me = await IAP.refreshNavWallet(); - const signedIn = me && me.signedIn; + // one way in: email. A wallet-only session (no account) is sent back to the + // email card with a finish-setup note; verifying the code links that wallet. + const walletOnly = !!(me && me.signedIn && !me.email); + const signedIn = me && me.signedIn && !walletOnly; $('authArea').hidden = !!signedIn; $('memberArea').hidden = !signedIn; + if ($('mcFinish')) $('mcFinish').hidden = !walletOnly; if (!signedIn) return; if ($('adminLink')) $('adminLink').hidden = !me.isAdmin; // admin portal link, only for ADMIN_EMAIL setPane(location.hash.slice(1) || 'overview'); @@ -1403,16 +1407,6 @@ if (!(await showGauntlet())) await showLoginAd(); // welcome tour outranks the login ad await render(); })); - $('walletSigninLink').addEventListener('click', async e => { - e.preventDefault(); - try { - IAP.status('Check your wallet for the free sign-in signature…'); - await IAPWallet.signIn(); - IAP.status('Signed in with your wallet.', 'ok'); - if (!(await showGauntlet())) await showLoginAd(); // welcome tour outranks the login ad - await render(); - } catch (err) { IAP.status((err && err.message) || String(err), 'bad'); } - }); $('linkBtn').addEventListener('click', busy($('linkBtn'), async () => { IAP.status('Check your wallet for the free link signature…'); await IAPWallet.signIn(); // server binds the wallet to the signed-in email account diff --git a/public/my.html b/public/my.html index 5c27005..34ebe2d 100644 --- a/public/my.html +++ b/public/my.html @@ -27,6 +27,8 @@

-

Crypto-native? You can also sign in with just your wallet. - One free signature, no email needed.

← Back to the site

@@ -697,7 +697,7 @@ - + diff --git a/server.js b/server.js index bbb7f38..f8daedb 100644 --- a/server.js +++ b/server.js @@ -568,6 +568,16 @@ const server = http.createServer(async (req, res) => { const ref = parseCookies(req)['iap.sponsor'] || ''; const r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged if (r.error) return json(res, 400, r); + // a wallet-only session (signed with a wallet, no account) finishing setup: + // adopt that wallet into the email account so member #, purchases and + // payouts stay attached, then retire the wallet-only session + const prior = await auth.fromRequest(req); + if (prior && prior.address && !prior.email) { + const lr = await accounts.linkWallet(e, prior.address); + if (lr.error) return json(res, 400, lr); + r.account = lr.account || await accounts.byEmail(e); + await auth.logout(req); + } if (r.created) { sendWelcome(e, ref).catch(() => {}); } // sponsor notified at username set (/api/my/profile) if (r.created && b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent, new joins only let memberId = 0; @@ -577,7 +587,8 @@ const server = http.createServer(async (req, res) => { } // -- wallet auth: link-to-account when an email session exists, or - // wallet-first sign-in for crypto-native users + // wallet-first sign-in (no UI door since 2026-09-09; a wallet-only session + // is walked to the email card, which adopts the wallet on verify) if (p === '/api/auth/challenge' && req.method === 'POST') { const b = await readBody(req); const r = auth.makeChallenge(b.address);