mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Request NIP-07 permissions needed for watch history decrypt
This commit is contained in:
1 parent
0b0d3628ee
commit
4e42db2667
2 files changed
+232
-8
No files matched your search
+55
-8
@@ -5265,15 +5265,62 @@ export class NostrClient {
|
||||
if (isDevMode) {
|
||||
console.log("Requesting permissions from NIP-07 extension...");
|
||||
}
|
||||
try {
|
||||
await runNip07WithRetry(() => extension.enable(), {
|
||||
label: "extension.enable",
|
||||
});
|
||||
} catch (enableErr) {
|
||||
const requestedPermissionMethods = [
|
||||
"get_public_key",
|
||||
"sign_event",
|
||||
"nip04.encrypt",
|
||||
"nip04.decrypt",
|
||||
"read_relays",
|
||||
"write_relays",
|
||||
];
|
||||
|
||||
const permissionVariants = [];
|
||||
const objectPermissions = requestedPermissionMethods
|
||||
.map((method) =>
|
||||
typeof method === "string" && method.trim()
|
||||
? { method: method.trim() }
|
||||
: null,
|
||||
)
|
||||
.filter(Boolean);
|
||||
if (objectPermissions.length) {
|
||||
permissionVariants.push({ permissions: objectPermissions });
|
||||
}
|
||||
const stringPermissions = Array.from(
|
||||
new Set(objectPermissions.map((entry) => entry.method)),
|
||||
);
|
||||
if (stringPermissions.length) {
|
||||
permissionVariants.push({ permissions: stringPermissions });
|
||||
}
|
||||
permissionVariants.push(null);
|
||||
|
||||
let enableError = null;
|
||||
for (const options of permissionVariants) {
|
||||
try {
|
||||
await runNip07WithRetry(
|
||||
() =>
|
||||
options
|
||||
? extension.enable(options)
|
||||
: extension.enable(),
|
||||
{ label: "extension.enable" },
|
||||
);
|
||||
enableError = null;
|
||||
break;
|
||||
} catch (error) {
|
||||
enableError = error;
|
||||
if (options && isDevMode) {
|
||||
console.warn(
|
||||
"[nostr] extension.enable request with explicit permissions failed:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enableError) {
|
||||
throw new Error(
|
||||
enableErr && enableErr.message
|
||||
? enableErr.message
|
||||
: "The NIP-07 extension denied the permission request."
|
||||
enableError && enableError.message
|
||||
? enableError.message
|
||||
: "The NIP-07 extension denied the permission request.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { nostrClient } from "../js/nostr.js";
|
||||
import { accessControl } from "../js/accessControl.js";
|
||||
|
||||
const HEX_PUBKEY = "f".repeat(64);
|
||||
|
||||
function setupLoginEnvironment({ enableImpl, getPublicKey = HEX_PUBKEY } = {}) {
|
||||
const previousWindow = typeof global.window === "undefined" ? undefined : global.window;
|
||||
const previousGlobalNostr = global.nostr;
|
||||
const previousGlobalNostrTools = global.NostrTools;
|
||||
const previousCanonicalTools = global.__BITVID_CANONICAL_NOSTR_TOOLS__;
|
||||
const previousNostrToolsReady = global.nostrToolsReady;
|
||||
|
||||
const windowRef = previousWindow ?? global;
|
||||
global.window = windowRef;
|
||||
|
||||
const previousWindowNostr = previousWindow?.nostr;
|
||||
const previousWindowNostrTools = previousWindow?.NostrTools;
|
||||
|
||||
const enableCalls = [];
|
||||
const nostrStub = {
|
||||
enable: (options) => {
|
||||
enableCalls.push(options);
|
||||
if (typeof enableImpl === "function") {
|
||||
return Promise.resolve(enableImpl(options, enableCalls.length));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
getPublicKey: () => Promise.resolve(getPublicKey),
|
||||
};
|
||||
|
||||
windowRef.nostr = nostrStub;
|
||||
global.nostr = nostrStub;
|
||||
|
||||
const toolkitStub = {
|
||||
nip19: {
|
||||
npubEncode: (hex) => `npub${hex}`,
|
||||
},
|
||||
};
|
||||
|
||||
windowRef.NostrTools = toolkitStub;
|
||||
global.NostrTools = toolkitStub;
|
||||
global.__BITVID_CANONICAL_NOSTR_TOOLS__ = toolkitStub;
|
||||
global.nostrToolsReady = Promise.resolve({ ok: true, value: toolkitStub });
|
||||
|
||||
const originalAccessControl = {
|
||||
canAccess: accessControl.canAccess,
|
||||
getWhitelist: accessControl.getWhitelist,
|
||||
getBlacklist: accessControl.getBlacklist,
|
||||
isBlacklisted: accessControl.isBlacklisted,
|
||||
};
|
||||
|
||||
accessControl.canAccess = () => true;
|
||||
accessControl.getWhitelist = () => [];
|
||||
accessControl.getBlacklist = () => [];
|
||||
accessControl.isBlacklisted = () => false;
|
||||
|
||||
return {
|
||||
enableCalls,
|
||||
restore() {
|
||||
accessControl.canAccess = originalAccessControl.canAccess;
|
||||
accessControl.getWhitelist = originalAccessControl.getWhitelist;
|
||||
accessControl.getBlacklist = originalAccessControl.getBlacklist;
|
||||
accessControl.isBlacklisted = originalAccessControl.isBlacklisted;
|
||||
|
||||
if (previousWindow === undefined) {
|
||||
delete global.window;
|
||||
} else {
|
||||
global.window = previousWindow;
|
||||
if (previousWindow) {
|
||||
previousWindow.nostr = previousWindowNostr;
|
||||
previousWindow.NostrTools = previousWindowNostrTools;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousGlobalNostr === undefined) {
|
||||
delete global.nostr;
|
||||
} else {
|
||||
global.nostr = previousGlobalNostr;
|
||||
}
|
||||
|
||||
if (previousGlobalNostrTools === undefined) {
|
||||
delete global.NostrTools;
|
||||
} else {
|
||||
global.NostrTools = previousGlobalNostrTools;
|
||||
}
|
||||
|
||||
global.__BITVID_CANONICAL_NOSTR_TOOLS__ = previousCanonicalTools;
|
||||
|
||||
if (previousNostrToolsReady === undefined) {
|
||||
delete global.nostrToolsReady;
|
||||
} else {
|
||||
global.nostrToolsReady = previousNostrToolsReady;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("NIP-07 login requests decrypt permissions upfront", async () => {
|
||||
const env = setupLoginEnvironment();
|
||||
try {
|
||||
const pubkey = await nostrClient.login();
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
assert.ok(env.enableCalls.length >= 1, "extension.enable should be invoked");
|
||||
const firstCall = env.enableCalls[0];
|
||||
assert.equal(typeof firstCall, "object");
|
||||
assert.ok(Array.isArray(firstCall.permissions), "permissions array should be provided");
|
||||
const methods = firstCall.permissions.map((entry) =>
|
||||
typeof entry === "string" ? entry : entry?.method,
|
||||
);
|
||||
assert.ok(methods.includes("nip04.decrypt"), "nip04.decrypt permission should be requested");
|
||||
assert.ok(methods.includes("nip04.encrypt"), "nip04.encrypt permission should be requested");
|
||||
assert.ok(methods.includes("sign_event"), "sign_event permission should be requested");
|
||||
} finally {
|
||||
env.restore();
|
||||
nostrClient.logout();
|
||||
}
|
||||
});
|
||||
|
||||
test("NIP-07 login falls back when structured permissions fail", async () => {
|
||||
const env = setupLoginEnvironment({
|
||||
enableImpl(options) {
|
||||
if (
|
||||
options &&
|
||||
Array.isArray(options.permissions) &&
|
||||
options.permissions.length &&
|
||||
typeof options.permissions[0] === "object"
|
||||
) {
|
||||
return Promise.reject(new Error("object permissions unsupported"));
|
||||
}
|
||||
if (options && Array.isArray(options.permissions)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("unexpected enable invocation"));
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const pubkey = await nostrClient.login();
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
assert.equal(env.enableCalls.length >= 2, true, "should retry with alternate payload");
|
||||
const [firstCall, secondCall] = env.enableCalls;
|
||||
assert.ok(Array.isArray(firstCall?.permissions));
|
||||
assert.equal(
|
||||
typeof firstCall.permissions[0],
|
||||
"object",
|
||||
"first attempt should use structured permission objects",
|
||||
);
|
||||
assert.ok(Array.isArray(secondCall?.permissions));
|
||||
assert.equal(
|
||||
typeof secondCall.permissions[0],
|
||||
"string",
|
||||
"second attempt should fall back to string permissions",
|
||||
);
|
||||
} finally {
|
||||
env.restore();
|
||||
nostrClient.logout();
|
||||
}
|
||||
});
|
||||
|
||||
test("NIP-07 login surfaces enable permission errors", async () => {
|
||||
const env = setupLoginEnvironment({
|
||||
enableImpl() {
|
||||
return Promise.reject(new Error("permission denied"));
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await assert.rejects(() => nostrClient.login(), /permission denied/);
|
||||
} finally {
|
||||
env.restore();
|
||||
nostrClient.logout();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user