Files
bitvid/tests/stored-nsec-switch.test.mjs
thePR0M3TH3ANandClaude Opus 4.8 fb2f12b49d [auth] #48: fluent switch to a saved nsec account (prompt for PIN + unlock stored key)
Switching to a saved nsec profile silently failed: switchProfile -> requestLogin -> the
nsec provider was called with no passphrase, so it threw "secret-required" and the switch
was caught+logged with no user feedback. It never prompted for the PIN.

Now handleProfileSwitchRequest detects an nsec target and, before switching, prepares the
stored-key unlock:
- evaluateStoredNsecSwitch(meta, target) (pure) gates on a persisted nsec key for THIS
  account (via getStoredSessionActorMetadata); errors clearly otherwise ("log in with its
  nsec", "different account's key saved").
- showPasswordPrompt (js/ui/promptDialog.js) collects the PIN/passphrase in a styled
  dialog (Promise<string|null>, stacks above modals).
- switchProfile(pubkey, { unlockStored: true, passphrase }) — authService.switchProfile
  now forwards unlockStored/passphrase to requestLogin, which routes to the nsec
  provider's unlockStoredSessionActor to restore the signer and activate the account.
- Wrong PIN -> "Incorrect PIN/passphrase" toast (decrypt-failed).

So NIP-07 -> nsec switching works fluently. NIP-46 switching is unchanged (follow-up to
verify/extend); multiple-nsec accounts still share one stored slot (follow-up).

Tests: tests/stored-nsec-switch.test.mjs (6), tests/password-prompt.test.mjs (6). Build +
lint clean; auth suites 25/25.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-30 22:11:43 -04:00

50 lines
1.7 KiB
JavaScript

// #48: switching to a saved nsec account must prompt for the PIN and unlock its stored
// key (switchProfile alone threw "secret-required"). evaluateStoredNsecSwitch is the
// pure gate: proceed to prompt when a matching stored nsec key exists; error otherwise.
import test from "node:test";
import { strict as assert } from "node:assert";
import { evaluateStoredNsecSwitch } from "../js/app/storedNsecSwitch.js";
const A = "a".repeat(64);
const B = "b".repeat(64);
const nsecMeta = (pubkey = A) => ({ hasEncryptedKey: true, pubkey, source: "nsec" });
test("prompts when a stored nsec key matches the target account", () => {
assert.deepEqual(evaluateStoredNsecSwitch(nsecMeta(A), A), { action: "prompt" });
});
test("prompts when the stored key has no pubkey to compare (can't rule it out)", () => {
assert.deepEqual(
evaluateStoredNsecSwitch({ hasEncryptedKey: true, source: "nsec" }, A),
{ action: "prompt" },
);
});
test("errors when there is no stored session at all", () => {
assert.deepEqual(evaluateStoredNsecSwitch(null, A), {
action: "error",
reason: "no-stored-key",
});
});
test("errors when the stored key isn't an nsec (e.g. left-over non-nsec metadata)", () => {
assert.deepEqual(
evaluateStoredNsecSwitch({ hasEncryptedKey: true, pubkey: A, source: "extension" }, A),
{ action: "error", reason: "no-stored-key" },
);
});
test("errors when the stored key belongs to a DIFFERENT account", () => {
assert.deepEqual(evaluateStoredNsecSwitch(nsecMeta(A), B), {
action: "error",
reason: "pubkey-mismatch",
});
});
test("case-insensitive pubkey match", () => {
assert.deepEqual(evaluateStoredNsecSwitch(nsecMeta(A.toUpperCase()), A), {
action: "prompt",
});
});