Skip to content

Commit a1aa98a

Browse files
committed
Extract verifyLocation method
1 parent 483b008 commit a1aa98a

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import HttpURLConnectionClient from "../httpClient/httpURLConnectionClient";
2+
3+
describe("HttpURLConnectionClient", () => {
4+
let client: HttpURLConnectionClient;
5+
6+
beforeEach(() => {
7+
client = new HttpURLConnectionClient();
8+
});
9+
10+
describe("verifyLocation", () => {
11+
test.each([
12+
"https://example.adyen.com/path",
13+
"https://sub.adyen.com",
14+
"http://another.adyen.com/a/b/c?q=1",
15+
"https://checkout-test.adyen.com",
16+
])("should return true for valid adyen.com domain: %s", (location) => {
17+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
18+
// @ts-ignore - testing a private method
19+
expect(client.verifyLocation(location)).toBe(true);
20+
});
21+
22+
test.each([
23+
"https://example.ADYEN.com/path",
24+
"HTTPS://sub.adyen.COM",
25+
])("should be case-insensitive for valid adyen.com domain: %s", (location) => {
26+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
27+
// @ts-ignore - testing a private method
28+
expect(client.verifyLocation(location)).toBe(true);
29+
});
30+
31+
test.each([
32+
"https://adyen.com.evil.com/path",
33+
"https://evil-adyen.com",
34+
"http://adyen.co",
35+
"https://www.google.com",
36+
"https://adyen.com-scam.com",
37+
])("should return false for invalid domain: %s", (location) => {
38+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
39+
// @ts-ignore - testing a private method
40+
expect(client.verifyLocation(location)).toBe(false);
41+
});
42+
43+
test.each([
44+
"https://adyen.com.another.domain/path",
45+
"https://myadyen.com.org",
46+
])("should return false for domains that contain but do not end with adyen.com: %s", (location) => {
47+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
48+
// @ts-ignore - testing a private method
49+
expect(client.verifyLocation(location)).toBe(false);
50+
});
51+
52+
test.each([
53+
"not a url",
54+
"adyen.com",
55+
"//adyen.com/path",
56+
])("should return false for malformed URLs: %s", (location) => {
57+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
58+
// @ts-ignore - testing a private method
59+
expect(client.verifyLocation(location)).toBe(false);
60+
});
61+
});
62+
});

src/httpClient/httpURLConnectionClient.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ class HttpURLConnectionClient implements ClientInterface {
5555
* @throws {ApiException} when an error occurs
5656
*/
5757
public request(
58-
endpoint: string,
59-
json: string,
60-
config: Config,
58+
endpoint: string,
59+
json: string,
60+
config: Config,
6161
isApiRequired: boolean,
6262
requestOptions: IRequest.Options,
6363
): Promise<string> {
@@ -181,9 +181,7 @@ class HttpURLConnectionClient implements ClientInterface {
181181
try {
182182
const url = new URL(location);
183183

184-
// allow-list of trusted domains (*.adyen.com)
185-
const allowedHostnameRegex = /^([a-z0-9-]+\.)*adyen\.com$/;
186-
if (!allowedHostnameRegex.test(url.hostname)) {
184+
if (!this.verifyLocation(location)) {
187185
return reject(new Error(`Redirect to host ${url.hostname} is not allowed.`));
188186
}
189187

@@ -197,7 +195,6 @@ class HttpURLConnectionClient implements ClientInterface {
197195
};
198196
const clientRequestFn = url.protocol === "https:" ? httpsRequest : httpRequest;
199197
const redirectedRequest: ClientRequest = clientRequestFn(newRequestOptions);
200-
201198
const redirectResponse = this.doRequest(redirectedRequest, json);
202199
return resolve(redirectResponse);
203200
} catch (err) {
@@ -206,7 +203,7 @@ class HttpURLConnectionClient implements ClientInterface {
206203
} else {
207204
return reject(new Error(`Redirect status ${res.statusCode} but no Location header`));
208205
}
209-
}
206+
}
210207

211208
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
212209
// API error handling
@@ -278,6 +275,18 @@ class HttpURLConnectionClient implements ClientInterface {
278275
}
279276

280277
}
278+
279+
private verifyLocation(location: string): boolean {
280+
try {
281+
const url = new URL(location);
282+
// allow-list of trusted domains (*.adyen.com)
283+
const allowedHostnameRegex = /\.adyen\.com$/i;
284+
return allowedHostnameRegex.test(url.hostname);
285+
} catch (e) {
286+
return false;
287+
}
288+
}
281289
}
282290

291+
283292
export default HttpURLConnectionClient;

0 commit comments

Comments
 (0)