From 1d1c847b793620acf3a2b193ab45eabf53234cb2 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 8 Mar 2022 19:19:29 +0100 Subject: wallet: throttle all http requests even from browsers / service workers --- .../src/browserHttpLib.ts | 44 ++++++++++++++----- .../src/serviceWorkerHttpLib.ts | 51 ++++++++++++++-------- 2 files changed, 67 insertions(+), 28 deletions(-) (limited to 'packages/taler-wallet-webextension/src') diff --git a/packages/taler-wallet-webextension/src/browserHttpLib.ts b/packages/taler-wallet-webextension/src/browserHttpLib.ts index 63fd456f4..8877edfc3 100644 --- a/packages/taler-wallet-webextension/src/browserHttpLib.ts +++ b/packages/taler-wallet-webextension/src/browserHttpLib.ts @@ -24,7 +24,11 @@ import { HttpResponse, Headers, } from "@gnu-taler/taler-wallet-core"; -import { Logger, TalerErrorCode } from "@gnu-taler/taler-util"; +import { + Logger, + RequestThrottler, + TalerErrorCode, +} from "@gnu-taler/taler-util"; const logger = new Logger("browserHttpLib"); @@ -33,12 +37,32 @@ const logger = new Logger("browserHttpLib"); * browser's XMLHttpRequest. */ export class BrowserHttpLib implements HttpRequestLibrary { - fetch(url: string, options?: HttpRequestOptions): Promise { - const method = options?.method ?? "GET"; + private throttle = new RequestThrottler(); + private throttlingEnabled = true; + + fetch( + requestUrl: string, + options?: HttpRequestOptions, + ): Promise { + const requestMethod = options?.method ?? "GET"; let requestBody = options?.body; + + if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) { + const parsedUrl = new URL(requestUrl); + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, + `request to origin ${parsedUrl.origin} was throttled`, + { + requestMethod, + requestUrl, + throttleStats: this.throttle.getThrottleStats(requestUrl), + }, + ); + } + return new Promise((resolve, reject) => { const myRequest = new XMLHttpRequest(); - myRequest.open(method, url); + myRequest.open(requestMethod, requestUrl); if (options?.headers) { for (const headerName in options.headers) { myRequest.setRequestHeader(headerName, options.headers[headerName]); @@ -58,7 +82,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_NETWORK_ERROR, "Could not make request", { - requestUrl: url, + requestUrl: requestUrl, }, ), ); @@ -71,7 +95,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_NETWORK_ERROR, "HTTP request failed (status 0, maybe URI scheme was wrong?)", { - requestUrl: url, + requestUrl: requestUrl, }, ); reject(exc); @@ -92,7 +116,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, "Invalid JSON from HTTP response", { - requestUrl: url, + requestUrl: requestUrl, httpStatusCode: myRequest.status, }, ); @@ -102,7 +126,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, "Invalid JSON from HTTP response", { - requestUrl: url, + requestUrl: requestUrl, httpStatusCode: myRequest.status, }, ); @@ -126,10 +150,10 @@ export class BrowserHttpLib implements HttpRequestLibrary { headerMap.set(headerName, value); }); const resp: HttpResponse = { - requestUrl: url, + requestUrl: requestUrl, status: myRequest.status, headers: headerMap, - requestMethod: method, + requestMethod: requestMethod, json: makeJson, text: makeText, bytes: async () => myRequest.response, diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts index a66d4e097..6f2585c1e 100644 --- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts +++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts @@ -17,37 +17,55 @@ /** * Imports. */ -import { Logger, TalerErrorCode } from "@gnu-taler/taler-util"; +import { RequestThrottler, TalerErrorCode } from "@gnu-taler/taler-util"; import { - Headers, HttpRequestLibrary, + Headers, + HttpRequestLibrary, HttpRequestOptions, HttpResponse, - OperationFailedError + OperationFailedError, } from "@gnu-taler/taler-wallet-core"; -const logger = new Logger("browserHttpLib"); - /** * An implementation of the [[HttpRequestLibrary]] using the * browser's XMLHttpRequest. */ export class ServiceWorkerHttpLib implements HttpRequestLibrary { - async fetch(requestUrl: string, options?: HttpRequestOptions): Promise { + private throttle = new RequestThrottler(); + private throttlingEnabled = true; + + async fetch( + requestUrl: string, + options?: HttpRequestOptions, + ): Promise { const requestMethod = options?.method ?? "GET"; const requestBody = options?.body; const requestHeader = options?.headers; + if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) { + const parsedUrl = new URL(requestUrl); + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, + `request to origin ${parsedUrl.origin} was throttled`, + { + requestMethod, + requestUrl, + throttleStats: this.throttle.getThrottleStats(requestUrl), + }, + ); + } + const response = await fetch(requestUrl, { headers: requestHeader, body: requestBody, method: requestMethod, // timeout: options?.timeout - }) + }); const headerMap = new Headers(); response.headers.forEach((value, key) => { headerMap.set(key, value); - }) + }); return { headers: headerMap, status: response.status, @@ -56,11 +74,9 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary { json: makeJsonHandler(response, requestUrl), text: makeTextHandler(response, requestUrl), bytes: async () => (await response.blob()).arrayBuffer(), - } - + }; } - get(url: string, opt?: HttpRequestOptions): Promise { return this.fetch(url, { method: "GET", @@ -89,7 +105,7 @@ function makeTextHandler(response: Response, requestUrl: string) { return async function getJsonFromResponse(): Promise { let respText; try { - respText = await response.text() + respText = await response.text(); } catch (e) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, @@ -100,15 +116,15 @@ function makeTextHandler(response: Response, requestUrl: string) { }, ); } - return respText - } + return respText; + }; } function makeJsonHandler(response: Response, requestUrl: string) { return async function getJsonFromResponse(): Promise { let responseJson; try { - responseJson = await response.json() + responseJson = await response.json(); } catch (e) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, @@ -129,7 +145,6 @@ function makeJsonHandler(response: Response, requestUrl: string) { }, ); } - return responseJson - } + return responseJson; + }; } - -- cgit v1.2.3