// The connection, transaction and signature prompts name the site by its full // origin, scheme and port included, not by its bare hostname // (https://git.eeqj.de/sneak/AutistMask/issues/402). A page served over http, // or on another port, of a host the user trusts over https must not raise a // prompt that reads as that trusted site. // // Driven against a minimal DOM stub in the shape // tests/contractCreation.test.js uses. globalThis.chrome = { storage: { local: { get: async () => ({}), set: async () => {} } }, }; const { state } = require("../src/shared/state"); const approval = require("../src/popup/views/approval"); // The site asking, in cleartext and on a port, which is what the hostname // alone, dapp.example, used to hide. const ORIGIN = "http://dapp.example:8080"; const FROM = "0x0000000000000000000000000000000000000a11"; const RECIPIENT = "0x66133E8ea0f5D1d612D2502a968757D1048c214a"; function makeElement(id) { const classes = new Set(); const el = { id, textContent: "", value: "", innerHTML: "", disabled: false, style: {}, dataset: {}, classList: { add: (...names) => names.forEach((n) => classes.add(n)), remove: (...names) => names.forEach((n) => classes.delete(n)), contains: (n) => classes.has(n), toggle: (n, force) => { const on = force === undefined ? !classes.has(n) : force; if (on) classes.add(n); else classes.delete(n); return on; }, }, addEventListener: () => {}, querySelectorAll: () => [], appendChild: () => {}, }; // Views reach for .parentElement to hide whole sections. Object.defineProperty(el, "parentElement", { get: () => node(id + "-parent"), }); return el; } function makeDocument() { const els = new Map(); return { getElementById(id) { // The debug banner is created on demand by helpers.js; absent // is the state a non-debug, non-testnet popup is in. if (id === "debug-banner") return null; if (!els.has(id)) els.set(id, makeElement(id)); return els.get(id); }, createElement: () => makeElement("created"), body: { prepend: () => {} }, }; } function node(id) { return globalThis.document.getElementById(id); } // Open the prompt the background describes with `details`, the way the popup // does: it asks for the approval and show() draws it. async function openApproval(details) { globalThis.document = makeDocument(); globalThis.window = { location: { search: "" } }; globalThis.chrome.runtime = { connect: () => ({ postMessage: () => {} }), sendMessage: (msg, reply) => { if (!reply) return; if (msg.type !== "AUTISTMASK_GET_APPROVAL") return reply(null); reply({ origin: ORIGIN, isPhishingDomain: false, approvedFrom: FROM, ...details, }); }, }; approval.init({}); await approval.show(1); } beforeEach(() => { state.wallets = []; state.activeAddress = FROM; state.viewData = {}; state.viewStack = []; state.currentView = null; }); test("the connection prompt shows the origin", async () => { await openApproval({}); expect(node("approve-origin").textContent).toBe(ORIGIN); }); test("the transaction prompt shows the origin", async () => { await openApproval({ type: "tx", approvedTx: { type: 2, from: FROM, chainId: "0x1", nonce: "0x7", gasLimit: "0x5208", maxPriorityFeePerGas: "0x3b9aca00", maxFeePerGas: "0x77359400", to: RECIPIENT, value: "0x0", data: "0x", accessList: [], }, }); expect(node("approve-tx-origin").textContent).toBe(ORIGIN); }); test("the signature prompt shows the origin", async () => { await openApproval({ type: "sign", // "Hello" as the hex a dApp passes to personal_sign. signParams: { method: "personal_sign", message: "0x48656c6c6f", from: FROM, }, }); expect(node("approve-sign-origin").textContent).toBe(ORIGIN); });