diff --git a/package-lock.json b/package-lock.json index 7d91135c7..f0ca38f54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -541,6 +541,14 @@ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", "dev": true }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, "ajv": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", @@ -1522,8 +1530,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1938,8 +1945,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -1995,7 +2001,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2039,14 +2044,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -2609,7 +2612,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -3056,6 +3058,19 @@ "stackframe": "^1.0.4" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4412,6 +4427,15 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", @@ -5793,8 +5817,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "msgpack5": { "version": "4.2.1", diff --git a/package.json b/package.json index c7e9eec91..8563973a2 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "electron-store": "1.3.0", "electron-updater": "4.0.6", "form-data": "2.3.2", + "https-proxy-agent": "2.2.1", "inquirer": "6.2.0", "jsdom": "13.2.0", "keytar": "4.4.1", diff --git a/src/abstractions/api.service.ts b/src/abstractions/api.service.ts index 6732b8c04..1c471521f 100644 --- a/src/abstractions/api.service.ts +++ b/src/abstractions/api.service.ts @@ -266,4 +266,5 @@ export abstract class ApiService { getActiveBearerToken: () => Promise; fetch: (request: Request) => Promise; + nativeFetch: (request: Request) => Promise; } diff --git a/src/globals.d.ts b/src/globals.d.ts index 8116ab173..1da4c7893 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,3 +1,28 @@ declare function escape(s: string): string; declare function unescape(s: string): string; declare module 'duo_web_sdk'; + +// From: https://github.com/TooTallNate/node-https-proxy-agent/issues/27 +declare module 'https-proxy-agent' { + import * as https from 'https' + + namespace HttpsProxyAgent { + interface HttpsProxyAgentOptions { + host: string + port: number + secureProxy?: boolean + headers?: { + [key: string]: string + } + [key: string]: any + } + } + + // HttpsProxyAgent doesnt *actually* extend https.Agent, but for my purposes I want it to pretend that it does + class HttpsProxyAgent extends https.Agent { + constructor(opts: string) + constructor(opts: HttpsProxyAgent.HttpsProxyAgentOptions) + } + + export = HttpsProxyAgent +} diff --git a/src/services/api.service.ts b/src/services/api.service.ts index d3db7012e..cd4e65f13 100644 --- a/src/services/api.service.ts +++ b/src/services/api.service.ts @@ -897,6 +897,10 @@ export class ApiService implements ApiServiceAbstraction { request.headers.set('Cache-Control', 'no-cache'); request.headers.set('Pragma', 'no-cache'); } + return this.nativeFetch(request); + } + + nativeFetch(request: Request): Promise { return fetch(request); } diff --git a/src/services/audit.service.ts b/src/services/audit.service.ts index 7b821afff..1282ce2bd 100644 --- a/src/services/audit.service.ts +++ b/src/services/audit.service.ts @@ -20,7 +20,7 @@ export class AuditService implements AuditServiceAbstraction { const hashStart = hash.substr(0, 5); const hashEnding = hash.substr(5); - const response = await fetch(PwnedPasswordsApi + hashStart); + const response = await this.apiService.nativeFetch(new Request(PwnedPasswordsApi + hashStart)); const leakedHashes = await response.text(); const match = leakedHashes.split(/\r?\n/).find((v) => { return v.split(':')[0] === hashEnding; diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts index 87bd575d2..c602c409e 100644 --- a/src/services/cipher.service.ts +++ b/src/services/cipher.service.ts @@ -794,7 +794,8 @@ export class CipherService implements CipherServiceAbstraction { private async shareAttachmentWithServer(attachmentView: AttachmentView, cipherId: string, organizationId: string): Promise { - const attachmentResponse = await fetch(new Request(attachmentView.url, { cache: 'no-cache' })); + const attachmentResponse = await this.apiService.nativeFetch( + new Request(attachmentView.url, { cache: 'no-cache' })); if (attachmentResponse.status !== 200) { throw Error('Failed to download attachment: ' + attachmentResponse.status.toString()); } diff --git a/src/services/nodeApi.service.ts b/src/services/nodeApi.service.ts index 3815d46ec..07559c89c 100644 --- a/src/services/nodeApi.service.ts +++ b/src/services/nodeApi.service.ts @@ -1,4 +1,5 @@ import * as FormData from 'form-data'; +import * as HttpsProxyAgent from 'https-proxy-agent'; import * as fe from 'node-fetch'; import { ApiService } from './api.service'; @@ -17,4 +18,12 @@ export class NodeApiService extends ApiService { logoutCallback: (expired: boolean) => Promise) { super(tokenService, platformUtilsService, logoutCallback); } + + nativeFetch(request: Request): Promise { + const proxy = process.env.http_proxy || process.env.https_proxy; + if (proxy) { + (request as any).agent = new HttpsProxyAgent(proxy); + } + return fetch(request); + } }