// What reaches the console when an endpoint check in Settings fails. // // fetch refuses a URL with a user name and password in it, or one it cannot // parse, with an error whose message carries the whole URL: the password, and // any API key in the path or query string. The checks behind the RPC and // Blockscout Save buttons printed that message // (https://git.eeqj.de/sneak/AutistMask/issues/410); they now name the // endpoint by its origin. // // The real fetch runs; it throws before making any request. Debug mode is on, // so every log level is printed. const SECRETS = ["SECRETPASS789", "PATHKEY123", "QUERYTOKEN456"]; const RPC_WITH_PASSWORD = "https://user:SECRETPASS789@rpc.example.invalid/v3/PATHKEY123?token=QUERYTOKEN456"; // Port 99999 is out of range, so the URL does not parse. const RPC_UNPARSEABLE = "https://rpc.example.invalid:99999/v3/PATHKEY123?token=QUERYTOKEN456"; const BLOCKSCOUT_WITH_PASSWORD = "https://user:SECRETPASS789@explorer.example.invalid/PATHKEY123/api/v2"; const SAVED_RPC = "https://saved-rpc.example.invalid"; const SAVED_BLOCKSCOUT = "https://saved-explorer.example.invalid/api/v2"; let elements; let flashes; let printed; let state; // A stand-in for one DOM node: enough of an element for init() to set // properties on it and hang listeners off it. function fakeElement() { return { value: "", checked: false, textContent: "", href: "", style: {}, dataset: {}, classList: { add() {}, remove() {} }, listeners: {}, addEventListener(event, handler) { this.listeners[event] = handler; }, querySelectorAll: () => [], }; } function element(id) { return (elements[id] ||= fakeElement()); } function loadSettingsView() { elements = {}; flashes = []; jest.resetModules(); jest.doMock("../src/popup/views/helpers", () => ({ $: element, showView: () => {}, updateDebugBanner: () => {}, showFlash: (msg) => flashes.push(msg), escapeHtml: (s) => s, flashCopyFeedback: () => {}, goBack: () => {}, pushCurrentView: () => {}, onViewLeave: () => {}, VIEWS: [], })); state = require("../src/shared/state").state; state.rpcUrl = SAVED_RPC; state.blockscoutUrl = SAVED_BLOCKSCOUT; require("../src/shared/log").setRuntimeDebug(true); require("../src/popup/views/settings").init({}); } async function save(fieldId, buttonId, typed) { element(fieldId).value = typed; await element(buttonId).listeners.click(); } // The check failed, nothing was saved, and nothing printed carries the // password or the key. function expectFailedWithoutSecrets(label) { expect(flashes).toContain("Could not reach endpoint."); expect(state.rpcUrl).toBe(SAVED_RPC); expect(state.blockscoutUrl).toBe(SAVED_BLOCKSCOUT); const line = printed.find((text) => text.includes(label)); expect(line).toBeDefined(); const all = printed.join("\n"); for (const secret of SECRETS) { expect(all).not.toContain(secret); } return line; } beforeEach(() => { printed = []; for (const method of ["log", "warn", "error"]) { jest.spyOn(console, method).mockImplementation((...args) => { printed.push(args.map(String).join(" ")); }); } globalThis.chrome = { runtime: { sendMessage: () => {} }, storage: { local: { get: async () => ({}), set: async () => {} } }, }; }); afterEach(() => { jest.dontMock("../src/popup/views/helpers"); delete globalThis.chrome; jest.restoreAllMocks(); }); test("fetch puts the whole URL in the error it throws for such a URL", async () => { for (const url of [RPC_WITH_PASSWORD, RPC_UNPARSEABLE]) { const error = await fetch(url).catch((e) => e); expect(error.message).toContain("PATHKEY123"); } }); test("the RPC check of a URL with a user name and password", async () => { loadSettingsView(); await save("settings-rpc", "btn-save-rpc", RPC_WITH_PASSWORD); const line = expectFailedWithoutSecrets("RPC validation fetch failed"); expect(line).toContain("https://rpc.example.invalid"); }); test("the RPC check of a URL that does not parse", async () => { loadSettingsView(); await save("settings-rpc", "btn-save-rpc", RPC_UNPARSEABLE); expectFailedWithoutSecrets("RPC validation fetch failed"); }); test("the Blockscout check of a URL with a user name and password", async () => { loadSettingsView(); await save( "settings-blockscout", "btn-save-blockscout", BLOCKSCOUT_WITH_PASSWORD, ); const line = expectFailedWithoutSecrets("Blockscout validation failed"); expect(line).toContain("https://explorer.example.invalid"); });