[admin] fix whitelist/blacklist edits with nsec & NIP-46; loosen file-size limits

Admin list edits worked with a NIP-07 extension but silently failed with nsec and
NIP-46 (Amber). persistNostrState signed with window.nostr.signEvent directly (and
required requestDefaultExtensionPermissions), so non-extension logins threw
"nostr-extension-missing".

Fix: sign with the ACTIVE signer (getActiveSigner() from the registry — extension/nsec/
NIP-46 all expose signEvent), and only run the extension-permission prompt when the
signer is an extension (type extension/nip07). Regression test in
tests/admin-list-store.test.mjs (nsec signer used while window.nostr.signEvent throws).

Also (per maintainer request): raise the file-size lint margins to stop churning on an
arbitrary cap — THRESHOLD 1000→1200 (new files), GROWTH_MARGIN 50→200 (grandfathered).

TODOs added: #42 follow-up audit of every signing path (must use the active signer, not
window.nostr); #43 logged-in profile card at the top of the profile modal (half-on/half-
off); #44 in-modal storage selector + unlock for the upload modal.

Build + lint clean; admin-list suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-30 17:00:57 -04:00
1 parent ad83c9f8db
commit c48083dfcc
4 files changed
+83 -20

No files matched your search

+18 -18
View File
@@ -12,6 +12,7 @@ import {
import { SHORT_TIMEOUT_MS } from "./constants.js";
import {
getRegisteredNostrClient,
getActiveSigner,
requestDefaultExtensionPermissions,
} from "./nostrClientRegistry.js";
import { devLogger, userLogger } from "./utils/logger.js";
@@ -574,8 +575,7 @@ async function fetchLatestListEvent(filter, contextLabel = "admin-list") {
pubkey,
dTag,
relayUrls: relays,
// Force a full fetch: admin lists are replaceable (see
// tests/nostr/relayBatchFetcherReplaceable.test.mjs).
// Full fetch: admin lists are replaceable (relayBatchFetcherReplaceable.test).
since: 0,
}),
timeoutPromise,
@@ -682,8 +682,7 @@ async function loadCommunityBlacklistEntries() {
return [];
}
// Batch all curator blacklists into ONE REQ (was the cold-start kind-30000 storm).
// Best-effort: on failure the cached admin state carries over.
// Batch all curator blacklists into ONE REQ (best-effort; cache carries over on fail).
try {
const matched = await fetchBatchedReferenceEvents({
references,
@@ -874,20 +873,21 @@ async function persistNostrState(actorNpub, updates = {}) {
}
ensureNostrReady();
const permissionResult = await requestDefaultExtensionPermissions();
if (!permissionResult.ok) {
throw createError(
"extension-permission-denied",
"The NIP-07 extension must allow signing before updating admin lists.",
permissionResult.error,
);
// Sign with the ACTIVE signer (extension / nsec / NIP-46), not window.nostr.
const signer = getActiveSigner();
if (!signer || typeof signer.signEvent !== "function") {
throw createError("nostr-signer-missing", "No active signer to update admin lists. Please log in.");
}
const extension = window?.nostr;
if (!extension || typeof extension.signEvent !== "function") {
throw createError(
"nostr-extension-missing",
"A Nostr extension with signEvent support is required."
);
// Only a NIP-07 extension needs an explicit permission prompt before signing.
if (signer.type === "extension" || signer.type === "nip07") {
const permissionResult = await requestDefaultExtensionPermissions();
if (!permissionResult.ok) {
throw createError(
"extension-permission-denied",
"The NIP-07 extension must allow signing before updating admin lists.",
permissionResult.error,
);
}
}
const actorHex = decodeNpubToHex(actorNpub);
@@ -950,7 +950,7 @@ async function persistNostrState(actorNpub, updates = {}) {
let signedEvent;
try {
signedEvent = await extension.signEvent(event);
signedEvent = await signer.signEvent(event);
} catch (error) {
throw createError("signature-failed", "Failed to sign admin list event.", error);
}