mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(access): canAccess cross-derives hex+npub so whitelisted authors aren't blocked (#32)
A video from a genuinely whitelisted author refused to play with "not from a whitelisted author" (the source URL was confirmed healthy — HTTP 200, video/mp4, range, CORS *). Root cause: accessControl keeps the whitelist in two sets — whitelistPubkeys (hex) and whitelist (npub) — and canAccess consulted only one based on the caller's input format without cross-deriving. playbackCoordinator passes the author's npub, so it checked only the npub set; an entry present in only the hex representation read as "not allowed." - canAccess now derives BOTH hex and npub from the candidate and checks both sets (only fixes false-negatives; a non-whitelisted author is still denied). - Dev-gated diagnostic at the playback rejection logs hex/npub, whitelistMode, set sizes, and inNpubSet/inHexSet to pinpoint any remaining denial. - Tests: tests/access-control-canaccess.test.mjs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
f5bf3d115c
commit
8ab531b241
4 files changed
+129
-1
No files matched your search
+33
-1
@@ -749,7 +749,39 @@ class AccessControl {
|
||||
}
|
||||
}
|
||||
|
||||
const normalized = normalizeNpub(npub);
|
||||
let normalized = normalizeNpub(npub);
|
||||
|
||||
// Cross-derive the missing representation. `whitelistPubkeys`/`blacklistPubkeys`
|
||||
// are hex-keyed while `whitelist`/`blacklist` are npub-keyed; both are built
|
||||
// from the same admin lists, but a format mismatch between the caller's input
|
||||
// and how a given entry was stored must NOT read as "not allowed". Filling in
|
||||
// both forms lets the checks below consult both sets.
|
||||
const accessTools =
|
||||
(typeof window !== "undefined" ? window?.NostrTools : null) ||
|
||||
(typeof globalThis !== "undefined" ? globalThis?.NostrTools : null) ||
|
||||
null;
|
||||
if (normalized && !hex && typeof accessTools?.nip19?.decode === "function") {
|
||||
try {
|
||||
const decoded = accessTools.nip19.decode(normalized);
|
||||
if (decoded?.type === "npub") {
|
||||
hex = normalizeHexKey(
|
||||
typeof decoded.data === "string"
|
||||
? decoded.data
|
||||
: bytesToHex(decoded.data)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore — fall back to the npub-only checks
|
||||
}
|
||||
}
|
||||
if (hex && !normalized && typeof accessTools?.nip19?.npubEncode === "function") {
|
||||
try {
|
||||
normalized = normalizeNpub(accessTools.nip19.npubEncode(hex));
|
||||
} catch (error) {
|
||||
// ignore — fall back to the hex-only checks
|
||||
}
|
||||
}
|
||||
|
||||
if (!normalized) {
|
||||
if (!hex) {
|
||||
return false;
|
||||
|
||||
@@ -677,6 +677,18 @@ export function createPlaybackCoordinator(deps) {
|
||||
}
|
||||
const authorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
|
||||
if (!accessControl.canAccess(authorNpub)) {
|
||||
// Dev diagnostic: pinpoint WHY access was denied (format mismatch vs the
|
||||
// author genuinely not being on the list vs lists not loaded).
|
||||
devLogger.warn("[playback] access denied — whitelist diagnostic", {
|
||||
pubkey: video.pubkey,
|
||||
authorNpub,
|
||||
foreign: video.foreign === true || video.source === "nip71-ingest",
|
||||
whitelistMode: accessControl.whitelistMode?.(),
|
||||
whitelistNpubSize: accessControl.whitelist?.size,
|
||||
whitelistHexSize: accessControl.whitelistPubkeys?.size,
|
||||
inNpubSet: accessControl.whitelist?.has?.(authorNpub),
|
||||
inHexSet: accessControl.whitelistPubkeys?.has?.(video.pubkey),
|
||||
});
|
||||
if (accessControl.isBlacklisted(authorNpub)) {
|
||||
this.showError("This content has been removed or is not allowed.");
|
||||
} else if (accessControl.whitelistMode()) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import * as nostrTools from "nostr-tools";
|
||||
|
||||
import { accessControl } from "../js/accessControl.js";
|
||||
|
||||
// Bug: a genuinely whitelisted author was blocked at playback because canAccess
|
||||
// checks the hex set when given hex and the npub set when given an npub, without
|
||||
// cross-deriving. If an entry lands in only one representation (e.g. hex decode
|
||||
// hadn't run when the list loaded), querying in the other format read as "not
|
||||
// allowed". The fix cross-derives both forms and checks both sets.
|
||||
|
||||
// canAccess resolves NostrTools from window/globalThis for the cross-derive.
|
||||
const priorTools = globalThis.NostrTools;
|
||||
globalThis.NostrTools = { nip19: nostrTools.nip19 };
|
||||
|
||||
const sk = nostrTools.generateSecretKey();
|
||||
const HEX = nostrTools.getPublicKey(sk);
|
||||
const NPUB = nostrTools.nip19.npubEncode(HEX);
|
||||
|
||||
const otherSk = nostrTools.generateSecretKey();
|
||||
const OTHER_HEX = nostrTools.getPublicKey(otherSk);
|
||||
const OTHER_NPUB = nostrTools.nip19.npubEncode(OTHER_HEX);
|
||||
|
||||
function seed({ whitelist = [], whitelistHex = [] } = {}) {
|
||||
accessControl.whitelistEnabled = true;
|
||||
accessControl.whitelist = new Set(whitelist);
|
||||
accessControl.whitelistPubkeys = new Set(whitelistHex);
|
||||
accessControl.blacklist = new Set();
|
||||
accessControl.blacklistPubkeys = new Set();
|
||||
accessControl.editors = new Set(); // ensure the author isn't treated as admin
|
||||
}
|
||||
|
||||
test.after(() => {
|
||||
globalThis.NostrTools = priorTools;
|
||||
});
|
||||
|
||||
test("whitelisted by HEX only is recognized when queried by npub (cross-derive)", () => {
|
||||
seed({ whitelistHex: [HEX] }); // npub set deliberately empty
|
||||
assert.equal(accessControl.canAccess(NPUB), true, "npub query finds the hex-set author");
|
||||
assert.equal(accessControl.canAccess(HEX), true, "hex query still works");
|
||||
});
|
||||
|
||||
test("whitelisted by NPUB only is recognized when queried by hex (cross-derive)", () => {
|
||||
seed({ whitelist: [NPUB] }); // hex set deliberately empty
|
||||
assert.equal(accessControl.canAccess(HEX), true, "hex query finds the npub-set author");
|
||||
assert.equal(accessControl.canAccess(NPUB), true, "npub query still works");
|
||||
});
|
||||
|
||||
test("a non-whitelisted author is still denied in both formats (no over-allow)", () => {
|
||||
seed({ whitelist: [NPUB], whitelistHex: [HEX] });
|
||||
assert.equal(accessControl.canAccess(OTHER_NPUB), false);
|
||||
assert.equal(accessControl.canAccess(OTHER_HEX), false);
|
||||
});
|
||||
|
||||
test("with whitelist mode OFF, any author is allowed regardless of sets", () => {
|
||||
seed({});
|
||||
accessControl.whitelistEnabled = false;
|
||||
assert.equal(accessControl.canAccess(OTHER_NPUB), true);
|
||||
assert.equal(accessControl.canAccess(OTHER_HEX), true);
|
||||
});
|
||||
@@ -599,6 +599,29 @@ SILENT one: `handleEventDetailsAction` only opened the modal `if (… && payload
|
||||
- [ ] **VERIFY live**: click ⋯ → Event Details AND ⋯ → Popularity in the video
|
||||
modal AND on a grid/channel card; both should open the correct video.
|
||||
|
||||
### 32. Whitelisted author's video won't play — "not from a whitelisted author" (BUG)
|
||||
Reported 2026-06-25: a video from a known-whitelisted author refused to play
|
||||
("This content is not from a whitelisted author." at `playbackCoordinator
|
||||
.playVideoByEventId`). The source URL was confirmed 100% healthy (HTTP 200,
|
||||
video/mp4, range + CORS `*`) — the block is the whitelist gate, not the source.
|
||||
- [x] **Root cause + fix: `canAccess` format asymmetry.** `accessControl` keeps the
|
||||
whitelist in TWO sets — `whitelistPubkeys` (hex) and `whitelist` (npub) — and
|
||||
`canAccess` checked only ONE based on the caller's input format (npub→npub set,
|
||||
hex→hex set) without cross-deriving. `playbackCoordinator` passes the author's
|
||||
**npub**, so it consulted only the npub set; if the entry was present in only
|
||||
the hex set (e.g. the hex decode hadn't run / a normalization mismatch), a
|
||||
genuinely whitelisted author read as "not allowed." Fixed `canAccess` to
|
||||
cross-derive both forms and check BOTH sets (false-negatives only; a
|
||||
non-whitelisted author is still denied). Tests:
|
||||
`tests/access-control-canaccess.test.mjs`.
|
||||
- [x] Added a dev-gated diagnostic at the playback rejection
|
||||
(`playbackCoordinator`) logging hex/npub, whitelistMode, set sizes, and
|
||||
`inNpubSet`/`inHexSet` — so if it still denies, we know instantly whether the
|
||||
author is genuinely off the list (→ then it's the separate "ingested foreign
|
||||
content shown in a whitelist-mode feed but unplayable" question) vs a load race.
|
||||
- [ ] **VERIFY live**: replay the failing nevent. If it plays → fixed. If it still
|
||||
denies, paste the new `[playback] access denied — whitelist diagnostic` line.
|
||||
|
||||
### 28. Beacon torrent app stuck — spinner never resolves (BUG) — FIXED 2026-06-24
|
||||
- [x] **Root cause:** the processing overlay (`torrent/app.js`) was gated on
|
||||
WebTorrent's metadata-ready callback (`client.add(magnet, opts, cb)` /
|
||||
|
||||
Reference in new issue
Block a user