mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
Design doc: centralized OAuth on-ramps as a journey toward self-custody
Proposes 'Sign in with Google/GitHub/…' as a clearly labeled fourth method family behind the same login surface. Two architectures: an OAuth-gated NIP-46 bunker (works today, full custody, the proof point) and the recommended one -- OAuth as the password manager for a REAL capsule account, where the service holds only the unlock secret, the identity lives on relays, and service compromise maps to the password- compromise analysis the spec already has (§14). Phrase handling is a labeled custody dial (shown at signup / first- session-only with a 'secure your account' card / escrowed). Graduation keeps the same npub at every step: save the npub, claim the phrase, hold your own password, full export -- with §SF10 import as the already-implemented escape from any custodial tier. Honesty contract: every centralized option answers who can access the account, what happens if they vanish, and how to leave. Conforming services must export and delete on demand and publish their tier machine-readably. Owner framing on the record: meet users at whatever sign-in they trust, nudge toward decentralization over time -- 'it's a journey for all of us.' Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
a7cf55ea84
commit
dc6490e46a
3 files changed
+273
-3
No files matched your search
+7
-3
@@ -12,9 +12,13 @@ for compatible applications.
|
||||
|
||||
- [`spec.md`](./spec.md) — canonical BitLogin protocol specification
|
||||
- [`second-factor.md`](./second-factor.md) — second-factor design notes
|
||||
- [`login-methods.md`](./login-methods.md) — proposal: the widget as the single
|
||||
login surface, adding NIP-07 and NIP-46 sign-in behind the existing
|
||||
provider facade (nsec-paste login rejected on the record)
|
||||
- [`login-methods.md`](./login-methods.md) — the widget as the single login
|
||||
surface: NIP-07 and NIP-46 sign-in behind the existing provider facade
|
||||
(implemented; nsec-paste login rejected on the record)
|
||||
- [`centralized-onramps.md`](./centralized-onramps.md) — proposal: clearly
|
||||
labeled OAuth sign-in (Google, GitHub, …) as a fourth method, with OAuth as
|
||||
a password manager for a real capsule account and a graduation ladder to
|
||||
self-custody that keeps the same npub
|
||||
|
||||
## Portable account layer
|
||||
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
# Centralized On-Ramps — OAuth Sign-In as a Journey Toward Self-Custody
|
||||
|
||||
**Document version:** 0.1 (design proposal)
|
||||
**Status:** Proposal only — nothing here is implemented, and the reference
|
||||
services it describes live outside this repository by definition. Depends on
|
||||
`docs/spec.md`, `docs/login-methods.md` (the one-login-surface model), and
|
||||
`docs/second-factor.md` §SF10 (imported identities, implemented), which turns
|
||||
out to be the graduation mechanism.
|
||||
**Framing decision (owner, 2026-08-04):** centralized options are offered
|
||||
openly and labeled honestly. The goal is normies using Nostr sites with
|
||||
whatever sign-in they are already comfortable with — Google, GitHub, Facebook
|
||||
— and a designed path that nudges them toward self-custody over time. "It's a
|
||||
journey for all of us."
|
||||
|
||||
---
|
||||
|
||||
# CO1. Purpose and scope
|
||||
|
||||
BitLogin's native account is already the low-friction end of self-custody: a
|
||||
login name and password, no extension, no server. But the true beginner does
|
||||
not want *any* new credential — they want the button they already trust:
|
||||
**Sign in with Google**.
|
||||
|
||||
This document designs that button as a fourth method behind the same
|
||||
`<bitlogin-auth>` surface, such that:
|
||||
|
||||
1. The user gets a working Nostr identity — post, react, set a profile photo
|
||||
— with nothing but their existing OAuth account.
|
||||
2. The centralized dependency is **labeled, bounded, and escapable**: the
|
||||
design maximizes what survives if the service or the OAuth provider
|
||||
disappears, and every tier has a defined graduation path to full
|
||||
self-custody **that keeps the same npub**.
|
||||
3. The core protocol is untouched. Everything here is a deployment pattern
|
||||
composed from existing primitives — capsule accounts, NIP-46, §SF10
|
||||
import. `knownAnswer.test.ts` never notices.
|
||||
|
||||
Out of scope: implementing the services (they are server-side by nature and
|
||||
cannot live in this static repo), and any change that would make a
|
||||
centralized rail *required*. The native password path remains the default
|
||||
presented to new users (login-methods.md §LM9.1).
|
||||
|
||||
# CO2. The principle: centralization as a rail, not a foundation
|
||||
|
||||
The test every design below must pass: **what does the user keep when the
|
||||
centralized party is gone?**
|
||||
|
||||
BitLogin's answer is layered. The identity key lives in capsules on public
|
||||
relays (or in the user's eventual export), not in the service's database
|
||||
wherever the tier allows it. The centralized party holds an *unlock
|
||||
credential* — the thing that makes everyday login effortless — and its
|
||||
disappearance costs convenience, not identity, at every tier above the
|
||||
lowest. This is the structural difference from "Sign in with Google" as
|
||||
normally deployed, where the provider's disappearance costs the account.
|
||||
|
||||
# CO3. Two architectures
|
||||
|
||||
## CO3.1 Architecture A — the OAuth-gated bunker (works today)
|
||||
|
||||
A service runs a NIP-46 remote signer. First OAuth sign-in mints a keypair
|
||||
server-side; thereafter, a valid OAuth session authorizes signing. BitLogin
|
||||
connects to it exactly as it connects to any bunker — the widget's existing
|
||||
NIP-46 support (§LM5) is the whole client.
|
||||
|
||||
- **Custody:** full. The service holds the signing key and sees every event
|
||||
it signs.
|
||||
- **What survives the service:** nothing, unless the service offers nsec
|
||||
export (it must — see CO6).
|
||||
- **Cost to build:** zero widget changes. nsec.app's noauth already proves
|
||||
the pattern in production with email/passkey gating.
|
||||
- **Role in this design:** the fastest possible proof point, and the
|
||||
compatibility answer for services that already exist. Not the recommended
|
||||
destination.
|
||||
|
||||
## CO3.2 Architecture B — OAuth as the password manager (recommended)
|
||||
|
||||
The insight: a BitLogin account requires a high-entropy password, but the
|
||||
protocol never cares *who remembers it*. So let the service remember it.
|
||||
|
||||
**First sign-in (registration):**
|
||||
|
||||
1. The widget completes the OAuth dance with the service (CO5).
|
||||
2. The service verifies the OAuth token, sees a new user, and returns
|
||||
`register: true` plus a service-generated login name.
|
||||
3. The **client** — the static widget, in the worker — runs the ordinary
|
||||
registration flow (§15): generates the password, derives everything,
|
||||
builds both capsules, publishes to relays. The recovery phrase is
|
||||
generated and the recovery capsule written, per spec.
|
||||
4. The client sends the service the password (over TLS, after OAuth). The
|
||||
service stores `{oauth_subject → login_name, password}` and nothing else.
|
||||
5. Phrase handling per deployment tier (CO4).
|
||||
|
||||
**Every later sign-in:** OAuth token in, `{loginName, password}` out, then
|
||||
the client runs the standard password login (§16) — Argon2id, locator,
|
||||
capsule decrypt, rollback checks, all client-side, all existing code.
|
||||
|
||||
- **Custody:** the service holds an unlock secret, never the signing key.
|
||||
Compromise of the service is password compromise, which the spec already
|
||||
analyzes (§14): no cryptographic authority over the recovery capsule,
|
||||
detectable rollback, phrase path unaffected.
|
||||
- **What survives the service:** the account, fully — it is a standard
|
||||
capsule account on public relays. Anyone holding the password or phrase
|
||||
can use it with zero service involvement.
|
||||
- **Privacy:** the service learns login times. It does not see events,
|
||||
contacts, or activity — the client talks to relays directly.
|
||||
- **The service is one endpoint** plus OAuth plumbing:
|
||||
`POST /unlock {provider, oauth_token} → {loginName, password, register?}`.
|
||||
No relay access, no Nostr code, no key material. Small enough to audit.
|
||||
|
||||
# CO4. Phrase handling in Architecture B — the custody dial
|
||||
|
||||
Registration necessarily has the phrase in memory (§13); what happens to it
|
||||
is a deployment choice, and each choice is a labeled tier:
|
||||
|
||||
- **Tier B1 — phrase shown at signup.** Maximum sovereignty, maximum
|
||||
friction; defeats the purpose for true normies. Offered, not default.
|
||||
- **Tier B2 — phrase held for the first session, then discarded
|
||||
(recommended default).** The phrase stays in worker memory for the first
|
||||
session only. The dashboard shows a persistent, dismissible "Secure your
|
||||
account" card: view and verify the phrase any time before the session
|
||||
ends; afterward it is gone — nobody has it, and recovery rests on the
|
||||
OAuth rail until the user graduates (CO6). Honest failure mode: losing
|
||||
the OAuth account *and* the service before graduating loses account
|
||||
access (the npub itself can still be preserved — CO6, step 0).
|
||||
- **Tier B3 — phrase escrowed by the service,** encrypted at rest, released
|
||||
only through the OAuth gate. Deepest custody, fullest recovery. A
|
||||
deployment offering B3 must label it as holding everything needed to
|
||||
take the account — functionally similar trust to Architecture A, with
|
||||
better privacy.
|
||||
|
||||
The tier is the deployment's call; the widget's labeling (CO7) states it in
|
||||
one sentence either way.
|
||||
|
||||
# CO5. Widget integration
|
||||
|
||||
A fifth option row in the "More sign-in options" menu (login-methods.md UI),
|
||||
under an explicit group heading:
|
||||
|
||||
```
|
||||
Centralized options
|
||||
ⓖ Continue with Google
|
||||
Convenient — AcmeSigner manages your sign-in
|
||||
```
|
||||
|
||||
- The row is configured, never built-in: a host opts in via element
|
||||
attributes (`onramp-url`, `onramp-providers="google,github"`,
|
||||
`onramp-name="AcmeSigner"`). No configuration, no row — BitLogin ships no
|
||||
centralized dependency by default.
|
||||
- Flow: the row opens the service's OAuth page (new tab, like the NIP-46
|
||||
auth_url ceremony). The service finishes OAuth and hands the widget its
|
||||
result — for Architecture B, `{loginName, password}` into the existing
|
||||
login flow; for a service that is an Architecture-A bunker, a `bunker://`
|
||||
URI into the existing NIP-46 flow. Either way the widget reuses a flow
|
||||
that already exists and already has tests.
|
||||
- Session shape: an Architecture-B session **is** a BitLogin account session
|
||||
— vault, rotation, export all work. `bitlogin-login` gains an optional
|
||||
`detail.onramp` (e.g. `"oauth:google"`) so a host can know the rail
|
||||
without a new method value; `detail.method` stays `"bitlogin"` because
|
||||
that is what the session is. Architecture-A sessions are `"nip46"`
|
||||
sessions, unchanged.
|
||||
- The credential handoff (password in a redirect/postMessage payload) must
|
||||
be same-origin-verified, single-use, and never appear in a URL that hits
|
||||
server logs (fragment or postMessage, not query string).
|
||||
|
||||
# CO6. Graduation — the journey, mechanically
|
||||
|
||||
The nudge sequence, from most passive to full self-custody. Every step is
|
||||
optional and every step keeps the same npub.
|
||||
|
||||
**Step 0 — know your npub.** The dashboard already shows it. Add a "save
|
||||
your npub" affordance (copy/share card) with one line of copy: *this is your
|
||||
public identity; it works on every Nostr app, not just this site.* Even a
|
||||
user who saves nothing else has learned their identity is portable — and a
|
||||
lost account's npub can still be referenced, cited, followed.
|
||||
|
||||
**Step 1 — secure your account (Tier B2's first-session card).** View the
|
||||
phrase, verify three words, done: the user now holds the sovereign recovery
|
||||
path and the OAuth rail becomes pure convenience. This is the cheapest
|
||||
meaningful graduation and the one to nudge hardest.
|
||||
|
||||
**Step 2 — hold your own password.** Rotate the password (existing §18
|
||||
flow) to one the user stores in their own password manager, and tell the
|
||||
service to drop its copy. OAuth stops working as a rail; the account is now
|
||||
fully native. Offered when engagement suggests readiness (CO8).
|
||||
|
||||
**Step 3 — full export.** The existing identity-export screen (npub, nsec,
|
||||
recovery export file). For users leaving for another signer entirely —
|
||||
that is also success. The goal is Nostr users, not captive BitLogin users.
|
||||
|
||||
**Graduating out of custody (Architecture A, or B3, or a B2 user past their
|
||||
first session):** the service exposes "export my key" (required — CO9), and
|
||||
the user runs the **implemented §SF10 import flow**: paste the nsec once,
|
||||
wrap it in a self-chosen BitLogin account with a fresh phrase. Same npub,
|
||||
new custody. For Architecture A the service *held* the key, so the honest
|
||||
copy must say so and recommend watching for misuse — or, for the cautious,
|
||||
identity rotation (§18.4) with its social cost stated plainly. For
|
||||
Architecture B the service never had the key, so import-graduation carries
|
||||
no such asterisk; only the password should be rotated afterward.
|
||||
|
||||
# CO7. Labeling — the honesty contract
|
||||
|
||||
Every centralized option carries, at the point of choice and on the
|
||||
dashboard, plain-language answers to three questions:
|
||||
|
||||
1. **Who can access this account?** ("AcmeSigner can sign in as you" /
|
||||
"AcmeSigner holds your key and signs for you" per tier.)
|
||||
2. **What happens if they disappear?** ("Your account keeps working with
|
||||
your recovery phrase" / "your password" / "your account is lost —
|
||||
secure it below to change that.")
|
||||
3. **How do I leave?** One tap to the relevant graduation step.
|
||||
|
||||
No dark patterns in either direction: centralized options are not shamed
|
||||
(they are the on-ramp, not the enemy), and self-custody is not oversold
|
||||
(a phrase the user loses is worse than a Google account they keep). The
|
||||
nudge is informed comfort, not fear.
|
||||
|
||||
# CO8. Nudge mechanics
|
||||
|
||||
- First session (B2): the "Secure your account" card, persistent until
|
||||
completed or explicitly dismissed with "later."
|
||||
- Milestone nudges, capped and dismissible: after the account's Nth sign-in
|
||||
or first vault connection, one card — never a modal, never blocking —
|
||||
offering step 1 or 2. A dismissed nudge stays dismissed for a long, fixed
|
||||
interval; nagging teaches users to dismiss, not to graduate.
|
||||
- The dashboard always shows the current standing ("Signed in via Google ·
|
||||
AcmeSigner manages your sign-in") with the three CO7 answers one tap away
|
||||
— the ambient reminder that costs no interruption.
|
||||
|
||||
# CO9. Requirements on a conforming service
|
||||
|
||||
A service a BitLogin deployment points at must:
|
||||
|
||||
1. Export on demand: password + recovery-export file (B), or nsec (A/B3),
|
||||
through the OAuth gate, without support tickets.
|
||||
2. Delete on demand (its stored credential), completing step 2.
|
||||
3. State its tier in a machine-readable descriptor the widget can render
|
||||
(`/.well-known/bitlogin-onramp.json`: tier, custody statement, export
|
||||
endpoints), so the CO7 labels are generated, not hand-maintained lies.
|
||||
4. Rate-limit and log unlock attempts; the endpoint is the account's
|
||||
front door.
|
||||
|
||||
# CO10. Threat model summary
|
||||
|
||||
| Actor compromised | Architecture A | Architecture B (B2) |
|
||||
| --- | --- | --- |
|
||||
| OAuth provider account | Attacker signs as user until service session revoked | Attacker gets password → full session access (§14 limits); phrase path unaffected; detectable rollback |
|
||||
| On-ramp service | Key compromised; identity rotation required | Password compromised (§14); identity key never present; rotate password |
|
||||
| Both gone (no compromise, just gone) | Account lost unless previously exported | Account intact; sign in with password/phrase if graduated, else lost access (npub preservable) |
|
||||
| Relays | Availability only, as core spec | Availability only, as core spec |
|
||||
|
||||
# CO11. Open questions
|
||||
|
||||
1. Should the widget cache the Architecture-B password in the existing
|
||||
session cache (it already caches session material for reload-survival),
|
||||
or re-OAuth every visit?
|
||||
2. `detail.onramp` shape: string rail identifier vs. structured
|
||||
`{ provider, service, tier }`.
|
||||
3. Whether Tier B2's first-session phrase window should survive a reload
|
||||
within some time budget, or end strictly with the worker.
|
||||
4. Whether the option-menu group heading "Centralized options" is the right
|
||||
label, or something warmer ("Use an account you already have") with the
|
||||
custody statement carried entirely by the sub-label.
|
||||
@@ -59,6 +59,11 @@ never needs a login UI other than BitLogin, and BitLogin's security story —
|
||||
*no method ever puts a root key on the page* — holds across every method it
|
||||
offers (§LM6).
|
||||
|
||||
A fourth family of methods — clearly labeled centralized OAuth on-ramps
|
||||
(Google, GitHub, …) that manage the credential while the account stays a
|
||||
standard capsule account — is designed separately in
|
||||
[`centralized-onramps.md`](./centralized-onramps.md).
|
||||
|
||||
# LM3. Architecture: one facade, pluggable signers
|
||||
|
||||
The widget already exposes a NIP-07-shaped provider — installed as
|
||||
|
||||
Reference in new issue
Block a user