mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-30 01:51:22 +00:00
ce6ade73b467dcd4b7e6c84cff2c46f3ee5fd9f2
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1dbb4fca51 |
Fail closed (not open-and-crash) when window.nostr can't be claimed
Some NIP-07 extensions install window.nostr as a non-configurable, non-writable property to stop another script from overwriting it. claimSigner() did a plain, unguarded `window.nostr = ...` assignment, so on a page where such an extension is active, this threw "Cannot assign to read only property 'nostr' of object '#<Window>'" -- and because it ran synchronously before dispatching bitlogin-login or moving to the dashboard screen, it froze account creation/import at the final "verify your recovery phrase" step, with that raw TypeError as the only feedback shown. Reported from a real BitRoad integration session. claimSigner() and releaseSigner() now catch this and return false instead of throwing; connectedCallback's initial claim attempt is guarded the same way. Every caller (registration, login, recovery, password change) now completes sign-in regardless -- a host page using this element's own methods directly (getPublicKey/signEvent/nip44Encrypt/nip44Decrypt) was never actually affected by a failed window.nostr takeover, only a page that reads window.nostr itself. The dashboard now shows a plain-language warning when this happens, reusing the existing rollback/relay-disagreement warning banner mechanism. Also fixed while auditing this area: the "Copy" button on the generated- password screen silently did nothing if the clipboard write failed (e.g. Firefox's stricter permissions, an unfocused document) -- it now flashes "Copied" or "Copy failed -- select the text manually" either way. Verified live in Chrome: reproduced the exact crash by locking down window.nostr the way a real extension does, confirmed account creation/import now completes end-to-end (in both the standalone demo and embedded in BitRoad) with the new warning shown instead of a stuck flow. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||
|
|
1eff23aca8 |
Expose widget theming as CSS custom properties, and a max-width override
<bitlogin-auth> renders in a shadow root, but every color/radius/font value already flowed through a CSS custom property on :host -- these inherit through the shadow boundary, so a host page can already override the widget's look from ordinary light-DOM CSS. This adds the two properties that weren't yet exposed: - --bl-accent-fg (default: white): text color on top of --bl-accent. Was hardcoded on button.primary, so a host with a light accent color and its own dark-on-accent convention had no way to match it. - --bl-max-width (default: 380px): the widget stays a fixed-width card by default (unchanged for existing embedders), but a host page that wants it to fill a wider container can now override this instead of being stuck with the hardcoded cap. Also routes the hardcoded font-family through --bl-font-family so that's themeable too. Documented in the README with a worked example (BitRoad, which maps every one of these onto its own design tokens to render the widget in its own colors at full width). Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||
|
|
1dc5e6802c |
Fail closed on credential-generation rollback; fix NIP-44 extended-length payloads
Found during a real integration pass (embedding this widget in BitRoad, a separate Nostr commerce app): - Password rotation's old-locator tombstone (§18.1) is only advisory -- a relay that never processes it can keep serving the pre-rotation capsule indefinitely. loginWithPassword/changePassword previously accepted that stale capsule with only a non-blocking warning. A device that has already seen the newer generation now refuses it (RollbackDetectedError) unless the caller explicitly passes acknowledgeRollback: true for the narrower relay-lag case. The widget surfaces this as a blocking confirmation screen before claiming the signer, not a banner shown after the fact. - NIP-44 capped plaintext at 65535 bytes; added the extended-length prefix for payloads up to 2^32-1 bytes, verified byte-for-byte against the nostr-tools reference implementation (conversation keys, padding at every bucket boundary, round trips in both directions). - <bitlogin-auth> now exposes nip44Encrypt/nip44Decrypt directly on the element instance, alongside the existing getPublicKey/signEvent, so a host page can reach every capability without racing other providers for the single window.nostr slot. docs/spec.md bumped to v0.4 with the full writeup (§16.2, §29.7). 75/75 tests passing (14 new NIP-44 tests, 2 new rollback e2e tests). Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||
|
|
9408fef484 |
Add MIT license, CONTRIBUTING, and SECURITY docs for open-sourcing
Preparing to open the project up beyond this repo's own use: - LICENSE: MIT, and a "license": "MIT" field on the root and all three workspace package.json files. - CONTRIBUTING.md: dev workflow, the mock-relay testing setup (most sandboxes/CI have no route to public Nostr relays, and registration needs a 2-of-N ack quorum so a single mock relay always fails), the spec-first process for anything touching wire format or crypto (docs/spec.md is canonical; speculative ideas go in their own doc, not the spec), and stricter expectations for crypto/key-handling changes. - SECURITY.md: private vulnerability disclosure via GitHub's advisory flow rather than public issues, given this handles private keys and password-derived secrets client-side; states current audit status plainly (none yet). - README: short "Contributing" section pointing at all three. Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6 |
||
|
|
a24719184e |
Add signer switching, manual-password parity, phrase-quiz split, and offline recovery import
Signer interop: - <bitlogin-auth> gains claimSigner()/releaseSigner() instance methods, and window.bitlogin gains a matching releaseSigner(). Before this, once the widget claimed window.nostr it held it forever (even after logout), so a site offering multiple signing methods (an extension, a NIP-46 bunker, BitLogin) had no way to let a user switch away from BitLogin without a page reload. claimSigner() is now also called automatically on every successful sign-in/sign-up/recovery, and releaseSigner() on logout and DOM removal. Both fire bitlogin-signer-claimed/-released events. docs.html and llms.txt document the new API and a switch-signer example. Manual password parity + fixes: - The opt-in custom-password path (added earlier per spec §9.3) was only available at initial account creation; it's now offered on password rotation and phrase recovery too, via a shared renderManualPasswordFields()/ readManualPassword() pair. - Added a required confirm-password field with live match feedback, wherever a manual password can be set. - Found and fixed a real bug while testing this: a validation failure (mismatched confirm, weak password) on any manual-password screen fully re-rendered the form, silently wiping the typed password and un-checking "I saved it" -- forcing a full retype. Fixed via draft fields that repopulate the inputs' value attribute across the failure re-render, and by reading the checkbox before validation instead of after. Recovery-phrase display split (§anti-cheat): - The 12-word phrase and its 3-word verification quiz were shown on the same screen, defeating the point of the quiz. Split into a display-only "confirm-phrase" screen and a separate "verify-phrase" screen where the phrase is hidden while the quiz is answered. Offline recovery-export import (§19.5): - recoverWithPhrase() now accepts optional offlineRecoveryCapsuleEvents, merged with whatever the live relay read returns; recovery still requires the phrase either way, since the export file never contains it or a phrase-derived key. Covered by a new core e2e test that recovers successfully against genuinely unreachable relay URLs using only the offline candidate. - The recover-phrase screen gets an optional file input for a previously downloaded recovery export, parsed and validated client-side before being passed through to the worker. Landing page copy updated to reflect that manual passwords are allowed (with a real entropy floor and common-password check), not disallowed. 60/60 tests pass; core and widget typecheck clean. Verified live in Chromium via Playwright against a 3-relay in-memory mock quorum: account creation (generated and manual, including the mismatch/retry-preserves- draft path), phrase display/quiz split, signer claim/release/reclaim across logout and re-login, password rotation and phrase recovery with manual passwords (including logging back in with the result), and the offline recovery-export file path. Zero page or console errors across every run. Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6 |
||
|
|
0801371566 |
Fix live-deployment bugs, add manual passwords, and improve integration support
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
|
||
|
|
a76c8484f7 |
Add nsec import: use an existing Nostr identity as the everyday key (§SF10)
core: - registerAccount gains an optional everydayPrivateKey (validated scalar, used instead of generating); result carries an `imported` flag - importAccount() + decodeEverydayPrivateKey() convenience helpers accept nsec (bech32) or 64-char hex, wiring in the existing decodeNsec primitive - e2e tests: imported nsec round-trips to the same npub through both clean-device login and phrase recovery; malformed keys rejected widget: - worker gains a `previewImportKey` action (validate + preview npub without publishing) and passes importKey through `register` - <bitlogin-auth> gets an "Import an existing Nostr key" screen: paste nsec/hex, check-key preview shows the resulting npub for confirmation, then the normal name -> password -> phrase flow, with the pasted key dropped from the main thread after registration (§11.10) and a risky-paste warning shown up front docs: mark §SF10 implemented; note backup codes are redundant with the phrase and unlikely to ship; README implemented-list updated. Verified end-to-end in real Chromium (preview npub, import, clean-device login all reproduce the imported identity). 51/51 tests pass. Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6 |
||
|
|
5ee63ae6b0 |
Implement BitLogin as a modular protocol library, embeddable widget, and demo site
Builds out the v0.3 spec as an npm workspace monorepo any static site can consume for login and Nostr signing: - packages/core: framework-free protocol implementation — Argon2id/HKDF/ ScalarExpand/AES-256-GCM/JCS crypto primitives, NIP-01 event handling and a quorum-aware relay pool, credential/recovery capsule construction and validation, and the full account lifecycle (registration, any-device login with rollback/relay-disagreement detection, phrase recovery with recovery-capsule refresh, password rotation with mandatory tombstone + NIP-09 deletion, replica repair, recovery export, NIP-19 encoding, a NIP-44 v2 implementation, and a NIP-07-shaped signer). 49 tests cover the spec's Phase 0 byte-level vectors plus full create/login/recover/rotate end-to-end scenarios against an in-memory mock relay. - packages/widget: a <bitlogin-auth> custom element with a dedicated crypto Web Worker (private keys never touch the main thread) and a window.nostr NIP-07-compatible provider, so existing Nostr web apps work against it with zero code changes. Built with Vite into two relocatable static files. - packages/demo: a plain static site (no build step of its own) consuming the widget — a branding/landing page, an integration guide, and an account-manager page covering create/login/recover, password rotation, and identity export. All flows were verified twice: protocol-level tests against a mock relay, and the actual built widget driven end-to-end in real Chromium (registration, phrase confirmation, event signing, clean-device login, window.nostr, and phrase recovery all reproduce the identical everyday pubkey). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6 |