Skip to content

Commit f334292

Browse files
Merge pull request #221 from AikidoSec/AIK-2984
Match wildcard endpoints
2 parents 0de8787 + aab4a4b commit f334292

File tree

6 files changed

+332
-130
lines changed

6 files changed

+332
-130
lines changed

library/agent/ServiceConfig.test.ts

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import { ServiceConfig } from "./ServiceConfig";
33

44
t.test("it returns false if empty rules", async () => {
55
const config = new ServiceConfig([], 0, [], []);
6-
t.same(config.shouldProtectEndpoint("GET", "/foo"), true);
76
t.same(config.getLastUpdatedAt(), 0);
87
t.same(config.isUserBlocked("id"), false);
98
t.same(config.isAllowedIP("1.2.3.4"), false);
9+
t.same(
10+
config.getEndpoint({ url: undefined, method: undefined, route: undefined }),
11+
undefined
12+
);
1013
});
1114

1215
t.test("it works", async () => {
@@ -48,51 +51,28 @@ t.test("it works", async () => {
4851
[]
4952
);
5053

51-
t.same(config.shouldProtectEndpoint("GET", "/foo"), true);
52-
t.same(config.shouldProtectEndpoint("POST", "/foo"), false);
53-
t.same(config.shouldProtectEndpoint("GET", "/unknown"), true);
54-
t.same(config.shouldProtectEndpoint("POST", /fly+/), false);
5554
t.same(config.isUserBlocked("123"), true);
5655
t.same(config.isUserBlocked("567"), false);
57-
});
58-
59-
t.test("it returns rate limiting", async () => {
60-
const config = new ServiceConfig(
61-
[
62-
{
56+
t.same(
57+
config.getEndpoint({
58+
url: undefined,
59+
method: "GET",
60+
route: "/foo",
61+
}),
62+
{
63+
endpoint: {
6364
method: "GET",
6465
route: "/foo",
6566
forceProtectionOff: false,
66-
rateLimiting: { enabled: true, maxRequests: 10, windowSizeInMS: 1000 },
67-
},
68-
{
69-
method: "POST",
70-
route: "/foo",
71-
forceProtectionOff: true,
7267
rateLimiting: {
7368
enabled: false,
7469
maxRequests: 0,
7570
windowSizeInMS: 0,
7671
},
7772
},
78-
],
79-
0,
80-
[],
81-
[]
73+
route: "/foo",
74+
}
8275
);
83-
84-
t.same(config.getRateLimiting("GET", "/foo"), {
85-
enabled: true,
86-
maxRequests: 10,
87-
windowSizeInMS: 1000,
88-
});
89-
90-
t.same(config.getRateLimiting("GET", "/unknown"), undefined);
91-
t.same(config.getRateLimiting("POST", "/foo"), {
92-
enabled: false,
93-
maxRequests: 0,
94-
windowSizeInMS: 0,
95-
});
9676
});
9777

9878
t.test("it checks if IP is allowed", async () => {

library/agent/ServiceConfig.ts

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
1+
import { LimitedContext, matchEndpoint } from "../helpers/matchEndpoint";
12
import { Endpoint } from "./Config";
23

34
export class ServiceConfig {
4-
private endpoints: Map<string, Endpoint> = new Map();
55
private blockedUserIds: Map<string, string> = new Map();
66
private allowedIPAddresses: Map<string, string> = new Map();
77

88
constructor(
9-
endpoints: Endpoint[],
9+
private readonly endpoints: Endpoint[],
1010
private readonly lastUpdatedAt: number,
1111
blockedUserIds: string[],
1212
allowedIPAddresses: string[]
1313
) {
14-
endpoints.forEach((rule) => {
15-
this.endpoints.set(this.getKey(rule.method, rule.route), {
16-
method: rule.method,
17-
route: rule.route,
18-
forceProtectionOff: rule.forceProtectionOff,
19-
rateLimiting: rule.rateLimiting,
20-
});
21-
});
22-
2314
blockedUserIds.forEach((userId) => {
2415
this.blockedUserIds.set(userId, userId);
2516
});
@@ -29,44 +20,14 @@ export class ServiceConfig {
2920
});
3021
}
3122

32-
private getKey(method: string, route: string) {
33-
return `${method}:${route}`;
34-
}
35-
36-
getRateLimiting(method: string, route: string | RegExp) {
37-
const key = this.getKey(
38-
method,
39-
typeof route === "string" ? route : route.source
40-
);
41-
42-
const rule = this.endpoints.get(key);
43-
44-
if (!rule || !rule.rateLimiting) {
45-
return undefined;
46-
}
47-
48-
return rule.rateLimiting;
23+
getEndpoint(context: LimitedContext) {
24+
return matchEndpoint(context, this.endpoints);
4925
}
5026

5127
isAllowedIP(ip: string) {
5228
return this.allowedIPAddresses.has(ip);
5329
}
5430

55-
shouldProtectEndpoint(method: string, route: string | RegExp) {
56-
const key = this.getKey(
57-
method,
58-
typeof route === "string" ? route : route.source
59-
);
60-
61-
const rule = this.endpoints.get(key);
62-
63-
if (!rule) {
64-
return true;
65-
}
66-
67-
return !rule.forceProtectionOff;
68-
}
69-
7031
isUserBlocked(userId: string) {
7132
return this.blockedUserIds.has(userId);
7233
}

library/agent/applyHooks.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -160,20 +160,17 @@ function wrapWithoutArgumentModification(
160160
const args = Array.from(arguments);
161161
const context = getContext();
162162

163-
if (
164-
context &&
165-
context.method &&
166-
context.route &&
167-
!agent
168-
.getConfig()
169-
.shouldProtectEndpoint(context.method, context.route)
170-
) {
171-
return original.apply(
172-
// @ts-expect-error We don't now the type of this
173-
this,
174-
// eslint-disable-next-line prefer-rest-params
175-
arguments
176-
);
163+
if (context) {
164+
const match = agent.getConfig().getEndpoint(context);
165+
166+
if (match && match.endpoint.forceProtectionOff) {
167+
return original.apply(
168+
// @ts-expect-error We don't now the type of this
169+
this,
170+
// eslint-disable-next-line prefer-rest-params
171+
arguments
172+
);
173+
}
177174
}
178175

179176
const start = performance.now();

0 commit comments

Comments
 (0)