Merge pull request #881 from PR0M3TH3AN/codex/update-bootstrap-and-accesscontrol-for-trust-seeds

Ensure moderation trust seeds include whitelist entries
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-24 23:01:48 -04:00
commit 2d4eb52b68
3 files changed
+134 -6

No files matched your search

+60
View File
@@ -31,6 +31,27 @@ function dedupeNpubs(values) {
);
}
function areSetsEqual(first, second) {
if (first === second) {
return true;
}
const a = first instanceof Set ? first : new Set();
const b = second instanceof Set ? second : new Set();
if (a.size !== b.size) {
return false;
}
for (const value of a) {
if (!b.has(value)) {
return false;
}
}
return true;
}
function isValidNpub(npub) {
if (typeof npub !== "string") {
return false;
@@ -59,6 +80,7 @@ class AccessControl {
this._isRefreshing = false;
this._refreshPromise = Promise.resolve();
this._hydratedFromCache = false;
this._whitelistListeners = new Set();
this._hydrateFromCache();
}
@@ -78,11 +100,15 @@ class AccessControl {
const whitelist = Array.isArray(state?.whitelist) ? state.whitelist : [];
const blacklist = Array.isArray(state?.blacklist) ? state.blacklist : [];
const previousWhitelist =
this.whitelist instanceof Set ? new Set(this.whitelist) : new Set();
this.editors = new Set(
dedupeNpubs([...ADMIN_EDITORS_NPUBS, ...editors])
);
const normalizedWhitelist = dedupeNpubs(whitelist);
this.whitelist = new Set(normalizedWhitelist);
const whitelistChanged = !areSetsEqual(previousWhitelist, this.whitelist);
const blacklistDedupe = dedupeNpubs(blacklist);
const whitelistSet = new Set(normalizedWhitelist.map(normalizeNpub));
@@ -112,6 +138,28 @@ class AccessControl {
this.hasLoaded = true;
this._hydratedFromCache = false;
}
if (whitelistChanged) {
this._emitWhitelistChange(Array.from(this.whitelist));
}
}
_emitWhitelistChange(whitelistValues) {
if (!this._whitelistListeners.size) {
return;
}
const snapshot = Array.isArray(whitelistValues)
? [...whitelistValues]
: this.getWhitelist();
for (const listener of Array.from(this._whitelistListeners)) {
try {
listener(snapshot);
} catch (error) {
userLogger.error("accessControl whitelist listener failed", error);
}
}
}
_performRefresh() {
@@ -209,6 +257,18 @@ class AccessControl {
return Array.from(this.editors);
}
onWhitelistChange(listener) {
if (typeof listener !== "function") {
return () => {};
}
this._whitelistListeners.add(listener);
return () => {
this._whitelistListeners.delete(listener);
};
}
async addModerator(requestorNpub, moderatorNpub) {
if (!this.isSuperAdmin(requestorNpub)) {
return { ok: false, error: "forbidden" };
+54 -6
View File
@@ -5,23 +5,71 @@ import {
DEFAULT_TRUST_SEED_NPUBS,
FEATURE_TRUST_SEEDS,
} from "./constants.js";
import { accessControl } from "./accessControl.js";
import moderationService from "./services/moderationService.js";
import nostrService from "./services/nostrService.js";
import r2Service from "./services/r2Service.js";
import { loadView } from "./viewManager.js";
if (
FEATURE_TRUST_SEEDS &&
moderationService &&
typeof moderationService.setTrustedSeeds === "function"
) {
function mergeSeedsWithWhitelist(source) {
const seeds = new Set(DEFAULT_TRUST_SEED_NPUBS);
if (source && typeof source[Symbol.iterator] === "function") {
for (const value of source) {
if (typeof value !== "string") {
continue;
}
const trimmed = value.trim();
if (trimmed) {
seeds.add(trimmed);
}
}
}
return seeds;
}
async function bootstrapTrustedSeeds() {
if (
!(
FEATURE_TRUST_SEEDS &&
moderationService &&
typeof moderationService.setTrustedSeeds === "function"
)
) {
return;
}
const applySeeds = (whitelist = null) => {
try {
const merged = mergeSeedsWithWhitelist(
whitelist ?? accessControl?.getWhitelist?.()
);
moderationService.setTrustedSeeds(merged);
} catch {
// Swallow bootstrap errors so login flow can continue; flag enables quick rollback.
}
};
try {
moderationService.setTrustedSeeds(DEFAULT_TRUST_SEED_NPUBS);
if (accessControl && typeof accessControl.ensureReady === "function") {
await accessControl.ensureReady();
}
} catch {
// Swallow bootstrap errors so login flow can continue; flag enables quick rollback.
}
applySeeds();
if (accessControl && typeof accessControl.onWhitelistChange === "function") {
accessControl.onWhitelistChange((nextWhitelist) => {
applySeeds(nextWhitelist);
});
}
}
bootstrapTrustedSeeds();
function mergeServices(overrides = {}) {
const merged = { nostrService, r2Service };
+20
View File
@@ -66,3 +66,23 @@ test("trusted seeds contribute to trusted report counts", (t) => {
const reporters = service.getTrustedReporters(eventId, "nudity");
assert.deepEqual(reporters.map((entry) => entry.pubkey), [seedHex]);
});
test("whitelist entries augment trusted seeds", (t) => {
withMockedNostrTools(t);
const whitelistHex = "a".repeat(64);
const whitelistNpub = `npub${whitelistHex}`;
const { service } = createModerationServiceHarness(t);
service.setTrustedSeeds([...DEFAULT_TRUST_SEED_NPUBS, whitelistNpub]);
assert.ok(
service.trustedSeedContacts.has(whitelistHex),
"whitelist npubs should populate trusted seed contacts"
);
assert.ok(
service.trustedContacts.has(whitelistHex),
"whitelist npubs should populate trusted contacts"
);
});