mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 12:52:26 +00:00
Merge pull request #1166 from PR0M3TH3AN/bugfix/trust-seed-initialization-12779455138942724140
Fix Trusted Seed initialization and add blacklist enforcement
This commit is contained in:
3 files changed
+76
-12
No files matched your search
@@ -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" };
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user