diff --git a/js/defaults.js b/js/defaults.js index bd590894cb..81b5264e61 100644 --- a/js/defaults.js +++ b/js/defaults.js @@ -11,6 +11,8 @@ const defaults = { basePath: "/", electronOptions: {}, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], + cors: "disabled", // or "allowAll" or "allowWhitelist" + corsDomainWhitelist: [], // example: ["api.mapbox.com"] language: "en", logLevel: ["INFO", "LOG", "WARN", "ERROR"], diff --git a/js/server_functions.js b/js/server_functions.js index 1d0f5e1ff5..b1b0b19c83 100644 --- a/js/server_functions.js +++ b/js/server_functions.js @@ -25,6 +25,7 @@ async function isPrivateTarget (url) { const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); if (hostname.toLowerCase() === "localhost") return true; + if (global.config.cors === "allowWhitelist" && !global.config.corsDomainWhitelist.includes(hostname.toLowerCase())) return true; try { const results = await dns.lookup(hostname, { all: true }); @@ -68,6 +69,10 @@ function replaceSecretPlaceholder (input) { * @returns {Promise} A promise that resolves when the response is sent */ async function cors (req, res) { + if (global.config.cors === "disabled") { + Log.error("CORS is disabled, you need to enable it in `config.js` by setting `cors` to `allowAll` or `allowWhitelist`"); + return res.status(403).json({ error: "CORS proxy is disabled" }); + } try { const urlRegEx = "url=(.+?)$"; let url; diff --git a/tests/unit/functions/server_functions_spec.js b/tests/unit/functions/server_functions_spec.js index 2b855e42a0..979a6a633d 100644 --- a/tests/unit/functions/server_functions_spec.js +++ b/tests/unit/functions/server_functions_spec.js @@ -35,6 +35,7 @@ describe("server_functions tests", () => { let fetchMock; beforeEach(() => { + global.config = { cors: "allowAll" }; fetchResponseHeadersGet = vi.fn(() => {}); fetchResponseArrayBuffer = vi.fn(() => {}); fetchResponse = { @@ -263,4 +264,22 @@ describe("server_functions tests", () => { expect(response.json).toHaveBeenCalledWith({ error: "Forbidden: private or reserved addresses are not allowed" }); }); }); + + describe("The isPrivateTarget method with allowWhitelist", () => { + beforeEach(() => { + mockLookup.mockReset(); + }); + + it("Block public unicast IPs if not whitelistet", async () => { + global.config = { cors: "allowWhitelist", corsDomainWhitelist: [] }; + mockLookup.mockResolvedValue([{ address: "93.184.216.34", family: 4 }]); + expect(await isPrivateTarget("http://example.com/api")).toBe(true); + }); + + it("Allow public unicast IPs if whitelistet", async () => { + global.config = { cors: "allowWhitelist", corsDomainWhitelist: ["example.com"] }; + mockLookup.mockResolvedValue([{ address: "93.184.216.34", family: 4 }]); + expect(await isPrivateTarget("http://example.com/api")).toBe(false); + }); + }); });