mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
scheduleStoredRemoteSignerRestore reconnected a stored remote-signer session on
startup via useStoredRemoteSigner({ silent: true }) with NO validator, so a
pubkey that had since been blocked could silently reconnect — the same
access-control bypass class as the nsec fix. It now passes an access-control
validator (waits for the admin lists to load, then checks canAccess); since
useStoredRemoteSigner already clears the stored session when the validator
rejects, a blocked pubkey's session is forgotten on restore. The connect and
explicit "use stored" paths already enforced this; only the silent startup
restore was missing it.
Tests: the restore supplies a validator function; the validator allows
permitted pubkeys and rejects blocked ones. Mutation-verified (dropping the
validator fails the test).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
84 lines
2.6 KiB
JavaScript
84 lines
2.6 KiB
JavaScript
// The silent NIP-46 session restore on startup must enforce access control,
|
|
// so a since-blocked pubkey's stored remote-signer session can't reconnect
|
|
// unchecked.
|
|
|
|
import "./test-helpers/setup-localstorage.mjs";
|
|
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { SignerManager } from "../js/nostr/managers/SignerManager.js";
|
|
import { accessControl } from "../js/accessControl.js";
|
|
|
|
const NIP46_SESSION_KEY = "bitvid:nip46:session:v1";
|
|
|
|
function seedStoredSession() {
|
|
localStorage.setItem(
|
|
NIP46_SESSION_KEY,
|
|
JSON.stringify({
|
|
version: 1,
|
|
remotePubkey: "a".repeat(64),
|
|
clientPrivateKey: "b".repeat(64),
|
|
relays: ["wss://relay.example"],
|
|
}),
|
|
);
|
|
}
|
|
|
|
test("scheduleStoredRemoteSignerRestore passes an access-control validator", async () => {
|
|
seedStoredSession();
|
|
const manager = new SignerManager({ relays: ["wss://relay.example"] });
|
|
|
|
let capturedOptions = null;
|
|
manager.useStoredRemoteSigner = async (options) => {
|
|
capturedOptions = options;
|
|
return { ok: true, pubkey: "a".repeat(64) };
|
|
};
|
|
|
|
await manager.scheduleStoredRemoteSignerRestore();
|
|
// schedule fires the restore without awaiting it; let the microtask run.
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.ok(capturedOptions, "the stored signer restore was attempted");
|
|
assert.equal(
|
|
capturedOptions.silent,
|
|
true,
|
|
"startup restore stays silent",
|
|
);
|
|
assert.equal(
|
|
typeof capturedOptions.validator,
|
|
"function",
|
|
"a validator must be supplied so access control is enforced on restore",
|
|
);
|
|
|
|
localStorage.removeItem(NIP46_SESSION_KEY);
|
|
});
|
|
|
|
test("the restore validator allows permitted pubkeys and rejects blocked ones", async () => {
|
|
const manager = new SignerManager({ relays: [] });
|
|
const validator = manager.buildAccessControlValidator();
|
|
|
|
const origWaitForReady = accessControl.waitForReady;
|
|
const origCanAccess = accessControl.canAccess;
|
|
const origIsBlacklisted = accessControl.isBlacklisted;
|
|
accessControl.waitForReady = async () => {};
|
|
|
|
try {
|
|
accessControl.canAccess = () => true;
|
|
accessControl.isBlacklisted = () => false;
|
|
await assert.doesNotReject(
|
|
() => validator("a".repeat(64)),
|
|
"an allowed pubkey passes the validator",
|
|
);
|
|
|
|
accessControl.canAccess = () => false;
|
|
accessControl.isBlacklisted = () => true;
|
|
await assert.rejects(
|
|
() => validator("c".repeat(64)),
|
|
/blocked/i,
|
|
"a blocked pubkey is rejected so the stored session is forgotten",
|
|
);
|
|
} finally {
|
|
accessControl.waitForReady = origWaitForReady;
|
|
accessControl.canAccess = origCanAccess;
|
|
accessControl.isBlacklisted = origIsBlacklisted;
|
|
}
|
|
});
|