mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-08 05:43:04 +00:00
[auth] Re-unlock the signer on demand instead of blanket-erroring encryption ops (#57)
A persisted nsec session restores the pubkey + UI on reload but NOT the in-memory signer (the private key is only passphrase-encrypted — TODO #36). So after a refresh, adding/editing a hashtag on an nsec account dead-ended with "Connect a Nostr signer that supports encryption before managing hashtag preferences" — even though the user looks logged in. Same class blocks subscriptions/blocks/DMs/reactions. New reusable gate app.ensureEncryptionCapableSigner({ pubkey, need, promptMessage }): checks the active signer's capabilities; if it can't encrypt/sign AND a MATCHING locked nsec key exists for the account, prompts once for the passphrase and re-unlocks the signer app-wide via unlockStoredSessionActor. One unlock re-enables every signing flow until the next reload. Never unlocks a different account's key (pubkey-mismatch guard); "no-stored-key" leaves non-nsec cases (e.g. an unresponsive NIP-07 extension) to the caller's own error. Branching lives in js/nostr/ensureSignerDecision.js (pure, unit-testable without the app shell). Wired hashtag add/edit through it (ProfileHashtagController.persistHashtagPreferences): a cancelled prompt aborts silently, a bad passphrase shows its own toast, and publish never runs until the signer is confirmed. Exposed to the modal as services.ensureEncryptionCapableSigner. This is the shared machinery for the whole reloaded-nsec cluster (#51/#54/#56/#57); subscriptions/blocks/DM-send/reactions are mechanical fast-follows against the same gate (tracked in TODO #57). Tests: tests/ensure-signer-decision.test.mjs (9 — capability + recoverable/fail branching, never-wrong-key), + 2 wiring tests (gate blocks publish until ok) in tests/profile-modal-controller.test.mjs. Lint + build green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
633e946325
commit
1b79ab9d09
7 files changed
+442
No files matched your search
@@ -336,6 +336,45 @@ export class ProfileHashtagController {
|
||||
|
||||
const payload = normalizedPubkey ? { pubkey: normalizedPubkey } : {};
|
||||
|
||||
// Managing hashtags needs an encryption-capable signer. If a reloaded nsec
|
||||
// session lost its in-memory key, re-unlock it (one passphrase prompt) instead
|
||||
// of dead-ending with "connect a signer that supports encryption" (TODO #57).
|
||||
const ensureSigner =
|
||||
this.mainController.services?.ensureEncryptionCapableSigner;
|
||||
if (typeof ensureSigner === "function") {
|
||||
let ensured = { ok: true };
|
||||
try {
|
||||
ensured = await ensureSigner({
|
||||
pubkey: normalizedPubkey,
|
||||
need: "encrypt",
|
||||
promptMessage:
|
||||
"Re-enter your PIN / passphrase to unlock your key and manage hashtag preferences.",
|
||||
});
|
||||
} catch (error) {
|
||||
ensured = { ok: false, reason: "ensure-failed", error };
|
||||
}
|
||||
if (!ensured?.ok) {
|
||||
// "bad-passphrase" already showed its own toast; "cancelled" is a silent
|
||||
// user choice. Only surface the generic hint for other reasons.
|
||||
if (ensured?.reason !== "cancelled" && ensured?.reason !== "bad-passphrase") {
|
||||
const message = this.describeHashtagPreferencesError(
|
||||
Object.assign(new Error("Signer unavailable."), {
|
||||
code: "hashtag-preferences-missing-signer",
|
||||
}),
|
||||
);
|
||||
if (message) {
|
||||
this.mainController.showError(message);
|
||||
this.setHashtagStatus(message, "warning");
|
||||
}
|
||||
} else {
|
||||
this.setHashtagStatus("", "muted");
|
||||
}
|
||||
const error = new Error("Signer unavailable for hashtag preferences.");
|
||||
error.code = "hashtag-preferences-missing-signer";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const pendingMessage =
|
||||
typeof progressMessage === "string" && progressMessage.trim()
|
||||
? progressMessage.trim()
|
||||
|
||||
Reference in new issue
Block a user