mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
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
This commit is contained in:
8 files changed
+249
-17
No files matched your search
@@ -88,10 +88,11 @@ Implemented: registration (§15), any-device password login with rollback and
|
||||
relay-disagreement detection (§16), phrase recovery with recovery-capsule
|
||||
refresh (§17), known-password change with mandatory tombstone + NIP-09
|
||||
deletion (§18), replica repair (§24.4), recovery export (§19.5), NIP-19
|
||||
nsec/npub encoding, a NIP-44 v2 implementation for the `window.nostr.nip44`
|
||||
surface, bootstrap/discovery relay list plumbing (§19), and the full
|
||||
crypto stack from §11 (Argon2id, HKDF, ScalarExpand, AES-256-GCM, JCS,
|
||||
fixed padding buckets).
|
||||
nsec/npub encoding, importing an existing Nostr `nsec` as the everyday
|
||||
identity (§28.1, §SF10 — with a preview-before-commit UI step), a NIP-44 v2
|
||||
implementation for the `window.nostr.nip44` surface, bootstrap/discovery
|
||||
relay list plumbing (§19), and the full crypto stack from §11 (Argon2id,
|
||||
HKDF, ScalarExpand, AES-256-GCM, JCS, fixed padding buckets).
|
||||
|
||||
**Deferred / not in this pass:**
|
||||
- Phase 2 messaging (NIP-17 inbox/compose) — the NIP-44 primitive is built
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# BitLogin Second Factor, Backup Codes, and Imported Identities
|
||||
|
||||
**Document version:** 0.1 (draft addendum to the v0.3 core specification)
|
||||
**Status:** Design proposal — not implemented, not final
|
||||
**Status:** Mixed. §SF10 (imported identities) is **implemented**. Phone-approval
|
||||
sign-in (§SF5–SF7) and backup codes (§SF8) are **design proposals only** — and
|
||||
backup codes are unlikely to ship, since the recovery phrase already covers the
|
||||
same ground (see §SF8 note). The "why not TOTP" analysis (§SF2) is a settled
|
||||
rejection, kept on the record.
|
||||
**Depends on:** `docs/spec.md` (§ references below point there unless marked §SF)
|
||||
|
||||
---
|
||||
@@ -307,7 +311,7 @@ Lost everything incl. phrase → unrecoverable (unchanged, §19.4)
|
||||
|
||||
---
|
||||
|
||||
# SF10. Imported identities
|
||||
# SF10. Imported identities — IMPLEMENTED
|
||||
|
||||
An existing Nostr identity may be used as the everyday identity under all
|
||||
of the above (§28.1 anticipated this; the everyday key is by design never
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { describe, expect, it, beforeEach, afterEach } from "vitest";
|
||||
import { MockRelay } from "../test-support/mockRelay.js";
|
||||
import { generatePassphrase } from "./passphrase.js";
|
||||
import { registerAccount } from "./create.js";
|
||||
import { registerAccount, importAccount, decodeEverydayPrivateKey } from "./create.js";
|
||||
import { generatePrivateKey, getPublicKeyHex } from "../crypto/secp256k1.js";
|
||||
import { encodeNsec, encodeNpub } from "../nostr/nip19.js";
|
||||
import { bytesToHex } from "../crypto/encoding.js";
|
||||
import { loginWithPassword } from "./login.js";
|
||||
import { recoverWithPhrase, completeRecoveryWithNewCredentials } from "./recover.js";
|
||||
import { changePassword } from "./changePassword.js";
|
||||
@@ -50,6 +53,50 @@ describe("BitLogin Phase 0 end-to-end scenarios (§32)", () => {
|
||||
ARGON2_TIMEOUT
|
||||
);
|
||||
|
||||
it(
|
||||
"import an existing nsec -> clean-device login and phrase recovery both reproduce the imported npub (§SF10)",
|
||||
async () => {
|
||||
// A pre-existing Nostr identity the user already controls.
|
||||
const existingKey = generatePrivateKey();
|
||||
const existingPubkey = getPublicKeyHex(existingKey);
|
||||
const nsec = encodeNsec(existingKey);
|
||||
const npub = encodeNpub(existingPubkey);
|
||||
|
||||
const loginName = "importer";
|
||||
const password = generatePassphrase().secret;
|
||||
|
||||
// decodeEverydayPrivateKey accepts both nsec and 64-char hex forms.
|
||||
const existingKeyHex = bytesToHex(existingKey);
|
||||
expect(getPublicKeyHex(decodeEverydayPrivateKey(nsec))).toBe(existingPubkey);
|
||||
expect(getPublicKeyHex(decodeEverydayPrivateKey(existingKeyHex))).toBe(existingPubkey);
|
||||
|
||||
const registration = await importAccount({ nsecOrHex: nsec, loginName, password, vaultRelayUrls });
|
||||
expect(registration.imported).toBe(true);
|
||||
expect(registration.everydayPublicKey).toBe(existingPubkey);
|
||||
|
||||
// Clean-device password login yields the imported identity, unchanged.
|
||||
const login = await loginWithPassword({ loginName, password, vaultRelayUrls });
|
||||
expect(login.everydayPublicKey).toBe(existingPubkey);
|
||||
expect(encodeNpub(login.everydayPublicKey)).toBe(npub);
|
||||
|
||||
// The freshly generated BitLogin phrase recovers the SAME imported identity.
|
||||
const recovered = await recoverWithPhrase({
|
||||
phrase: registration.recoveryPhrase,
|
||||
vaultRelayUrls,
|
||||
discoveryRelayUrls: vaultRelayUrls
|
||||
});
|
||||
expect(recovered.everydayPublicKey).toBe(existingPubkey);
|
||||
},
|
||||
ARGON2_TIMEOUT
|
||||
);
|
||||
|
||||
it("rejects malformed keys on import (§SF10)", () => {
|
||||
expect(() => decodeEverydayPrivateKey("not-a-key")).toThrow();
|
||||
expect(() => decodeEverydayPrivateKey("nsec1invalid")).toThrow();
|
||||
expect(() => decodeEverydayPrivateKey("00".repeat(32))).toThrow(); // zero scalar is invalid
|
||||
expect(() => decodeEverydayPrivateKey("ab".repeat(20))).toThrow(); // wrong length hex
|
||||
});
|
||||
|
||||
it(
|
||||
"wrong password on a real account is indistinguishable from a nonexistent account (§16.3)",
|
||||
async () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/** Registration flow (§15). Recovery capsule is built and published before the credential capsule. */
|
||||
import { randomEntropy128, randomAccountId } from "../crypto/random.js";
|
||||
import { bytesToBase64url } from "../crypto/encoding.js";
|
||||
import { generatePrivateKey, getPublicKeyHex } from "../crypto/secp256k1.js";
|
||||
import { bytesToBase64url, hexToBytes } from "../crypto/encoding.js";
|
||||
import { generatePrivateKey, getPublicKeyHex, isValidScalar } from "../crypto/secp256k1.js";
|
||||
import { decodeNsec } from "../nostr/nip19.js";
|
||||
import { entropyToRecoveryPhrase, recoveryPhraseToSeed } from "../crypto/bip39.js";
|
||||
import { derivePasswordKeys, deriveRecoveryKeys, normalizeLoginName } from "./normalize.js";
|
||||
import { RelayPool } from "../nostr/pool.js";
|
||||
@@ -19,6 +20,13 @@ export interface RegisterAccountParams {
|
||||
/** A client-generated credential (§9.2); manual passwords are prohibited in the alpha (§9.3). */
|
||||
password: string;
|
||||
vaultRelayUrls: string[];
|
||||
/**
|
||||
* Optional existing everyday identity to wrap instead of generating a fresh one (§28.1, §SF10).
|
||||
* The everyday key is never derived from any BitLogin secret (§7.2), so an imported key is
|
||||
* indistinguishable to the rest of the protocol — only the origin differs. Must be a valid
|
||||
* 32-byte secp256k1 scalar; the caller is responsible for decoding an `nsec` first.
|
||||
*/
|
||||
everydayPrivateKey?: Uint8Array;
|
||||
minAcknowledgements?: number;
|
||||
timeoutMs?: number;
|
||||
/** Testing hook; defaults to the current time. */
|
||||
@@ -33,6 +41,8 @@ export interface RegisterAccountResult {
|
||||
recoveryPublicKey: string;
|
||||
locatorPublicKey: string;
|
||||
accountId: string;
|
||||
/** True when the everyday identity was imported (§SF10) rather than freshly generated. */
|
||||
imported: boolean;
|
||||
credentialEvent: NostrEvent;
|
||||
recoveryEvent: NostrEvent;
|
||||
credentialPublish: PublishVerificationResult;
|
||||
@@ -49,8 +59,12 @@ export async function registerAccount(params: RegisterAccountParams): Promise<Re
|
||||
const { recoveryPrivateKey, capsuleKey: recoveryCapsuleKey } = deriveRecoveryKeys(bip39Seed);
|
||||
const recoveryPublicKey = getPublicKeyHex(recoveryPrivateKey);
|
||||
|
||||
// §15.3 — everyday identity
|
||||
const everydayPrivateKey = generatePrivateKey();
|
||||
// §15.3 — everyday identity: generated fresh, or an imported existing key (§28.1, §SF10).
|
||||
const imported = params.everydayPrivateKey !== undefined;
|
||||
if (imported && !isValidScalar(params.everydayPrivateKey!)) {
|
||||
throw new RegistrationFailedError("The provided key is not a valid secp256k1 private key.");
|
||||
}
|
||||
const everydayPrivateKey = imported ? params.everydayPrivateKey! : generatePrivateKey();
|
||||
const everydayPublicKey = getPublicKeyHex(everydayPrivateKey);
|
||||
const accountId = bytesToBase64url(randomAccountId());
|
||||
|
||||
@@ -129,9 +143,47 @@ export async function registerAccount(params: RegisterAccountParams): Promise<Re
|
||||
recoveryPublicKey,
|
||||
locatorPublicKey,
|
||||
accountId,
|
||||
imported,
|
||||
credentialEvent,
|
||||
recoveryEvent,
|
||||
credentialPublish,
|
||||
recoveryPublish
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper for importing an existing Nostr identity (§28.1, §SF10).
|
||||
* Accepts an `nsec` (bech32) or 64-char lowercase-hex private key, decodes it,
|
||||
* and registers a new BitLogin account (fresh recovery phrase + password)
|
||||
* around that key. Everything downstream is identical to a generated account —
|
||||
* only the everyday key's origin differs.
|
||||
*
|
||||
* Honest limitation (§SF10): BitLogin secures its own storage of the key, but
|
||||
* copies that already exist elsewhere (extensions, other signers, clipboard
|
||||
* history, backups) remain live attack surface outside its reach. If the key
|
||||
* may already be compromised, rotate to a fresh identity instead of importing.
|
||||
*/
|
||||
export async function importAccount(
|
||||
params: Omit<RegisterAccountParams, "everydayPrivateKey"> & { nsecOrHex: string }
|
||||
): Promise<RegisterAccountResult> {
|
||||
const { nsecOrHex, ...rest } = params;
|
||||
const everydayPrivateKey = decodeEverydayPrivateKey(nsecOrHex);
|
||||
return registerAccount({ ...rest, everydayPrivateKey });
|
||||
}
|
||||
|
||||
/** Decodes an `nsec` or 64-char hex string to a validated 32-byte scalar (§SF10). */
|
||||
export function decodeEverydayPrivateKey(nsecOrHex: string): Uint8Array {
|
||||
const trimmed = nsecOrHex.trim();
|
||||
let key: Uint8Array;
|
||||
if (trimmed.startsWith("nsec1")) {
|
||||
key = decodeNsec(trimmed);
|
||||
} else if (/^[0-9a-fA-F]{64}$/u.test(trimmed)) {
|
||||
key = hexToBytes(trimmed.toLowerCase());
|
||||
} else {
|
||||
throw new RegistrationFailedError("Enter a valid nsec (nsec1…) or a 64-character hex private key.");
|
||||
}
|
||||
if (!isValidScalar(key)) {
|
||||
throw new RegistrationFailedError("The provided key is not a valid secp256k1 private key.");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { WIDGET_STYLES } from "./styles.js";
|
||||
|
||||
type Screen =
|
||||
| "welcome"
|
||||
| "import-key"
|
||||
| "create-name"
|
||||
| "create-credential"
|
||||
| "confirm-phrase"
|
||||
@@ -39,6 +40,10 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
private recoveryPhrase = "";
|
||||
private confirmSlots: ConfirmSlot[] = [];
|
||||
|
||||
// Import flow (§SF10). importKey holds the pasted nsec/hex only until registration completes.
|
||||
private importKey = "";
|
||||
private importPreviewNpub = "";
|
||||
|
||||
private recoverPhraseInput = "";
|
||||
private recoveredPreview: { generalRelaysCount: number; dmRelaysCount: number; chainWarning?: string } | null = null;
|
||||
private newCredentialAfterRecovery = "";
|
||||
@@ -116,8 +121,19 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
switch (action) {
|
||||
case "goto-create":
|
||||
this.loginName = "";
|
||||
this.importKey = "";
|
||||
this.importPreviewNpub = "";
|
||||
this.goto("create-name");
|
||||
return;
|
||||
case "goto-import":
|
||||
this.importKey = "";
|
||||
this.importPreviewNpub = "";
|
||||
this.goto("import-key");
|
||||
return;
|
||||
case "preview-import":
|
||||
return this.handlePreviewImport();
|
||||
case "import-continue":
|
||||
return this.handleImportContinue();
|
||||
case "goto-login":
|
||||
this.goto("login");
|
||||
return;
|
||||
@@ -166,6 +182,8 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
const formName = form.dataset.form;
|
||||
try {
|
||||
switch (formName) {
|
||||
case "import-key":
|
||||
return await this.handlePreviewImport();
|
||||
case "create-name":
|
||||
return this.handleCreateNameSubmit();
|
||||
case "create-credential":
|
||||
@@ -188,6 +206,39 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async handlePreviewImport(): Promise<void> {
|
||||
const pasted = this.field("importKey").trim();
|
||||
if (!pasted) {
|
||||
this.errorMessage = "Paste your nsec or hex private key first.";
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
this.setBusy(true);
|
||||
try {
|
||||
const preview = await this.worker.previewImportKey({ nsecOrHex: pasted });
|
||||
this.importKey = pasted;
|
||||
this.importPreviewNpub = preview.npub;
|
||||
this.busy = false;
|
||||
this.render();
|
||||
} catch (err) {
|
||||
this.importKey = "";
|
||||
this.importPreviewNpub = "";
|
||||
this.fail(err);
|
||||
}
|
||||
}
|
||||
|
||||
private handleImportContinue(): void {
|
||||
// Reached only after a successful preview; move into the normal name -> credential flow,
|
||||
// which will register with importKey set (§SF10).
|
||||
if (!this.importKey || !this.importPreviewNpub) {
|
||||
this.errorMessage = "Check your key before continuing.";
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
this.loginName = "";
|
||||
this.goto("create-name");
|
||||
}
|
||||
|
||||
private handleCreateNameSubmit(): void {
|
||||
const name = this.field("loginName").trim().toLowerCase();
|
||||
if (!isValidLoginName(name)) {
|
||||
@@ -209,7 +260,14 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
this.setBusy(true);
|
||||
const result = await this.worker.register({ loginName: this.loginName, password: this.generatedCredential });
|
||||
const result = await this.worker.register({
|
||||
loginName: this.loginName,
|
||||
password: this.generatedCredential,
|
||||
importKey: this.importKey || undefined
|
||||
});
|
||||
// The pasted key is now wrapped in the capsules; drop the main-thread copy (§11.10).
|
||||
this.importKey = "";
|
||||
this.importPreviewNpub = "";
|
||||
this.recoveryPhrase = result.recoveryPhrase;
|
||||
const words = this.recoveryPhrase.split(" ");
|
||||
const indices = pickRandomIndices(words.length, 3);
|
||||
@@ -338,13 +396,43 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
${this.renderError()}
|
||||
<button class="primary" data-action="goto-login">Sign in</button>
|
||||
<button class="secondary" data-action="goto-create">Create account</button>
|
||||
<button class="link" data-action="goto-import">Import an existing Nostr key</button>
|
||||
<button class="link" data-action="goto-recover">Forgot password? Recover with phrase</button>
|
||||
`;
|
||||
|
||||
case "import-key": {
|
||||
const previewed = !!this.importPreviewNpub;
|
||||
return `
|
||||
<h2>Import an existing Nostr key</h2>
|
||||
<p class="sub">Wrap a Nostr identity you already control in a BitLogin login name and password. Your key never changes — you just get a friendlier way in.</p>
|
||||
<div class="notice warn">Pasting a private key into any web page is risky. Only do this on a BitLogin build you trust, and clear your clipboard afterward. BitLogin can't secure copies of this key that already exist elsewhere.</div>
|
||||
${this.renderError()}
|
||||
<form data-form="import-key">
|
||||
<label for="importKey">Your nsec or hex private key</label>
|
||||
<input type="password" name="importKey" id="importKey" autocomplete="off" placeholder="nsec1… or 64-character hex" required />
|
||||
<button class="secondary" type="submit" ${this.busy ? "disabled" : ""}>
|
||||
${this.busy ? '<span class="spinner"></span>Checking…' : "Check key"}
|
||||
</button>
|
||||
</form>
|
||||
${
|
||||
previewed
|
||||
? `<div class="notice info">This key's public identity:</div>
|
||||
<div class="credential-box">${escapeHtml(this.importPreviewNpub)}</div>
|
||||
<button class="primary" type="button" data-action="import-continue">This is my identity — continue</button>`
|
||||
: ""
|
||||
}
|
||||
<button class="link" data-action="goto-welcome">Back</button>
|
||||
`;
|
||||
}
|
||||
|
||||
case "create-name":
|
||||
return `
|
||||
<h2>Create your BitLogin</h2>
|
||||
<p class="sub">Choose a login name. It's a convenience, not a secret (it contributes no security).</p>
|
||||
<h2>${this.importKey ? "Set up your login" : "Create your BitLogin"}</h2>
|
||||
<p class="sub">${
|
||||
this.importKey
|
||||
? "Choose a login name for your imported identity. It's a convenience, not a secret."
|
||||
: "Choose a login name. It's a convenience, not a secret (it contributes no security)."
|
||||
}</p>
|
||||
${this.renderError()}
|
||||
<form data-form="create-name">
|
||||
<label for="loginName">Login name</label>
|
||||
@@ -368,7 +456,13 @@ export class BitLoginAuthElement extends HTMLElement {
|
||||
I have saved this password somewhere safe.
|
||||
</label>
|
||||
<button class="primary" type="submit" ${this.busy ? "disabled" : ""}>
|
||||
${this.busy ? '<span class="spinner"></span>Creating account…' : "Create account"}
|
||||
${
|
||||
this.busy
|
||||
? `<span class="spinner"></span>${this.importKey ? "Importing…" : "Creating account…"}`
|
||||
: this.importKey
|
||||
? "Import account"
|
||||
: "Create account"
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
`;
|
||||
|
||||
@@ -15,10 +15,13 @@ import {
|
||||
buildProfileEvent,
|
||||
buildRecoveryExport,
|
||||
repairReplicas,
|
||||
importAccount,
|
||||
decodeEverydayPrivateKey,
|
||||
NostrSigner,
|
||||
type RecoveredIdentity
|
||||
} from "@bitlogin/core/account";
|
||||
import { RelayPool, countAcknowledgements, BUILTIN_VAULT_RELAYS, BUILTIN_DISCOVERY_RELAYS, encodeNsec, encodeNpub, type NostrEvent } from "@bitlogin/core/nostr";
|
||||
import { getPublicKeyHex } from "@bitlogin/core/crypto";
|
||||
import { IndexedDbKeyValueStore } from "../storage/indexedDbStore.js";
|
||||
import type {
|
||||
WorkerRequest,
|
||||
@@ -32,7 +35,8 @@ import type {
|
||||
PublishProfilePayload,
|
||||
SignEventPayload,
|
||||
Nip44EncryptPayload,
|
||||
Nip44DecryptPayload
|
||||
Nip44DecryptPayload,
|
||||
PreviewImportKeyPayload
|
||||
} from "./protocol.js";
|
||||
|
||||
interface SessionState {
|
||||
@@ -94,7 +98,9 @@ async function handle(action: string, payload: unknown): Promise<unknown> {
|
||||
|
||||
case "register": {
|
||||
const p = payload as RegisterPayload;
|
||||
const result = await registerAccount({ loginName: p.loginName, password: p.password, vaultRelayUrls });
|
||||
const result = p.importKey
|
||||
? await importAccount({ nsecOrHex: p.importKey, loginName: p.loginName, password: p.password, vaultRelayUrls })
|
||||
: await registerAccount({ loginName: p.loginName, password: p.password, vaultRelayUrls });
|
||||
clearSession();
|
||||
session.signer = new NostrSigner(result.everydayPrivateKey);
|
||||
session.everydayPrivateKey = result.everydayPrivateKey;
|
||||
@@ -107,11 +113,23 @@ async function handle(action: string, payload: unknown): Promise<unknown> {
|
||||
everydayPublicKey: result.everydayPublicKey,
|
||||
recoveryPublicKey: result.recoveryPublicKey,
|
||||
accountId: result.accountId,
|
||||
imported: result.imported,
|
||||
credentialEventId: result.credentialEvent.id,
|
||||
recoveryEventId: result.recoveryEvent.id
|
||||
};
|
||||
}
|
||||
|
||||
case "previewImportKey": {
|
||||
// Validates and previews the npub WITHOUT publishing anything, so the user can
|
||||
// confirm they pasted the right key before committing to registration (§SF10).
|
||||
const p = payload as PreviewImportKeyPayload;
|
||||
const key = decodeEverydayPrivateKey(p.nsecOrHex);
|
||||
const everydayPublicKey = getPublicKeyHex(key);
|
||||
const preview = { everydayPublicKey, npub: encodeNpub(everydayPublicKey) };
|
||||
key.fill(0); // §11.10 — the previewed key is not retained
|
||||
return preview;
|
||||
}
|
||||
|
||||
case "login": {
|
||||
const p = payload as LoginPayload;
|
||||
const result = await loginWithPassword({ loginName: p.loginName, password: p.password, vaultRelayUrls, store });
|
||||
|
||||
@@ -19,16 +19,27 @@ export interface ConfigurePayload {
|
||||
export interface RegisterPayload {
|
||||
loginName: string;
|
||||
password: string;
|
||||
/** When present, import this existing identity (nsec or 64-char hex) instead of generating one (§SF10). */
|
||||
importKey?: string;
|
||||
}
|
||||
export interface RegisterResult {
|
||||
recoveryPhrase: string;
|
||||
everydayPublicKey: string;
|
||||
recoveryPublicKey: string;
|
||||
accountId: string;
|
||||
imported: boolean;
|
||||
credentialEventId: string;
|
||||
recoveryEventId: string;
|
||||
}
|
||||
|
||||
export interface PreviewImportKeyPayload {
|
||||
nsecOrHex: string;
|
||||
}
|
||||
export interface PreviewImportKeyResult {
|
||||
everydayPublicKey: string;
|
||||
npub: string;
|
||||
}
|
||||
|
||||
export interface LoginPayload {
|
||||
loginName: string;
|
||||
password: string;
|
||||
@@ -122,6 +133,7 @@ export interface SessionStatusResult {
|
||||
export type WorkerActionMap = {
|
||||
configure: [ConfigurePayload, Record<string, never>];
|
||||
register: [RegisterPayload, RegisterResult];
|
||||
previewImportKey: [PreviewImportKeyPayload, PreviewImportKeyResult];
|
||||
login: [LoginPayload, LoginResult];
|
||||
recover: [RecoverPayload, RecoverResult];
|
||||
completeRecovery: [CompleteRecoveryPayload, CompleteRecoveryResult];
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
WorkerResponse,
|
||||
ConfigurePayload,
|
||||
RegisterPayload,
|
||||
PreviewImportKeyPayload,
|
||||
LoginPayload,
|
||||
RecoverPayload,
|
||||
CompleteRecoveryPayload,
|
||||
@@ -61,6 +62,9 @@ export class WorkerClient {
|
||||
register(payload: RegisterPayload) {
|
||||
return this.call("register", payload);
|
||||
}
|
||||
previewImportKey(payload: PreviewImportKeyPayload) {
|
||||
return this.call("previewImportKey", payload);
|
||||
}
|
||||
login(payload: LoginPayload) {
|
||||
return this.call("login", payload);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user