From e67943ed007e2795a3034da787a3534106da4ee4 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Mon, 31 Aug 2026 17:50:49 +0200 Subject: [PATCH] feat(hephaestus): BCH wallet as default login (3-option picker + Caddy redirect) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - login.css/wallet.js: replace 2-tab (generate/import) UX with 3 big options — Create a new wallet [easiest], Import / add a wallet, Connect a wallet — WizardConnect [most private] (Cashonize/Paytaca placeholder, "coming soon"). - Caddyfile (both hostnames): /user/login now 302→ /user/oauth2/hephaestus-wallet. Escape hatch: /user/login?password=1 falls through to Forgejo's password form, linked from the picker footer as "Prefer old-school? Sign in with username & password". --- auth-proxy/public/login.css | 73 +++++++++++++++++++++++++---- auth-proxy/public/wallet.js | 91 +++++++++++++++++++++++++++++-------- caddy/Caddyfile | 17 +++++++ 3 files changed, 152 insertions(+), 29 deletions(-) diff --git a/auth-proxy/public/login.css b/auth-proxy/public/login.css index 55e30b1..73ced3c 100644 --- a/auth-proxy/public/login.css +++ b/auth-proxy/public/login.css @@ -23,21 +23,74 @@ * { box-sizing: border-box; } html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); min-height: 100%; } main { - max-width: 520px; - margin: 8vh auto; - padding: 32px 28px; + max-width: 560px; + margin: 6vh auto; + padding: 36px 32px; background: var(--card); border: 1px solid var(--border); border-radius: 14px; } -h1 { font-size: 22px; margin: 0 0 4px; } -p.lede { color: var(--muted); margin: 0 0 24px; font-size: 14px; } -.tab-row { display: flex; gap: 8px; margin-bottom: 20px; border-bottom: 1px solid var(--border); } -.tab { - padding: 10px 14px; cursor: pointer; background: transparent; border: none; - color: var(--muted); font: inherit; border-bottom: 2px solid transparent; +h1 { font-size: 22px; margin: 0 0 6px; line-height: 1.3; } +h1 .acid { color: var(--accent); } +h2 { font-size: 17px; margin: 0 0 6px; } +p.lede { color: var(--muted); margin: 0 0 20px; font-size: 14px; } +p.sub { color: var(--muted); margin: 0 0 12px; font-size: 13.5px; } + +/* --- Wallet picker (three big options — Create / Import / Connect) --- */ +.wallet-picker { display: grid; gap: 10px; margin: 8px 0 18px; } +.wallet-opt { + display: grid; grid-template-columns: 40px 1fr; gap: 14px; align-items: start; + padding: 14px 16px; background: var(--bg); border: 1px solid var(--border); + border-radius: 10px; cursor: pointer; text-align: left; font: inherit; color: var(--text); + transition: border-color .1s, background .1s; } -.tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); } +.wallet-opt:hover { border-color: var(--accent); background: var(--card); } +.wallet-opt .ico { font-size: 24px; line-height: 1; padding-top: 2px; } +.wallet-opt .body { display: flex; flex-direction: column; gap: 4px; } +.wallet-opt b { color: var(--text); font-size: 14.5px; font-weight: 600; + display: inline-flex; align-items: center; gap: 8px; } +.wallet-opt .sub { color: var(--muted); font-size: 13px; line-height: 1.5; } +.wallet-opt .pill { + display: inline-block; font-size: 10.5px; padding: 1px 8px; border-radius: 999px; + background: rgba(255,140,0,.14); color: var(--accent); font-weight: 500; + letter-spacing: 0.02em; text-transform: lowercase; +} +p.fine { + color: var(--muted); font-size: 12.5px; margin: 4px 0 0; text-align: center; +} +p.fine a { color: var(--muted); } +p.fine a:hover { color: var(--text); } + +/* --- Selected flow panel (Create/Import/Connect) --- */ +.flow { display: flex; flex-direction: column; gap: 4px; } +.flow > label { margin-top: 12px; } +.flow .row { + display: flex; gap: 10px; margin-top: 18px; align-items: center; +} +.flow .row .primary { flex: 1; margin-top: 0; } +.flow .row .ghost { + background: transparent; border: 1px solid var(--border); color: var(--muted); + padding: 10px 14px; border-radius: 8px; font: inherit; font-size: 13.5px; + cursor: pointer; +} +.flow .row .ghost:hover { color: var(--text); border-color: var(--muted); } + +/* --- WizardConnect coming-soon panel --- */ +.wc-supported { display: grid; gap: 8px; margin: 8px 0 14px; } +.wc-wallet { + display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: center; + padding: 10px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; + font-size: 13px; +} +.wc-wallet b { color: var(--text); } +.wc-wallet span { color: var(--dim, var(--muted)); font-size: 12px; } +.wc-wallet a { color: var(--accent); text-decoration: none; font-size: 12.5px; } +.wc-wallet a:hover { text-decoration: underline; } +.note-soon { + background: var(--bg); border: 1px dashed var(--border); border-left: 3px solid var(--accent); + border-radius: 8px; padding: 12px 14px; color: var(--muted); font-size: 13px; +} +.note-soon b { color: var(--text); } label { display: block; font-size: 13px; color: var(--muted); margin: 12px 0 6px; } input, textarea { width: 100%; padding: 10px 12px; background: var(--bg); diff --git a/auth-proxy/public/wallet.js b/auth-proxy/public/wallet.js index 467e086..d8a2919 100644 --- a/auth-proxy/public/wallet.js +++ b/auth-proxy/public/wallet.js @@ -156,57 +156,94 @@ function renderTabs() { app.innerHTML = ""; app.appendChild(el(`
-

