ADRs
ADR-0006: Bootstrap pairing
One-time pairing codes instead of QR for first-time node association.
Status
Accepted
Context
Storage Nodes are separate machines (plan §11) that must be associated with an account before they can sync. An earlier option was QR-based LAN node bootstrap (plan §7b "Non-goals"), which requires the phone/device and the node to be co-located and reachable on the same network. The server unit is self-hosted and exposed at a single public origin (plan §3b), and nodes connect outbound over WSS, so first-time association needs a credential that crosses the public internet without a shared LAN — and that never becomes a long-lived secret.
Decision
First-time node bootstrap uses a one-time pairing code (NODUS-XXXX-XXXX),
not a QR code:
- Minted by an authenticated account via
POST /pairing/codes. Only the SHA-256 hash of the normalized code is stored; the plaintext is returned once and never logged. TTL: 15 minutes. - Redeemed by the node through the open, IP rate-limited
POST /pairing/codes/redeem, which atomically consumes the code and upserts thestorage_nodesrow bound to the issuing account in one transaction (reusing the first-nodeis_primaryrule). A rejected registration (e.g. a node owned by another account) rolls the transaction back, so the code is not burned and ownership never moves accounts. The node's Ed25519 key is immutable pernode_id: a changed key is rejected withnode_key_mismatch, same-key re-registration is an idempotent no-op, and a non-ACTIVEnode is never silently revived. At most oneis_primarynode per account is enforced by a partial unique index. - The code is a bootstrap credential only. It attaches the node's
persistent Ed25519 identity to an account and is never used again; permanent
authentication remains the existing
/wschallenge-response (§8). No new identity layer, token, or long-lived auth protocol is introduced. nodus node pairresolves the relay URL with the locked precedence (CLI--relay>config.toml relay_url>NODUS_RELAY_URL> none), redeems over HTTPS, and persistsrelay_urlonly after success. First-run resolution never defaults to localhost/127.0.0.1.
Consequences
- Positive: works across the public internet with no co-located scanner; hashed storage + single-use + 15-minute TTL + IP rate limiting + HTTPS-only mitigate code theft and replay; node identity and the existing auth path are reused unchanged; deployment is self-hosted with no project-operated relay.
- Negative: a human transfers the code and URL to the node (a command rather than a scan); re-pairing and key rotation are v1 non-goals, so an intentional key rotation must be a separate explicit flow.
- Deferred, not precluded: QR pairing can later encode
{relay_url, code}and reuse the exact same redemption mechanism, so this decision does not block the QR UX if it is revisited.