mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-06-04 18:29:34 +00:00
ipWhitelist was only applied to HTTP routes, so Socket.IO module namespaces could still be reached from disallowed clients. This adds the same whitelist check to Socket.IO handshakes (allowRequest), and reuses the same client IP resolution for both HTTP and Socket.IO (forwarded IP is only trusted for loopback peers). Also adds tests for handshake allow/deny and forwarded-header behavior. Fixes: GHSA-w26r-fwg8-rcp3
80 lines
2.3 KiB
JavaScript
80 lines
2.3 KiB
JavaScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { ipAccessControl, socketIpAccessControl } from "../../../js/ip_access_control";
|
|
|
|
/**
|
|
* Creates a minimal Express-like response mock used by the middleware tests.
|
|
* @returns {{ header: ReturnType<typeof vi.fn>, status: ReturnType<typeof vi.fn>, send: ReturnType<typeof vi.fn> }} Mock response object.
|
|
*/
|
|
function createResponseMock () {
|
|
return {
|
|
header: vi.fn(),
|
|
status: vi.fn(function () {
|
|
return this;
|
|
}),
|
|
send: vi.fn()
|
|
};
|
|
}
|
|
|
|
describe("ip_access_control", () => {
|
|
describe("ipAccessControl", () => {
|
|
it("trusts first X-Forwarded-For entry when direct peer is loopback", () => {
|
|
const middleware = ipAccessControl(["203.0.113.10"]);
|
|
const req = {
|
|
socket: { remoteAddress: "127.0.0.1" },
|
|
headers: { "x-forwarded-for": "203.0.113.10, 10.0.0.2" }
|
|
};
|
|
const res = createResponseMock();
|
|
const next = vi.fn();
|
|
|
|
middleware(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalledOnce();
|
|
expect(res.status).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("ignores X-Forwarded-For when direct peer is not loopback", () => {
|
|
const middleware = ipAccessControl(["203.0.113.10"]);
|
|
const req = {
|
|
socket: { remoteAddress: "198.51.100.7" },
|
|
headers: { "x-forwarded-for": "203.0.113.10" }
|
|
};
|
|
const res = createResponseMock();
|
|
const next = vi.fn();
|
|
|
|
middleware(req, res, next);
|
|
|
|
expect(next).not.toHaveBeenCalled();
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
});
|
|
});
|
|
|
|
describe("socketIpAccessControl", () => {
|
|
it("accepts socket handshake using forwarded client IP when direct peer is loopback", () => {
|
|
const allowRequest = socketIpAccessControl(["203.0.113.10"]);
|
|
const req = {
|
|
socket: { remoteAddress: "::1" },
|
|
headers: { "x-forwarded-for": "203.0.113.10, 10.0.0.2" }
|
|
};
|
|
const callback = vi.fn();
|
|
|
|
allowRequest(req, callback);
|
|
|
|
expect(callback).toHaveBeenCalledWith(null, true);
|
|
});
|
|
|
|
it("rejects socket handshake when only forwarded IP matches whitelist", () => {
|
|
const allowRequest = socketIpAccessControl(["203.0.113.10"]);
|
|
const req = {
|
|
socket: { remoteAddress: "198.51.100.7" },
|
|
headers: { "x-forwarded-for": "203.0.113.10" }
|
|
};
|
|
const callback = vi.fn();
|
|
|
|
allowRequest(req, callback);
|
|
|
|
expect(callback).toHaveBeenCalledWith("This device is not allowed to access your mirror.", false);
|
|
});
|
|
});
|
|
});
|