mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-28 17:11:26 +00:00
a4e9b1d2aebeee6241e3e46cb6c7cbca003c4bc7
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
01fc7c5ed3 |
Refuse to overwrite an existing account on register or password change
registerAccount() never checked whether a credential capsule already existed at the locator address it was about to publish to. That address is a NIP-33 replaceable event fully determined by login name + password, so registering with a login name + password combination that was already registered -- by mistake (double-clicking "create account", confusing it with "sign in"), or by coincidence with someone else's account under a shared login name -- silently replaced the existing account's identity binding. The old everyday identity became unreachable through that login unless the original recovery phrase was still on hand, with nothing warning the user beforehand. changePassword() had the identical gap on the new locator: rotating to a password that happened to already be registered under the same login name would silently destroy that other account. Both flows now query for an existing capsule at the target locator before publishing, and refuse with AccountAlreadyExistsError if one is found (or a retry-oriented error if no relay could be reached to check). Two new end-to-end tests reproduce both scenarios and confirm the pre-existing account survives untouched. Bumps docs/spec.md to v0.6 (§15.6, §18.1). |
||
|
|
8c01a121df |
Never overwrite an existing profile or relay list on import
After every successful registration -- including nsec imports -- the
widget unconditionally published a default kind 0 profile ({name:
loginName}) plus kind 10002/10050 relay lists. All three are
NIP-01/NIP-65/NIP-17 replaceable events, so importing an nsec that
already had a real profile (display name, bio, avatar, NIP-05) and/or
relay preferences published elsewhere silently erased all of it,
replacing it with just the chosen BitLogin login name and the vault
relays.
publishInitialProfile() (moved into @bitlogin/core so it can be
covered by the existing MockRelay-based test harness) now queries for
an existing event of each kind before publishing, and only fills in
whichever one is genuinely missing. A fresh registration has nothing
to find, so behavior there is unchanged; an import now leaves
whatever the identity already had untouched.
Bumps docs/spec.md to v0.5 (§15.8, §28.1) and adds two new core tests
covering the fresh-identity and existing-profile-preserved cases.
|
||
|
|
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 |