diff --git a/hephaestus/index.html b/hephaestus/index.html index d310cf7..9985083 100644 --- a/hephaestus/index.html +++ b/hephaestus/index.html @@ -129,6 +129,67 @@

Why no email? Nothing to leak, nothing to phish, no password-reset flow to hijack. The trade: if you lose your recovery phrase there is no support form to send you back in. Back the phrase up. Twice. On paper.

+
+

For AI sessions & programmatic testing read before debugging

+

If you're an AI assistant, CI job, or anyone debugging the sign-in without a real browser, read this first. It heads off two misdiagnoses we've already hit.

+ +

Misdiagnosis #1: "The login shell page is only 510 bytes — it's a broken stub"

+

It's not a stub — it's a deliberately small bootstrap page that renders the wallet UI client-side via a JS module (standard SPA pattern). Size ≠ brokenness. The full UI (mnemonic generation, passphrase encryption, sign flow) is inside wallet.js, not the shell. Test by requesting wallet.js instead:

+
curl -sI https://code.silentmode.st/auth/static/wallet.js
+# expect: HTTP 200, ~10 KB, Content-Type: application/javascript
+ +

Misdiagnosis #2: "static/wallet.js returns 404 at every path"

+

It only 404s if you drop the /auth/ prefix. Caddy path-multiplexes: /auth/* goes to the auth-proxy, everything else goes to Forgejo. Forgejo has no /static/wallet.js, so that path is expected-404. The real paths are:

+ + + + + + + + + + + + +
URLServed byPurpose
/auth/authorizeauth-proxyLogin shell HTML (SPA bootstrap)
/auth/static/wallet.jsauth-proxyWallet UI + signing logic (~10 KB)
/auth/static/login.cssauth-proxyStyles for the login card
/auth/challengeauth-proxyPOST cashaddr → get nonce + signed message
/auth/verifyauth-proxyPOST nonce + signature → get OAuth code
/auth/.well-known/openid-configurationauth-proxyOIDC discovery
/user/oauth2/hephaestus-walletForgejoKicks off the OAuth handshake (307 → /auth/authorize)
/user/oauth2/hephaestus-wallet/callbackForgejoReceives the OAuth code, mints session
/api/v1/versionForgejoPublic: returns Forgejo version
/api/v1/userForgejoRequires an API token (not a session cookie) — returning 401 in a browser is not a bug
+ +

Testing the sign-in flow end-to-end without a real browser

+

The wallet signing is easiest to reproduce in Node/Deno or a headless browser — the crypto is BIP-137 "Bitcoin Signed Message" over BCH secp256k1. But the server-side pieces alone can be smoke-tested with curl:

+
ISSUER=https://code.silentmode.st/auth
+
+# 1. discovery document lists all endpoints
+curl -s "$ISSUER/.well-known/openid-configuration" | jq .
+
+# 2. request a challenge for any cashaddr (server just holds it 5 min)
+curl -s -X POST -H 'content-type: application/json' \
+  -d '{"cashaddr":"bitcoincash:qq00000000000000000000000000000000000000q"}' \
+  "$ISSUER/challenge"
+# → returns {"nonce": "...", "message": "code.silentmode.st wants you to sign in..."}
+
+# 3. any BCH wallet that speaks "Sign Message" can produce a valid signature
+#    for that exact message. Then POST it:
+curl -s -X POST -H 'content-type: application/json' \
+  -d '{"nonce":"THE_NONCE","signature":"BASE64_SIG"}' \
+  "$ISSUER/verify"
+# → returns {"redirect": "https://code.silentmode.st/user/oauth2/hephaestus-wallet/callback?code=..."}
+

For the JS side (which is what the browser does), inspect auth-proxy/public/wallet.js in the repo — it's ~250 lines and does exactly this: generateBip39Mnemonic → deriveHdPath("m/44'/145'/0'/0/0") → sign(magicHash(message)). Nothing exotic.

+ +

Common browser-side checks

+

If you're in a browser (or a browser-driving tool) and want to know if you're actually signed in:

+
// The URL bar isn't reliable when Forgejo shows Security or Settings pages —
+// they render for logged-in users but don't have "Dashboard" in the title.
+// Best check: does the profile menu render, and is there no "Sign in" link?
+
+const signedIn = !document.querySelector('a[href*="/user/login"]')
+              && !!document.querySelector('[aria-label*="Profile"], .user-menu, .avatar');
+
+// The username, if you're on the Dashboard:
+document.title.match(/^(bch_\w+)/)?.[1];
+ +

Do not test with fetch('/api/v1/user') unless you have an API token. That endpoint requires token auth even when you're signed into the web UI. Session cookies get you into Forgejo pages; they don't get you into the token-scoped API. A 401 there means "no token" — it does not mean "not signed in."

+
+

Push some code

Two paths, either works. Pick whichever fits your setup.