diff --git a/library/agent/ServiceConfig.ts b/library/agent/ServiceConfig.ts index 8a78172c6..44e4da88a 100644 --- a/library/agent/ServiceConfig.ts +++ b/library/agent/ServiceConfig.ts @@ -1,4 +1,5 @@ import { LimitedContext, matchEndpoint } from "../helpers/matchEndpoint"; +import { matchEndpoints } from "../helpers/matchEndpoints"; import { Endpoint } from "./Config"; export class ServiceConfig { @@ -29,6 +30,10 @@ export class ServiceConfig { ); } + getEndpoints(context: LimitedContext) { + return matchEndpoints(context, this.nonGraphQLEndpoints); + } + getEndpoint(context: LimitedContext) { return matchEndpoint(context, this.nonGraphQLEndpoints); } diff --git a/library/helpers/matchEndpoints.ts b/library/helpers/matchEndpoints.ts new file mode 100644 index 000000000..709080b99 --- /dev/null +++ b/library/helpers/matchEndpoints.ts @@ -0,0 +1,53 @@ +import { Endpoint } from "../agent/Config"; +import { Context } from "../agent/Context"; +import { tryParseURLPath } from "./tryParseURLPath"; + +export type LimitedContext = Pick; + +export function matchEndpoints(context: LimitedContext, endpoints: Endpoint[]) { + const matches: Endpoint[] = []; + + if (!context.method) { + return matches; + } + + const possible = endpoints.filter((endpoint) => { + if (endpoint.method === "*") { + return true; + } + + return endpoint.method === context.method; + }); + + const exact = possible.find((endpoint) => endpoint.route === context.route); + if (exact) { + matches.push(exact); + } + + if (context.url) { + // req.url is relative, so we need to prepend a host to make it absolute + // We just match the pathname, we don't use the host for matching + const path = tryParseURLPath(context.url); + const wildcards = possible + .filter((endpoint) => endpoint.route.includes("*")) + .sort((a, b) => { + // Sort endpoints based on the amount of * in the route + return b.route.split("*").length - a.route.split("*").length; + }); + + if (path) { + for (const wildcard of wildcards) { + const regex = new RegExp( + `^${wildcard.route.replace(/\*/g, "(.*)")}\/?$`, + "i" + ); + + if (regex.test(path)) { + matches.push(wildcard); + } + } + } + } + + return matches; +} diff --git a/library/sources/HTTPServer.test.ts b/library/sources/HTTPServer.test.ts index 22789f83f..beccf74d3 100644 --- a/library/sources/HTTPServer.test.ts +++ b/library/sources/HTTPServer.test.ts @@ -638,11 +638,7 @@ t.test("it checks if IP can access route", async () => { }), ]).then(([response1, response2, response3]) => { t.equal(response1.statusCode, 200); - t.equal(response2.statusCode, 403); - t.same( - response2.body, - `Your IP address is not allowed to access this resource. (Your IP: ${getMajorNodeVersion() === 16 ? "::ffff:127.0.0.1" : "::1"})` - ); + t.equal(response2.statusCode, 200); t.equal(response3.statusCode, 403); t.same( response3.body, diff --git a/library/sources/http-server/ipAllowedToAccessRoute.test.ts b/library/sources/http-server/ipAllowedToAccessRoute.test.ts index 78db72c94..395662643 100644 --- a/library/sources/http-server/ipAllowedToAccessRoute.test.ts +++ b/library/sources/http-server/ipAllowedToAccessRoute.test.ts @@ -162,3 +162,45 @@ t.test("it blocks request if not allowed IP address", async () => { false ); }); + +t.test("it checks every matching endpoint", async () => { + const agent = new Agent( + true, + new LoggerNoop(), + new ReportingAPIForTesting({ + success: true, + allowedIPAddresses: [], + configUpdatedAt: 0, + blockedUserIds: [], + heartbeatIntervalInMS: 10 * 1000, + endpoints: [ + { + route: "/posts/:id", + rateLimiting: undefined, + method: "POST", + allowedIPAddresses: ["3.4.5.6"], + forceProtectionOff: false, + }, + { + route: "/posts/*", + rateLimiting: undefined, + method: "POST", + allowedIPAddresses: ["1.2.3.4"], + forceProtectionOff: false, + }, + ], + block: true, + }), + new Token("123"), + undefined + ); + + agent.start([]); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + t.same( + ipAllowedToAccessRoute({ ...context, remoteAddress: "3.4.5.6" }, agent), + true + ); +}); diff --git a/library/sources/http-server/ipAllowedToAccessRoute.ts b/library/sources/http-server/ipAllowedToAccessRoute.ts index f78a42fdc..84ece3643 100644 --- a/library/sources/http-server/ipAllowedToAccessRoute.ts +++ b/library/sources/http-server/ipAllowedToAccessRoute.ts @@ -7,27 +7,27 @@ export function ipAllowedToAccessRoute(context: Context, agent: Agent) { return true; } - const match = agent.getConfig().getEndpoint(context); + const matches = agent.getConfig().getEndpoints(context); - if (!match) { - return true; - } + for (const endpoint of matches) { + if (!Array.isArray(endpoint.allowedIPAddresses)) { + return true; + } - const { endpoint } = match; + if (endpoint.allowedIPAddresses.length === 0) { + return true; + } - if (!Array.isArray(endpoint.allowedIPAddresses)) { - return true; - } + if (!context.remoteAddress) { + return false; + } - if (endpoint.allowedIPAddresses.length === 0) { - return true; - } + const { allowedIPAddresses } = endpoint; - if (!context.remoteAddress) { - return false; + if (!allowedIPAddresses.includes(context.remoteAddress)) { + return false; + } } - const { allowedIPAddresses } = endpoint; - - return allowedIPAddresses.includes(context.remoteAddress); + return true; }