Merge pull request #1166 from PR0M3TH3AN/bugfix/trust-seed-initialization-12779455138942724140

Fix Trusted Seed initialization and add blacklist enforcement
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-09 22:39:22 -05:00
commit f8af3c27c1
3 files changed
+76 -12

No files matched your search

+42
View File
@@ -117,6 +117,7 @@ class AccessControl {
this._hydratedFromCache = false;
this._whitelistListeners = new Set();
this._editorListeners = new Set();
this._blacklistListeners = new Set();
this._scheduleHydrateFromCache();
}
@@ -242,6 +243,7 @@ class AccessControl {
}
_applyState(state, options = {}) {
userLogger.info("[accessControl] _applyState called", { state, options });
const markLoaded = options.markLoaded !== false;
const editors = Array.isArray(state?.editors) ? state.editors : [];
const whitelist = Array.isArray(state?.whitelist) ? state.whitelist : [];
@@ -251,6 +253,8 @@ class AccessControl {
this.editors instanceof Set ? new Set(this.editors) : new Set();
const previousWhitelist =
this.whitelist instanceof Set ? new Set(this.whitelist) : new Set();
const previousBlacklist =
this.blacklist instanceof Set ? new Set(this.blacklist) : new Set();
this.editors = new Set(
dedupeNpubs([...ADMIN_EDITORS_NPUBS, ...editors])
@@ -280,6 +284,7 @@ class AccessControl {
return true;
});
this.blacklist = new Set(sanitizedBlacklist);
const blacklistChanged = !areSetsEqual(previousBlacklist, this.blacklist);
const toolkitCandidate =
(typeof window !== "undefined" ? window?.NostrTools : null) ||
@@ -326,6 +331,9 @@ class AccessControl {
if (editorsChanged) {
this._emitEditorsChange(Array.from(this.editors));
}
if (blacklistChanged) {
this._emitBlacklistChange(Array.from(this.blacklist));
}
}
_emitWhitelistChange(whitelistValues) {
@@ -346,6 +354,24 @@ class AccessControl {
}
}
_emitBlacklistChange(blacklistValues) {
if (!this._blacklistListeners.size) {
return;
}
const snapshot = Array.isArray(blacklistValues)
? [...blacklistValues]
: this.getBlacklist();
for (const listener of Array.from(this._blacklistListeners)) {
try {
listener(snapshot);
} catch (error) {
userLogger.error("accessControl blacklist listener failed", error);
}
}
}
_emitEditorsChange(editorsValues) {
if (!this._editorListeners.size) {
return;
@@ -396,9 +422,11 @@ class AccessControl {
refresh() {
if (this._isRefreshing) {
userLogger.info("[accessControl] refresh ignored (already refreshing)");
return this._refreshPromise;
}
userLogger.info("[accessControl] refresh started");
const operation = this._performRefresh();
const tracked = operation.catch((error) => {
userLogger.error("Failed to refresh admin lists:", error);
@@ -415,8 +443,10 @@ class AccessControl {
try {
await this._refreshPromise;
userLogger.info("[accessControl] ensureReady resolved");
} catch (error) {
if (!this.hasLoaded) {
userLogger.warn("[accessControl] ensureReady retry");
await this.refresh();
await this._refreshPromise;
} else {
@@ -485,6 +515,18 @@ class AccessControl {
};
}
onBlacklistChange(listener) {
if (typeof listener !== "function") {
return () => {};
}
this._blacklistListeners.add(listener);
return () => {
this._blacklistListeners.delete(listener);
};
}
async addModerator(requestorNpub, moderatorNpub) {
if (!this.isSuperAdmin(requestorNpub)) {
return { ok: false, error: "forbidden" };
+26 -12
View File
@@ -520,9 +520,11 @@ export class ModerationService {
}
if (this.trustedSeedContacts instanceof Set) {
const adminSnapshot = this.getAdminListSnapshot();
const blacklistHex = adminSnapshot.blacklistHex;
for (const seed of this.trustedSeedContacts) {
const normalized = normalizeHex(seed);
if (normalized) {
if (normalized && (!blacklistHex || !blacklistHex.has(normalized))) {
merged.add(normalized);
}
}
@@ -634,19 +636,22 @@ export class ModerationService {
}
setTrustedSeeds(seeds = []) {
this.log("[moderationService] setTrustedSeeds called", { count: seeds?.length || seeds?.size || 0 });
const sanitizedSeeds = new Set();
const adminSnapshot = this.getAdminListSnapshot();
const blacklistHex = adminSnapshot.blacklistHex;
if (seeds instanceof Set || Array.isArray(seeds)) {
for (const candidate of seeds) {
const normalized = normalizeToHex(candidate);
if (normalized) {
if (normalized && (!blacklistHex || !blacklistHex.has(normalized))) {
sanitizedSeeds.add(normalized);
}
}
} else if (seeds && typeof seeds[Symbol.iterator] === "function") {
for (const candidate of seeds) {
const normalized = normalizeToHex(candidate);
if (normalized) {
if (normalized && (!blacklistHex || !blacklistHex.has(normalized))) {
sanitizedSeeds.add(normalized);
}
}
@@ -845,7 +850,7 @@ export class ModerationService {
this.emit("trusted-mutes", { total: 0 });
this.rebuildTrustedContacts(new Set(), { previous });
this.rebuildTrustedContacts(new Set(), { previous: new Set() });
}
isTrustedMuteOwner(pubkey) {
@@ -863,6 +868,11 @@ export class ModerationService {
const previous = previousSet instanceof Set ? new Set(previousSet) : new Set();
const next = nextSet instanceof Set ? new Set(nextSet) : new Set();
this.log("[moderationService] reconcileTrustedMuteSubscriptions", {
previous: previous.size,
next: next.size,
});
if (this.viewerPubkey) {
if (this.trustedMuteSubscriptions.has(this.viewerPubkey)) {
previous.add(this.viewerPubkey);
@@ -877,14 +887,12 @@ export class ModerationService {
}
for (const value of next) {
if (!previous.has(value)) {
this.subscribeToTrustedMuteList(value).catch((error) => {
this.log(
`(moderationService) failed to subscribe to trusted mute list for ${value}`,
error,
);
});
}
this.subscribeToTrustedMuteList(value).catch((error) => {
this.log(
`(moderationService) failed to subscribe to trusted mute list for ${value}`,
error,
);
});
}
}
@@ -923,9 +931,12 @@ export class ModerationService {
}
return;
} else if (typeof record.unsub === "function") {
this.log(`[moderationService] already subscribed to ${normalized}`);
return;
}
this.log(`[moderationService] subscribing to trusted mute list for ${normalized}`);
record.promise = (async () => {
try {
await this.ensurePool();
@@ -943,6 +954,7 @@ export class ModerationService {
const relays = resolveRelayList(this.nostrClient);
if (!relays.length) {
this.log(`[moderationService] no relays available for ${normalized}`);
return;
}
@@ -987,6 +999,7 @@ export class ModerationService {
});
sub.on("eose", () => {});
record.unsub = typeof sub.unsub === "function" ? () => sub.unsub() : null;
this.log(`[moderationService] subscribed to trusted mute list for ${normalized}`);
} catch (error) {
this.log(
`(moderationService) failed to subscribe to trusted mute list for ${normalized}`,
@@ -1149,6 +1162,7 @@ export class ModerationService {
return;
}
this.log(`[moderationService] ingesting trusted mute event from ${owner} (${event.id})`);
this.applyTrustedMuteEvent(owner, event);
}
+8
View File
@@ -92,6 +92,7 @@ async function waitForAccessControl({ timeoutMs = TRUST_SEED_READY_TIMEOUT_MS }
}
export async function bootstrapTrustedSeeds() {
devLogger.info("[trustBootstrap] bootstrapTrustedSeeds called");
if (
!(
FEATURE_TRUST_SEEDS &&
@@ -99,6 +100,7 @@ export async function bootstrapTrustedSeeds() {
typeof moderationService.setTrustedSeeds === "function"
)
) {
devLogger.warn("[trustBootstrap] Trust seeds feature disabled or service missing");
return;
}
@@ -113,6 +115,7 @@ export async function bootstrapTrustedSeeds() {
editors,
fallbackSeeds: DEFAULT_TRUST_SEED_NPUBS,
});
devLogger.info(`[trustBootstrap] Applying ${seeds.size} trusted seeds`, Array.from(seeds));
moderationService.setTrustedSeeds(seeds);
if (
moderationService &&
@@ -130,6 +133,7 @@ export async function bootstrapTrustedSeeds() {
attempts += 1;
try {
const result = await waitForAccessControl();
devLogger.info("[trustBootstrap] waitForAccessControl result", result);
applySeeds();
return result;
} catch (error) {
@@ -178,4 +182,8 @@ export async function bootstrapTrustedSeeds() {
if (accessControl && typeof accessControl.onEditorsChange === "function") {
accessControl.onEditorsChange(applyOnChange);
}
if (accessControl && typeof accessControl.onBlacklistChange === "function") {
accessControl.onBlacklistChange(applyOnChange);
}
}