Skip to content

Commit

Permalink
Improve URL validation and fail on startup
Browse files Browse the repository at this point in the history
  • Loading branch information
nflaig committed Aug 14, 2023
1 parent 7aa238e commit 9aa412b
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 5 deletions.
11 changes: 9 additions & 2 deletions packages/api/src/utils/client/httpClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ErrorAborted, Logger, TimeoutError} from "@lodestar/utils";
import {ErrorAborted, Logger, TimeoutError, isValidHttpUrl, toBase64} from "@lodestar/utils";
import {ReqGeneric, RouteDef} from "../index.js";
import {ApiClientResponse, ApiClientSuccessResponse} from "../../interfaces.js";
import {fetch, isFetchError} from "./fetch.js";
Expand Down Expand Up @@ -123,8 +123,15 @@ export class HttpClient implements IHttpClient {

// opts.baseUrl is equivalent to `urls: [{baseUrl}]`
// unshift opts.baseUrl to urls, without mutating opts.urls
for (const urlOrOpts of [...(baseUrl ? [baseUrl] : []), ...urls]) {
for (const [i, urlOrOpts] of [...(baseUrl ? [baseUrl] : []), ...urls].entries()) {
const urlOpts: URLOpts = typeof urlOrOpts === "string" ? {baseUrl: urlOrOpts, ...allUrlOpts} : urlOrOpts;

if (!urlOpts.baseUrl) {
throw Error(`HttpClient.urls[${i}] is empty or undefined: ${urlOpts.baseUrl}`);
}
if (!isValidHttpUrl(urlOpts.baseUrl)) {
throw Error(`HttpClient.urls[${i}] must be a valid URL: ${urlOpts.baseUrl}`);
}
// De-duplicate by baseUrl, having two baseUrls with different token or timeouts does not make sense
if (!this.urlsOpts.some((opt) => opt.baseUrl === urlOpts.baseUrl)) {
this.urlsOpts.push(urlOpts);
Expand Down
16 changes: 16 additions & 0 deletions packages/api/test/unit/client/httpClientOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,20 @@ describe("HTTPClient options", () => {
{baseUrl: baseUrl2, bearerToken: bearerToken2},
]);
});

it("Throw if empty baseUrl", () => {
expect(() => new HttpClient({baseUrl: ""})).to.throw(Error);
});

it("Throw if invalid baseUrl", () => {
expect(() => new HttpClient({baseUrl: "invalid"})).to.throw(Error);
});

it("Throw if empty value in urls option", () => {
expect(() => new HttpClient({urls: [""]})).to.throw(Error);
});

it("Throw if invalid value in urls option", () => {
expect(() => new HttpClient({urls: ["invalid"]})).to.throw(Error);
});
});
7 changes: 4 additions & 3 deletions packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {fetch} from "@lodestar/api";
import {ErrorAborted, TimeoutError, retry} from "@lodestar/utils";
import {ErrorAborted, TimeoutError, isValidHttpUrl, retry} from "@lodestar/utils";
import {IGauge, IHistogram} from "../../metrics/interface.js";
import {IJson, RpcPayload} from "../interface.js";
import {encodeJwtToken} from "./jwt.js";
Expand Down Expand Up @@ -92,6 +92,9 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient {
if (!url) {
throw Error(`JsonRpcHttpClient.urls[${i}] is empty or undefined: ${url}`);
}
if (!isValidHttpUrl(url)) {
throw Error(`JsonRpcHttpClient.urls[${i}] must be a valid URL: ${url}`);
}
}

this.jwtSecret = opts?.jwtSecret;
Expand Down Expand Up @@ -188,8 +191,6 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient {
* Fetches JSON and throws detailed errors in case the HTTP request is not ok
*/
private async fetchJsonOneUrl<R, T = unknown>(url: string, json: T, opts?: ReqOpts): Promise<R> {
if (!url) throw Error(`Empty or undefined JSON RPC HTTP client url: ${url}`);

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), opts?.timeout ?? this.opts?.timeout ?? REQUEST_TIMEOUT);

Expand Down

0 comments on commit 9aa412b

Please sign in to comment.