From 5bcfed1b248c060a10bc0461e9cd0b81fe5bf779 Mon Sep 17 00:00:00 2001 From: Anthony Potdevin <31413433+apotdevin@users.noreply.github.com> Date: Fri, 25 Dec 2020 21:42:19 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=94=A7=20enable=20tor=20proxy=20?= =?UTF-8?q?config=20(#197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 + README.md | 11 ++++ next.config.js | 1 + package-lock.json | 43 +++++++++++++- package.json | 3 +- server/api/Boltz.ts | 24 +++++--- server/api/LnMarkets.ts | 45 ++++++++------- server/helpers/lnAuth.ts | 5 +- server/schema/bitcoin/bitcoin.test.ts | 8 ++- server/schema/bitcoin/resolvers.ts | 5 +- server/schema/github/resolvers.ts | 5 +- server/schema/lnurl/resolvers.ts | 9 +-- server/schema/tbase/resolvers.ts | 82 ++++++++++++++++----------- server/utils/fetch.ts | 41 ++++++++++++++ 14 files changed, 208 insertions(+), 75 deletions(-) create mode 100644 server/utils/fetch.ts diff --git a/.env b/.env index 3710bd29..800ae8e0 100644 --- a/.env +++ b/.env @@ -10,6 +10,7 @@ # Server Configs # ----------- # LOG_LEVEL='info' +# TOR_PROXY_SERVER=socks://127.0.0.1:9050 # ----------- # Interface Configs diff --git a/README.md b/README.md index a9e36d90..a7bd5e1d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ You can define some environment variables that ThunderHub can start with. To do # Server Configs # ----------- LOG_LEVEL = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly' # Default: 'info' +TOR_PROXY_SERVER='socks://127.0.0.1:9050' # Default: '' # ----------- # Interface Configs @@ -131,6 +132,16 @@ DISABLE_LNMARKETS = true | false # Default: false NO_VERSION_CHECK = true | false # Default: false ``` +### TOR Requests + +ThunderHub connects to different external services for example to fetch BOS scores, BTC/fiat prices and BTC blockchain fees. Normally they are done through clearnet but you can configure a TOR proxy server so that they are all proxied through TOR. + +You need to add the following parameter into your `.env` file with your TOR endpoint: + +```bash +TOR_PROXY_SERVER='socks://your.tor.endpoint' # i.e. 'socks://127.0.0.1:9050' +``` + ### SSO Account You can define an account to work with SSO cookie authentication by adding the following parameters in the `.env` file: diff --git a/next.config.js b/next.config.js index e1865bef..aee92377 100644 --- a/next.config.js +++ b/next.config.js @@ -35,6 +35,7 @@ module.exports = withBundleAnalyzer({ serverRuntimeConfig: { nodeEnv: process.env.NODE_ENV || 'development', logLevel: process.env.LOG_LEVEL || 'info', + torProxy: process.env.TOR_PROXY_SERVER || '', ...ssoEnv, ...accountConfig, ...sslEnv, diff --git a/package-lock.json b/package-lock.json index 433a8189..b326066c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16439,7 +16439,8 @@ "extract-files": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", - "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==" + "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", + "dev": true }, "extsprintf": { "version": "1.3.0", @@ -17552,6 +17553,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-3.4.0.tgz", "integrity": "sha512-acrTzidSlwAj8wBNO7Q/UQHS8T+z5qRGquCQRv9J1InwR01BBWV9ObnoE+JS5nCCEj8wSGS0yrDXVDoRiKZuOg==", + "dev": true, "requires": { "cross-fetch": "^3.0.6", "extract-files": "^9.0.0", @@ -18303,6 +18305,11 @@ } } }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", @@ -26674,6 +26681,11 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, + "smart-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -26776,6 +26788,35 @@ } } }, + "socks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.5.1.tgz", + "integrity": "sha512-oZCsJJxapULAYJaEYBSzMcz8m3jqgGrHaGhkmU/o/PQfFWYWxkAaA0UMGImb6s6tEXfKi959X6VJjMMQ3P6TTQ==", + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", + "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "requires": { + "agent-base": "6", + "debug": "4", + "socks": "^2.3.3" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", diff --git a/package.json b/package.json index b661adf9..3322ba11 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "graphql": "^15.4.0", "graphql-iso-date": "^3.6.1", "graphql-rate-limit": "^2.0.1", - "graphql-request": "^3.4.0", "intersection-observer": "^0.12.0", "js-cookie": "^2.2.1", "js-yaml": "^3.14.1", @@ -70,6 +69,7 @@ "lodash.merge": "^4.6.2", "lodash.omit": "^4.5.0", "next": "^10.0.3", + "node-fetch": "^2.6.1", "numeral": "^2.0.6", "qrcode.react": "^1.0.0", "react": "^17.0.1", @@ -87,6 +87,7 @@ "react-toastify": "^6.2.0", "react-tooltip": "^4.2.11", "secp256k1": "^4.0.2", + "socks-proxy-agent": "^5.0.0", "styled-components": "^5.2.1", "styled-react-modal": "^2.0.1", "styled-theming": "^2.2.0", diff --git a/server/api/Boltz.ts b/server/api/Boltz.ts index 9cb3d5d0..7cc86861 100644 --- a/server/api/Boltz.ts +++ b/server/api/Boltz.ts @@ -1,10 +1,11 @@ import { logger } from 'server/helpers/logger'; import { appUrls } from 'server/utils/appUrls'; +import { fetchWithProxy } from 'server/utils/fetch'; export const BoltzApi = { getPairs: async () => { try { - const response = await fetch(`${appUrls.boltz}/getpairs`); + const response = await fetchWithProxy(`${appUrls.boltz}/getpairs`); return await response.json(); } catch (error) { logger.error('Error getting pairs from Boltz: %o', error); @@ -13,7 +14,9 @@ export const BoltzApi = { }, getFeeEstimations: async () => { try { - const response = await fetch(`${appUrls.boltz}/getfeeestimation`); + const response = await fetchWithProxy( + `${appUrls.boltz}/getfeeestimation` + ); return await response.json(); } catch (error) { logger.error('Error getting fee estimations from Boltz: %o', error); @@ -23,7 +26,7 @@ export const BoltzApi = { getSwapStatus: async (id: string) => { try { const body = { id }; - const response = await fetch(`${appUrls.boltz}/swapstatus`, { + const response = await fetchWithProxy(`${appUrls.boltz}/swapstatus`, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, @@ -48,7 +51,7 @@ export const BoltzApi = { preimageHash, claimPublicKey, }; - const response = await fetch(`${appUrls.boltz}/createswap`, { + const response = await fetchWithProxy(`${appUrls.boltz}/createswap`, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, @@ -65,11 +68,14 @@ export const BoltzApi = { currency: 'BTC', transactionHex, }; - const response = await fetch(`${appUrls.boltz}/broadcasttransaction`, { - method: 'POST', - body: JSON.stringify(body), - headers: { 'Content-Type': 'application/json' }, - }); + const response = await fetchWithProxy( + `${appUrls.boltz}/broadcasttransaction`, + { + method: 'POST', + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json' }, + } + ); return await response.json(); } catch (error) { logger.error('Error broadcasting transaction from Boltz: %o', error); diff --git a/server/api/LnMarkets.ts b/server/api/LnMarkets.ts index 025b432e..af3a76f8 100644 --- a/server/api/LnMarkets.ts +++ b/server/api/LnMarkets.ts @@ -1,10 +1,11 @@ import { logger } from 'server/helpers/logger'; import { appUrls } from 'server/utils/appUrls'; +import { fetchWithProxy } from 'server/utils/fetch'; export const LnMarketsApi = { getUser: async (token: string) => { try { - const response = await fetch(`${appUrls.lnMarkets}/user`, { + const response = await fetchWithProxy(`${appUrls.lnMarkets}/user`, { headers: { Accept: 'application/json', Authorization: `Bearer ${token}`, @@ -21,15 +22,18 @@ export const LnMarketsApi = { }, getDepositInvoice: async (token: string, amount: number) => { try { - const response = await fetch(`${appUrls.lnMarkets}/user/deposit`, { - method: 'post', - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}`, - 'content-type': 'application/json', - }, - body: JSON.stringify({ amount, unit: 'sat' }), - }); + const response = await fetchWithProxy( + `${appUrls.lnMarkets}/user/deposit`, + { + method: 'post', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ amount, unit: 'sat' }), + } + ); return await response.json(); } catch (error) { logger.error( @@ -41,15 +45,18 @@ export const LnMarketsApi = { }, withdraw: async (token: string, amount: number, invoice: string) => { try { - const response = await fetch(`${appUrls.lnMarkets}/user/withdraw`, { - method: 'post', - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}`, - 'content-type': 'application/json', - }, - body: JSON.stringify({ amount, unit: 'sat', invoice }), - }); + const response = await fetchWithProxy( + `${appUrls.lnMarkets}/user/withdraw`, + { + method: 'post', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ amount, unit: 'sat', invoice }), + } + ); return await response.json(); } catch (error) { logger.error(`Error withdrawing from LnMarkets. Error: %o`, error); diff --git a/server/helpers/lnAuth.ts b/server/helpers/lnAuth.ts index 7ecc9904..2566a8dd 100644 --- a/server/helpers/lnAuth.ts +++ b/server/helpers/lnAuth.ts @@ -11,6 +11,7 @@ import * as bip32 from 'bip32'; import * as secp256k1 from 'secp256k1'; import { appUrls } from 'server/utils/appUrls'; import { decodeLnUrl } from 'src/utils/url'; +import { fetchWithProxy } from 'server/utils/fetch'; import { to } from './async'; import { logger } from './logger'; @@ -108,7 +109,7 @@ export const getLnMarketsAuth = async ( // Get a new lnUrl from LnMarkets try { - const response = await fetch(`${appUrls.lnMarkets}/lnurl/a/c`); + const response = await fetchWithProxy(`${appUrls.lnMarkets}/lnurl/a/c`); const json = await response.json(); logger.debug('Get lnUrl from LnMarkets response: %o', json); @@ -128,7 +129,7 @@ export const getLnMarketsAuth = async ( // Try to authenticate with lnMarkets try { - const response = await fetch(`${finalUrl}&jwt=true&expiry=3600`); + const response = await fetchWithProxy(`${finalUrl}&jwt=true&expiry=3600`); const json = await response.json(); logger.debug('LnUrlAuth response: %o', json); diff --git a/server/schema/bitcoin/bitcoin.test.ts b/server/schema/bitcoin/bitcoin.test.ts index ff1eaed2..d65a2a34 100644 --- a/server/schema/bitcoin/bitcoin.test.ts +++ b/server/schema/bitcoin/bitcoin.test.ts @@ -19,7 +19,10 @@ describe('Bitcoin Resolvers', () => { expect(res.errors).toBe(undefined); - expect(fetchMock).toBeCalledWith('https://blockchain.info/ticker'); + expect(fetchMock).toBeCalledWith( + 'https://blockchain.info/ticker', + undefined + ); expect(res).toMatchSnapshot(); }); test('failure', async () => { @@ -54,7 +57,8 @@ describe('Bitcoin Resolvers', () => { expect(res.errors).toBe(undefined); expect(fetchMock).toBeCalledWith( - 'https://mempool.space/api/v1/fees/recommended' + 'https://mempool.space/api/v1/fees/recommended', + undefined ); expect(res).toMatchSnapshot(); }); diff --git a/server/schema/bitcoin/resolvers.ts b/server/schema/bitcoin/resolvers.ts index 64b977b6..68c6e95b 100644 --- a/server/schema/bitcoin/resolvers.ts +++ b/server/schema/bitcoin/resolvers.ts @@ -2,6 +2,7 @@ import { ContextType } from 'server/types/apiTypes'; import { logger } from 'server/helpers/logger'; import { requestLimiter } from 'server/helpers/rateLimiter'; import { appUrls } from 'server/utils/appUrls'; +import { fetchWithProxy } from 'server/utils/fetch'; export const bitcoinResolvers = { Query: { @@ -13,7 +14,7 @@ export const bitcoinResolvers = { await requestLimiter(context.ip, 'bitcoinPrice'); try { - const response = await fetch(appUrls.ticker); + const response = await fetchWithProxy(appUrls.ticker); const json = await response.json(); return JSON.stringify(json); @@ -30,7 +31,7 @@ export const bitcoinResolvers = { await requestLimiter(context.ip, 'bitcoinFee'); try { - const response = await fetch(appUrls.fees); + const response = await fetchWithProxy(appUrls.fees); const json = await response.json(); if (json) { diff --git a/server/schema/github/resolvers.ts b/server/schema/github/resolvers.ts index b2bd5634..0d2bf673 100644 --- a/server/schema/github/resolvers.ts +++ b/server/schema/github/resolvers.ts @@ -3,6 +3,7 @@ import { ContextType } from 'server/types/apiTypes'; import { toWithError } from 'server/helpers/async'; import { appUrls } from 'server/utils/appUrls'; import { logger } from 'server/helpers/logger'; +import { fetchWithProxy } from 'server/utils/fetch'; export const githubResolvers = { Query: { @@ -13,7 +14,9 @@ export const githubResolvers = { ) => { await requestLimiter(context.ip, 'getLnPay'); - const [response, error] = await toWithError(fetch(appUrls.github)); + const [response, error] = await toWithError( + fetchWithProxy(appUrls.github) + ); if (error || !response) { logger.debug('Unable to get latest github version'); diff --git a/server/schema/lnurl/resolvers.ts b/server/schema/lnurl/resolvers.ts index b89aec1f..ab03b4b7 100644 --- a/server/schema/lnurl/resolvers.ts +++ b/server/schema/lnurl/resolvers.ts @@ -10,6 +10,7 @@ import { PayInvoiceType, } from 'server/types/ln-service.types'; import { lnAuthUrlGenerator } from 'server/helpers/lnAuth'; +import { fetchWithProxy } from 'server/utils/fetch'; type LnUrlPayResponseType = { pr?: string; @@ -74,7 +75,7 @@ export const lnUrlResolvers = { const finalUrl = await lnAuthUrlGenerator(url, lnd); try { - const response = await fetch(finalUrl); + const response = await fetchWithProxy(finalUrl); const json = await response.json(); logger.debug('LnUrlAuth response: %o', json); @@ -97,7 +98,7 @@ export const lnUrlResolvers = { await requestLimiter(context.ip, 'fetchLnUrl'); try { - const response = await fetch(url); + const response = await fetchWithProxy(url); const json = await response.json(); if (json.status === 'ERROR') { @@ -136,7 +137,7 @@ export const lnUrlResolvers = { }; try { - const response = await fetch(finalUrl); + const response = await fetchWithProxy(finalUrl); lnServiceResponse = await response.json(); if (lnServiceResponse.status === 'ERROR') { @@ -215,7 +216,7 @@ export const lnUrlResolvers = { const finalUrl = `${callback}?k1=${k1}&pr=${info.request}`; try { - const response = await fetch(finalUrl); + const response = await fetchWithProxy(finalUrl); const json = await response.json(); logger.debug('LnUrlWithdraw response: %o', json); diff --git a/server/schema/tbase/resolvers.ts b/server/schema/tbase/resolvers.ts index 1fa29596..51612a4b 100644 --- a/server/schema/tbase/resolvers.ts +++ b/server/schema/tbase/resolvers.ts @@ -1,20 +1,19 @@ import { ContextType } from 'server/types/apiTypes'; import { requestLimiter } from 'server/helpers/rateLimiter'; -import { toWithError } from 'server/helpers/async'; import { appUrls } from 'server/utils/appUrls'; -import { request, gql } from 'graphql-request'; import { logger } from 'server/helpers/logger'; import { GraphQLError } from 'graphql'; import { appConstants } from 'server/utils/appConstants'; import cookieLib from 'cookie'; +import { graphqlFetchWithProxy } from 'server/utils/fetch'; -const getBaseCanConnectQuery = gql` +const getBaseCanConnectQuery = ` { hello } `; -const getBaseInfoQuery = gql` +const getBaseInfoQuery = ` { getInfo { lastBosUpdate @@ -24,7 +23,7 @@ const getBaseInfoQuery = gql` } `; -const getBaseNodesQuery = gql` +const getBaseNodesQuery = ` { getNodes { _id @@ -35,7 +34,7 @@ const getBaseNodesQuery = gql` } `; -const getBasePointsQuery = gql` +const getBasePointsQuery = ` { getPoints { alias @@ -44,7 +43,7 @@ const getBasePointsQuery = gql` } `; -const createBaseInvoiceQuery = gql` +const createBaseInvoiceQuery = ` mutation CreateInvoice($amount: Int!) { createInvoice(amount: $amount) { request @@ -53,7 +52,7 @@ const createBaseInvoiceQuery = gql` } `; -const createBaseTokenInvoiceQuery = gql` +const createBaseTokenInvoiceQuery = ` mutation CreateTokenInvoice($days: Int) { createTokenInvoice(days: $days) { request @@ -62,7 +61,7 @@ const createBaseTokenInvoiceQuery = gql` } `; -const createThunderPointsQuery = gql` +const createThunderPointsQuery = ` mutation CreatePoints( $id: String! $alias: String! @@ -73,13 +72,13 @@ const createThunderPointsQuery = gql` } `; -const createBaseTokenQuery = gql` +const createBaseTokenQuery = ` mutation CreateBaseToken($id: String!) { createBaseToken(id: $id) } `; -const getBosScoresQuery = gql` +const getBosScoresQuery = ` { getBosScores { updated @@ -94,7 +93,7 @@ const getBosScoresQuery = gql` } `; -const getBosNodeScoresQuery = gql` +const getBosNodeScoresQuery = ` query GetNodeScores($publicKey: String!, $token: String!) { getNodeScores(publicKey: $publicKey, token: $token) { alias @@ -111,8 +110,9 @@ export const tbaseResolvers = { getBaseInfo: async (_: undefined, __: undefined, context: ContextType) => { await requestLimiter(context.ip, 'getBaseInfo'); - const [data, error] = await toWithError( - request(appUrls.tbase, getBaseInfoQuery) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + getBaseInfoQuery ); if (error || !data?.getInfo) { @@ -129,8 +129,9 @@ export const tbaseResolvers = { ): Promise => { await requestLimiter(context.ip, 'getBaseCanConnect'); - const [data, error] = await toWithError( - request(appUrls.tbase, getBaseCanConnectQuery) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + getBaseCanConnectQuery ); if (error || !data?.hello) return false; @@ -149,11 +150,13 @@ export const tbaseResolvers = { await requestLimiter(ip, 'getBosNodeScores'); - const [data, error] = await toWithError( - request(appUrls.tbase, getBosNodeScoresQuery, { + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + getBosNodeScoresQuery, + { publicKey, token: tokenAuth, - }) + } ); if (error) { @@ -166,12 +169,14 @@ export const tbaseResolvers = { getBosScores: async (_: undefined, __: any, context: ContextType) => { await requestLimiter(context.ip, 'getBosScores'); - const [data, error] = await toWithError( - request(appUrls.tbase, getBosScoresQuery) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + getBosScoresQuery ); if (error || !data?.getBosScores) { logger.error('Error getting BOS scores'); + logger.error(error); throw new GraphQLError('ErrorGettingBosScores'); } @@ -180,8 +185,9 @@ export const tbaseResolvers = { getBaseNodes: async (_: undefined, __: any, context: ContextType) => { await requestLimiter(context.ip, 'getBaseNodes'); - const [data, error] = await toWithError( - request(appUrls.tbase, getBaseNodesQuery) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + getBaseNodesQuery ); if (error || !data?.getNodes) return []; @@ -193,8 +199,9 @@ export const tbaseResolvers = { getBasePoints: async (_: undefined, __: any, context: ContextType) => { await requestLimiter(context.ip, 'getBasePoints'); - const [data, error] = await toWithError( - request(appUrls.tbase, getBasePointsQuery) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + getBasePointsQuery ); if (error || !data?.getPoints) return []; @@ -212,8 +219,10 @@ export const tbaseResolvers = { if (!params?.amount) return ''; - const [data, error] = await toWithError( - request(appUrls.tbase, createBaseInvoiceQuery, params) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + createBaseInvoiceQuery, + params ); if (error) return null; @@ -228,8 +237,10 @@ export const tbaseResolvers = { ) => { await requestLimiter(ip, 'createBaseInvoice'); - const [data, error] = await toWithError( - request(appUrls.tbase, createBaseTokenQuery, { id }) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + createBaseTokenQuery, + { id } ); if (error || !data?.createBaseToken) { @@ -278,8 +289,9 @@ export const tbaseResolvers = { ) => { await requestLimiter(context.ip, 'createBaseTokenInvoice'); - const [data, error] = await toWithError( - request(appUrls.tbase, createBaseTokenInvoiceQuery) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + createBaseTokenInvoiceQuery ); if (error || !data?.createTokenInvoice) { @@ -295,11 +307,13 @@ export const tbaseResolvers = { ): Promise => { await requestLimiter(context.ip, 'createThunderPoints'); - const [info, error] = await toWithError( - request(appUrls.tbase, createThunderPointsQuery, params) + const { data, error } = await graphqlFetchWithProxy( + appUrls.tbase, + createThunderPointsQuery, + params ); - if (error || !info?.createPoints) return false; + if (error || !data?.createPoints) return false; return true; }, diff --git a/server/utils/fetch.ts b/server/utils/fetch.ts new file mode 100644 index 00000000..b702c232 --- /dev/null +++ b/server/utils/fetch.ts @@ -0,0 +1,41 @@ +import { Agent } from 'https'; +import getConfig from 'next/config'; +import { SocksProxyAgent } from 'socks-proxy-agent'; +import fetch from 'node-fetch'; +import { GraphQLError } from 'graphql'; +import { logger } from 'server/helpers/logger'; + +const { serverRuntimeConfig } = getConfig() || { serverRuntimeConfig: {} }; +const { torProxy } = serverRuntimeConfig; + +let agent: Agent | null = null; + +if (torProxy) { + logger.info(`Using tor proxy for external requests: ${torProxy}`); + agent = new SocksProxyAgent(torProxy) as any; +} + +export const fetchWithProxy = (url: string, options?: {}) => { + return agent ? fetch(url, { agent, ...options }) : fetch(url, options); +}; + +export const graphqlFetchWithProxy = async ( + url: string, + query: string, + variables?: { [key: string]: string | number | string[] } +): Promise<{ + data: any; + error: undefined | GraphQLError; +}> => { + return fetchWithProxy(url, { + method: 'post', + headers: { Accept: 'application/json' }, + body: JSON.stringify({ query, variables }), + }) + .then(res => res.json()) + .then(data => data) + .catch(error => { + logger.error('Error doing graphql fetch: %o', error); + return { data: undefined, error }; + }); +};