mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-11-06 05:21:31 +00:00
Fixes reported from the live Vercel deployment:
- vercel.json: remove cleanUrls, which was breaking every internal .html
link (ERR_FAILED) since links already use explicit extensions
- Recovery export was silently broken after login or password rotation:
the crypto worker's session state only picked up the recovery capsule
event on register/recover, never on login/changePassword, even though
the decrypted payload always carries it. Surfaced recoveryCapsuleEvent
(and recoveryPublicKey for changePassword) through core's LoginResult /
ChangePasswordResult and wired them into the worker session; covered by
a new account.e2e.test.ts case
- New accounts never got a Nostr profile: publishProfileAndRelayLists is
now called with name: loginName after registration, so the npub's
profile defaults to the chosen login name
- Password manager never recognized the fields (shadow DOM + JS-driven
submission defeat the passive save-prompt heuristics, and the
generated-password screen never put the password in a real input at
all). Fixed with an explicit navigator.credentials.store() call after
every register/login/recover/changePassword; documented as
Chromium-only in docs.html
New: optional manual/custom password (packages/core/src/account/manualPassword.ts),
opt-in per spec §9.3's "future release may permit manual passwords behind
an advanced setting" allowance. Enforces a length floor, a common-password
blocklist, a login-name-substring check, a low-variety-pattern check, and
an entropy floor; the widget shows live non-blocking feedback as the user
types. The generated high-entropy passphrase remains the default,
recommended option.
Integration/interop support:
- window.nostr provider now carries a readonly _bitlogin marker
- window.bitlogin ({ version, isActiveSigner() }) is installed
unconditionally as soon as the script loads, independent of login
state or window.nostr's current occupant. Found and fixed a real bug
along the way: an unconditional assignment is required because browsers
auto-expose any element with a matching id/name as a same-named global,
so <bitlogin-auth id="bitlogin"> silently shadowed window.bitlogin
under a naive `if (!window.bitlogin)` guard. Renamed the demo's own
element id to avoid the same trap and documented the gotcha in docs.html
- docs.html: new section on supporting multiple signing methods
(extensions, NIP-46, BitLogin) alongside each other
- Added packages/demo/public/llms.txt for AI-assisted integration
Deployment: GitHub Pages requires a paid plan for private repos, so its
workflow and the .github directory are removed; README documents
Vercel-only deployment and the direct-from-CDN integration path.
Docs: added docs/future-recovery-options.md, laying out passkey-based
2FA/recovery options (pure 2FA, full independent recovery, and a
constrained middle ground) with pros/cons for future consideration —
not a development plan, nothing here is decided.
59/59 tests pass; core and widget typecheck clean; all fixes verified
live in Chromium via Playwright against an in-memory mock relay.
Co-Authored-By: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6
144 lines
7.4 KiB
HTML
144 lines
7.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Integrate BitLogin — docs</title>
|
|
<meta name="description" content="How to add the BitLogin login widget to any static site in a few minutes." />
|
|
<link rel="icon" href="./assets/icon.svg" type="image/svg+xml" />
|
|
<link rel="manifest" href="./manifest.webmanifest" />
|
|
<link rel="stylesheet" href="./assets/site.css" />
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="nav">
|
|
<a class="brand" href="./index.html"><span class="dot"></span>BitLogin</a>
|
|
<div class="links">
|
|
<a href="./index.html">Overview</a>
|
|
<a class="active" href="./docs.html">Integrate</a>
|
|
<a href="./account.html">Try it</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<header class="hero" style="padding-top:48px">
|
|
<h1 style="font-size:38px">Add BitLogin to your site</h1>
|
|
<p class="lead">No npm install required. Two files, one custom element.</p>
|
|
</header>
|
|
|
|
<section class="container narrow">
|
|
<ol class="step-list">
|
|
<li>
|
|
<h3>Copy the widget files next to your site</h3>
|
|
<p>Grab <code>bitlogin.js</code> and <code>cryptoWorker.js</code> (plus the shared chunk they both import) from a BitLogin release (or build <code>@bitlogin/widget</code> yourself) and drop them in a folder your site serves, e.g. <code>/vendor/bitlogin/</code>. All of them must live in the same directory — the main script loads the worker by a relative URL.</p>
|
|
</li>
|
|
<li>
|
|
<h3>Include the script and the element</h3>
|
|
<pre><code><script type="module" src="/vendor/bitlogin/bitlogin.js"></script>
|
|
<bitlogin-auth
|
|
vault-relays="wss://relay-one.example,wss://relay-two.example,wss://relay-three.example"
|
|
discovery-relays="wss://discovery-one.example,wss://discovery-two.example">
|
|
</bitlogin-auth></code></pre>
|
|
<p class="section-sub" style="margin:10px 0 0">That's it — the element renders its own sign-in/create/recover UI inside a shadow root, so it won't collide with your site's CSS.</p>
|
|
</li>
|
|
<li>
|
|
<h3>React to sign-in and sign-out</h3>
|
|
<pre><code>const bl = document.querySelector('bitlogin-auth');
|
|
|
|
bl.addEventListener('bitlogin-login', (e) => {
|
|
console.log('signed in as', e.detail.publicKey);
|
|
});
|
|
|
|
bl.addEventListener('bitlogin-logout', () => {
|
|
console.log('signed out');
|
|
});</code></pre>
|
|
</li>
|
|
<li>
|
|
<h3>Sign events from anywhere on the page</h3>
|
|
<p>Once a user is signed in, BitLogin installs a <code>window.nostr</code> provider shaped like the de facto NIP-07 interface used by browser extensions (Alby, nos2x, …). Any existing Nostr web app code that already calls <code>window.nostr</code> works unmodified.</p>
|
|
<pre><code>const pubkey = await window.nostr.getPublicKey();
|
|
|
|
const signed = await window.nostr.signEvent({
|
|
kind: 1,
|
|
content: 'hello from my app',
|
|
tags: [],
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
});
|
|
|
|
// NIP-44 encrypted DMs
|
|
const cipher = await window.nostr.nip44.encrypt(peerPubkeyHex, 'hi there');
|
|
const plain = await window.nostr.nip44.decrypt(peerPubkeyHex, cipher);</code></pre>
|
|
<div class="callout">
|
|
<strong>Private keys never leave a dedicated Web Worker.</strong> The main thread — your page's JS — only ever
|
|
sees public keys, signed events, and ciphertext. <code>window.nostr</code> calls are proxied into the worker
|
|
and back.
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<h3>Supporting multiple signing methods (extensions, NIP-46, BitLogin)</h3>
|
|
<p><code>window.nostr</code> is a single global slot — only one provider can occupy it at a time. If a user also
|
|
has a NIP-07 extension (Alby, nos2x, …) installed, BitLogin will not overwrite it: it only installs
|
|
<code>window.nostr</code> when nothing is already there. The reverse isn't in BitLogin's control — an extension
|
|
that loads after BitLogin can still overwrite it, same as any two NIP-07 providers would collide.</p>
|
|
<p>To know specifically whether BitLogin is the active signer (as opposed to an extension), check
|
|
<code>window.bitlogin?.isActiveSigner()</code> rather than just <code>typeof window.nostr !== 'undefined'</code> —
|
|
the latter only tells you <em>a</em> signer is present, not which one. <code>window.bitlogin</code> is installed
|
|
as soon as the script loads, independent of whether <code><bitlogin-auth></code> has signed anyone in yet,
|
|
so it's safe to feature-detect immediately.</p>
|
|
<pre><code>if (window.bitlogin) {
|
|
console.log('BitLogin is available, version', window.bitlogin.version);
|
|
}
|
|
if (window.bitlogin?.isActiveSigner()) {
|
|
// window.nostr is currently backed by BitLogin specifically
|
|
}</code></pre>
|
|
<p>NIP-46 (remote "bunker" signers) is a different transport — it doesn't typically hook
|
|
<code>window.nostr</code> directly, so it isn't something BitLogin needs to avoid colliding with. A site can
|
|
offer "Sign in with BitLogin," "Connect an extension," and "Connect a bunker" as separate options; whichever
|
|
the user picks becomes the active signer, and your app code should call whatever's currently active rather
|
|
than assume a specific one.</p>
|
|
<div class="callout">
|
|
<strong>Don't give your <code><bitlogin-auth></code> element <code>id="bitlogin"</code>.</strong>
|
|
Browsers automatically expose any element with a matching <code>id</code> or <code>name</code> attribute as
|
|
a same-named global — an element with <code>id="bitlogin"</code> becomes <code>window.bitlogin</code>,
|
|
shadowing the API object described above. Use something like <code>id="bitlogin-widget"</code> instead
|
|
(as this demo does).
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<h3>Choose your relays</h3>
|
|
<p>BitLogin ships with a small built-in bootstrap relay list, but you should point <code>vault-relays</code> and
|
|
<code>discovery-relays</code> at relays you trust for your users. Use at least three vault relays for
|
|
redundancy — capsule publication and reads use a quorum, so no single relay is a single point of failure.</p>
|
|
</li>
|
|
<li>
|
|
<h3>That's the whole integration</h3>
|
|
<p>No account database, no password-reset email, no server-side session. The <a href="./account.html">account manager demo</a>
|
|
shows the full loop: create an account, sign a test event, rotate the password, and export a recovery file.</p>
|
|
</li>
|
|
</ol>
|
|
|
|
<div class="callout">
|
|
<strong>Password-manager save prompts.</strong> Because the widget's login form lives inside
|
|
a shadow root and submits via JavaScript (never a real form POST), browsers' passive
|
|
"offer to save this password" heuristics don't reliably fire — and the generated-password
|
|
screen never puts the password into a real input at all, since it's only shown as text.
|
|
The widget works around this by explicitly calling the Credential Management API
|
|
(<code>navigator.credentials.store()</code>) after every successful registration, login,
|
|
password rotation, and recovery. This is Chromium-only (Chrome, Edge, Brave, Opera) —
|
|
Firefox and Safari never implemented <code>PasswordCredential</code>, so users on those
|
|
browsers will need to save their generated password manually.
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
BitLogin is an experimental alpha protocol. This demo is a reference implementation, not a production account service.
|
|
</footer>
|
|
|
|
|
|
<script>
|
|
if ("serviceWorker" in navigator) {
|
|
navigator.serviceWorker.register("./sw.js").catch(() => {});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|