Sign in to Hephaestus

+

Sign in with your Bitcoin Cash wallet

No email. No password reset. Your wallet is your identity.

-
- - +
+ + +
+

Prefer old-school? Sign in with username & password →

`)); - app.querySelectorAll(".tab").forEach((b) => { + app.querySelectorAll(".wallet-opt").forEach((b) => { b.addEventListener("click", () => { - app.querySelectorAll(".tab").forEach((x) => - x.setAttribute("aria-selected", x === b ? "true" : "false")); - renderPanel(b.dataset.tab); + // Hide the picker while a specific panel is open + app.querySelector("#picker").classList.add("hidden"); + app.querySelector(".fine")?.classList.add("hidden"); + renderPanel(b.dataset.choice); }); }); - renderPanel("generate"); +} + +function backToPicker() { + app.querySelector("#picker")?.classList.remove("hidden"); + app.querySelector(".fine")?.classList.remove("hidden"); + const panel = document.getElementById("panel"); + if (panel) panel.innerHTML = ""; } function renderPanel(kind) { const panel = document.getElementById("panel"); panel.innerHTML = ""; - if (kind === "generate") { + if (kind === "create") { const mnemonic = generateBip39Mnemonic(); panel.appendChild(el(` -
- +
+

Your new recovery phrase

+

Write it down. This is the only way to recover your account — we can never help you reset it.

${mnemonic}
-

The passphrase never leaves your device. If you lose both the phrase and the passphrase your account is gone — we cannot help.

- +

The passphrase never leaves your device. Lose both phrase and passphrase = account is gone.

+
+ + +
`)); + panel.querySelector("#back").addEventListener("click", backToPicker); panel.querySelector("#go").addEventListener("click", async () => { const pw = panel.querySelector("#pw").value; if (pw.length < 8) return showErr(panel, "passphrase must be at least 8 chars"); await onboardAndLogin(mnemonic, pw, panel); }); - } else { + } else if (kind === "import") { panel.appendChild(el(` -
- - +
+

Import a wallet

+

Paste your BIP-39 recovery phrase (12 or 24 words). Same phrase = same account.

+ - +
+ + +
`)); + panel.querySelector("#back").addEventListener("click", backToPicker); panel.querySelector("#go").addEventListener("click", async () => { const mn = panel.querySelector("#mn").value.trim().replace(/\s+/g, " "); const pw = panel.querySelector("#pw").value; @@ -215,6 +252,22 @@ function renderPanel(kind) { catch { return showErr(panel, "invalid recovery phrase"); } await onboardAndLogin(mn, pw, panel); }); + } else if (kind === "connect") { + panel.appendChild(el(` +
+

Connect a wallet

+

Sign the challenge with an external BCH wallet — your keys never leave it. Encrypted end-to-end over Nostr; the relay only sees ciphertext.

+
+
Cashonizebrowser extension · v0.9+install →
+
Paytacamobile · iOS + Androidinstall →
+
+

Coming soon. WizardConnect for message-signing is being wired in — for now, use Create or Import above (or the button below to jump back to the picker).

+
+ +
+
+ `)); + panel.querySelector("#back").addEventListener("click", backToPicker); } } diff --git a/caddy/Caddyfile b/caddy/Caddyfile index b11c046..80ecceb 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -23,6 +23,15 @@ reverse_proxy auth-proxy:4000 } + # Make BCH-wallet login the DEFAULT for /user/login. Old-school + # username/password stays reachable via `/user/login?password=1` + # (unmatched by this rule; falls through to Forgejo). + @plain_login { + path /user/login + not query password=1 + } + redir @plain_login /user/oauth2/hephaestus-wallet 302 + handle { reverse_proxy forgejo:3000 request_body { @@ -43,6 +52,14 @@ hephaestus.x { reverse_proxy auth-proxy:4000 } + # Same wallet-login-as-default as code.silentmode.st. Old-school + # password login still reachable via /user/login?password=1. + @plain_login { + path /user/login + not query password=1 + } + redir @plain_login /user/oauth2/hephaestus-wallet 302 + # Static-file-first, Forgejo-fallback. If /srv/hephaestus.x/{path} (or {path}/index.html) # exists, serve it. Otherwise the request falls through to Forgejo so all git/api/user # routes still work.