mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-03 03:12:31 +00:00
Enforce admin list invariants
This commit is contained in:
1 parent
a56568e3c7
commit
66595edaf6
2 files changed
+191
-16
No files matched your search
+44
-7
@@ -86,12 +86,35 @@ class AccessControl {
|
||||
this.editors = new Set(
|
||||
dedupeNpubs([...ADMIN_EDITORS_NPUBS, ...editors])
|
||||
);
|
||||
this.whitelist = new Set(
|
||||
dedupeNpubs([...ADMIN_INITIAL_WHITELIST, ...whitelist])
|
||||
);
|
||||
this.blacklist = new Set(
|
||||
dedupeNpubs([...ADMIN_INITIAL_BLACKLIST, ...blacklist])
|
||||
);
|
||||
const normalizedWhitelist = dedupeNpubs([
|
||||
...ADMIN_INITIAL_WHITELIST,
|
||||
...whitelist,
|
||||
]);
|
||||
this.whitelist = new Set(normalizedWhitelist);
|
||||
|
||||
const blacklistDedupe = dedupeNpubs([
|
||||
...ADMIN_INITIAL_BLACKLIST,
|
||||
...blacklist,
|
||||
]);
|
||||
const whitelistSet = new Set(normalizedWhitelist.map(normalizeNpub));
|
||||
const adminGuardSet = new Set([
|
||||
normalizeNpub(ADMIN_SUPER_NPUB),
|
||||
...Array.from(this.editors),
|
||||
]);
|
||||
const sanitizedBlacklist = blacklistDedupe.filter((npub) => {
|
||||
const normalized = normalizeNpub(npub);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (whitelistSet.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
if (adminGuardSet.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.blacklist = new Set(sanitizedBlacklist);
|
||||
|
||||
this.whitelistEnabled = getWhitelistMode();
|
||||
this.hasLoaded = true;
|
||||
@@ -330,7 +353,13 @@ class AccessControl {
|
||||
|
||||
isBlacklisted(npub) {
|
||||
const normalized = normalizeNpub(npub);
|
||||
return normalized ? this.blacklist.has(normalized) : false;
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (this.whitelist.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return this.blacklist.has(normalized);
|
||||
}
|
||||
|
||||
canAccess(candidate) {
|
||||
@@ -355,6 +384,14 @@ class AccessControl {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isAdminEditor(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.whitelist.has(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.blacklist.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+147
-9
@@ -51,6 +51,77 @@ function dedupeNpubs(values) {
|
||||
);
|
||||
}
|
||||
|
||||
function canDecodeNpub() {
|
||||
return !!(
|
||||
typeof window !== "undefined" &&
|
||||
window?.NostrTools?.nip19 &&
|
||||
typeof window.NostrTools.nip19.decode === "function"
|
||||
);
|
||||
}
|
||||
|
||||
function isLikelyNpub(value) {
|
||||
if (typeof value !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canDecodeNpub()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = window.NostrTools.nip19.decode(trimmed);
|
||||
return decoded?.type === "npub";
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeNpubList(values) {
|
||||
return dedupeNpubs(values).filter(isLikelyNpub);
|
||||
}
|
||||
|
||||
function sanitizeAdminState(state = {}) {
|
||||
const sanitizedEditors = sanitizeNpubList(state.editors || []);
|
||||
const sanitizedWhitelist = sanitizeNpubList(state.whitelist || []);
|
||||
const sanitizedWhitelistSet = new Set([
|
||||
...ADMIN_INITIAL_WHITELIST.map(normalizeNpub),
|
||||
...sanitizedWhitelist,
|
||||
]);
|
||||
|
||||
const adminGuardSet = new Set([
|
||||
normalizeNpub(ADMIN_SUPER_NPUB),
|
||||
...ADMIN_EDITORS_NPUBS.map(normalizeNpub),
|
||||
...sanitizedEditors,
|
||||
]);
|
||||
|
||||
const sanitizedBlacklist = sanitizeNpubList(state.blacklist || []).filter(
|
||||
(npub) => {
|
||||
const normalized = normalizeNpub(npub);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (adminGuardSet.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
if (sanitizedWhitelistSet.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
editors: sanitizedEditors,
|
||||
whitelist: Array.from(sanitizedWhitelistSet),
|
||||
blacklist: sanitizedBlacklist,
|
||||
};
|
||||
}
|
||||
|
||||
function loadJSONList(key) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return null;
|
||||
@@ -102,7 +173,7 @@ function migrateLegacyList(targetKey, legacyKey, fallback) {
|
||||
}
|
||||
|
||||
async function loadLocalState() {
|
||||
return {
|
||||
const state = {
|
||||
editors: migrateLegacyList(ADMIN_EDITORS_KEY, null, ADMIN_EDITORS_NPUBS),
|
||||
whitelist: migrateLegacyList(
|
||||
ADMIN_WHITELIST_KEY,
|
||||
@@ -115,17 +186,35 @@ async function loadLocalState() {
|
||||
ADMIN_INITIAL_BLACKLIST
|
||||
),
|
||||
};
|
||||
|
||||
return sanitizeAdminState(state);
|
||||
}
|
||||
|
||||
async function persistLocalState(_actorNpub, updates = {}) {
|
||||
if (
|
||||
!updates ||
|
||||
(!updates.editors && !updates.whitelist && !updates.blacklist)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentState = await loadLocalState();
|
||||
const merged = {
|
||||
editors: updates.editors ? updates.editors : currentState.editors,
|
||||
whitelist: updates.whitelist ? updates.whitelist : currentState.whitelist,
|
||||
blacklist: updates.blacklist ? updates.blacklist : currentState.blacklist,
|
||||
};
|
||||
|
||||
const sanitized = sanitizeAdminState(merged);
|
||||
|
||||
if (updates.editors) {
|
||||
saveJSONList(ADMIN_EDITORS_KEY, dedupeNpubs(updates.editors));
|
||||
saveJSONList(ADMIN_EDITORS_KEY, sanitized.editors);
|
||||
}
|
||||
if (updates.whitelist) {
|
||||
saveJSONList(ADMIN_WHITELIST_KEY, dedupeNpubs(updates.whitelist));
|
||||
saveJSONList(ADMIN_WHITELIST_KEY, sanitized.whitelist);
|
||||
}
|
||||
if (updates.blacklist) {
|
||||
saveJSONList(ADMIN_BLACKLIST_KEY, dedupeNpubs(updates.blacklist));
|
||||
saveJSONList(ADMIN_BLACKLIST_KEY, sanitized.blacklist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,11 +326,11 @@ async function loadNostrState() {
|
||||
const whitelist = await loadNostrList(LIST_IDENTIFIERS.whitelist);
|
||||
const blacklist = await loadNostrList(LIST_IDENTIFIERS.blacklist);
|
||||
|
||||
return {
|
||||
return sanitizeAdminState({
|
||||
editors,
|
||||
whitelist,
|
||||
blacklist,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function persistNostrState(actorNpub, updates = {}) {
|
||||
@@ -259,9 +348,58 @@ async function persistNostrState(actorNpub, updates = {}) {
|
||||
throw createError("invalid npub", "Unable to decode the actor npub.");
|
||||
}
|
||||
|
||||
const entries = Object.entries(updates).filter(([key, value]) => {
|
||||
return key in LIST_IDENTIFIERS && Array.isArray(value);
|
||||
});
|
||||
const entries = Object.entries(updates)
|
||||
.filter(([key, value]) => {
|
||||
return key in LIST_IDENTIFIERS && Array.isArray(value);
|
||||
})
|
||||
.map(([key, value]) => {
|
||||
const base = sanitizeNpubList(value);
|
||||
if (key === "blacklist") {
|
||||
const guardSet = new Set([
|
||||
normalizeNpub(ADMIN_SUPER_NPUB),
|
||||
...ADMIN_EDITORS_NPUBS.map(normalizeNpub),
|
||||
...sanitizeNpubList(updates.editors || []),
|
||||
]);
|
||||
const whitelistSet = new Set(
|
||||
sanitizeNpubList([
|
||||
...ADMIN_INITIAL_WHITELIST,
|
||||
...(updates.whitelist || []),
|
||||
])
|
||||
);
|
||||
const sanitizedBlacklist = base.filter((npub) => {
|
||||
const normalized = normalizeNpub(npub);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (guardSet.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
if (whitelistSet.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return [key, sanitizedBlacklist];
|
||||
}
|
||||
|
||||
if (key === "editors") {
|
||||
const sanitizedEditors = base.filter((npub) => {
|
||||
const normalized = normalizeNpub(npub);
|
||||
return normalized && normalized !== normalizeNpub(ADMIN_SUPER_NPUB);
|
||||
});
|
||||
return [key, sanitizedEditors];
|
||||
}
|
||||
|
||||
if (key === "whitelist") {
|
||||
const mergedWhitelist = sanitizeNpubList([
|
||||
...ADMIN_INITIAL_WHITELIST,
|
||||
...base,
|
||||
]);
|
||||
return [key, mergedWhitelist];
|
||||
}
|
||||
|
||||
return [key, base];
|
||||
});
|
||||
|
||||
if (!entries.length) {
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user