From d99ae35c2ee660d2de1f43025ce613df9c79d974 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 19 Oct 2024 00:18:30 +0200 Subject: [PATCH 1/3] feat: add a proxy option into settings --- package.json | 1 + pnpm-lock.yaml | 11 ++++--- server/index.ts | 23 +++++++++++++++ server/lib/settings/index.ts | 2 ++ server/utils/restartFlag.ts | 3 +- .../Settings/SettingsMain/index.tsx | 29 +++++++++++++++++++ 6 files changed, 64 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9ce9330f8..e38813988 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "express-session": "1.17.3", "formik": "^2.4.6", "gravatar-url": "3.1.0", + "https-proxy-agent": "^7.0.5", "lodash": "4.17.21", "mime": "3", "next": "^14.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7391a775a..2d93710d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: gravatar-url: specifier: 3.1.0 version: 3.1.0 + https-proxy-agent: + specifier: ^7.0.5 + version: 7.0.5 lodash: specifier: 4.17.21 version: 4.17.21 @@ -5389,8 +5392,8 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.4: - resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} human-signals@1.1.1: @@ -12310,7 +12313,7 @@ snapshots: fs-extra: 11.2.0 globby: 11.1.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.4 + https-proxy-agent: 7.0.5 issue-parser: 6.0.0 lodash: 4.17.21 mime: 3.0.0 @@ -15739,7 +15742,7 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.4: + https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 debug: 4.3.5(supports-color@8.1.1) diff --git a/server/index.ts b/server/index.ts index 965903618..0b3af77dc 100644 --- a/server/index.ts +++ b/server/index.ts @@ -22,6 +22,7 @@ import routes from '@server/routes'; import avatarproxy from '@server/routes/avatarproxy'; import imageproxy from '@server/routes/imageproxy'; import { getAppVersion } from '@server/utils/appVersion'; +import bindHttpMethod from '@server/utils/bindHttpMethod'; import restartFlag from '@server/utils/restartFlag'; import { getClientIp } from '@supercharge/request-ip'; import { TypeormStore } from 'connect-typeorm/out'; @@ -32,6 +33,9 @@ import express from 'express'; import * as OpenApiValidator from 'express-openapi-validator'; import type { Store } from 'express-session'; import session from 'express-session'; +import http from 'http'; +import https from 'https'; +import { HttpsProxyAgent } from 'https-proxy-agent'; import next from 'next'; import dns from 'node:dns'; import net from 'node:net'; @@ -67,6 +71,25 @@ app const settings = await getSettings().load(); restartFlag.initializeSettings(settings.main); + // Register HTTP proxy + if (settings.main.httpProxy) { + const agent = new HttpsProxyAgent(settings.main.httpProxy); + + (globalThis as any)[Symbol.for('undici.globalDispatcher.1')] = agent; + + http.globalAgent = agent; + https.globalAgent = agent; + + const httpGet = http.get; + const httpRequest = http.request; + const httpsGet = https.get; + const httpsRequest = https.request; + http.get = bindHttpMethod(httpGet, agent); + http.request = bindHttpMethod(httpRequest, agent); + https.get = bindHttpMethod(httpsGet, agent); + https.request = bindHttpMethod(httpsRequest, agent); + } + // Migrate library types if ( settings.plex.libraries.length > 1 && diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 0fc47af9e..360aeb29d 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -119,6 +119,7 @@ export interface MainSettings { mediaServerType: number; partialRequestsEnabled: boolean; locale: string; + httpProxy: string; } interface PublicSettings { @@ -325,6 +326,7 @@ class Settings { mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, locale: 'en', + httpProxy: '', }, plex: { name: '', diff --git a/server/utils/restartFlag.ts b/server/utils/restartFlag.ts index 387ec5ce4..bb5f011d5 100644 --- a/server/utils/restartFlag.ts +++ b/server/utils/restartFlag.ts @@ -13,7 +13,8 @@ class RestartFlag { return ( this.settings.csrfProtection !== settings.csrfProtection || - this.settings.trustProxy !== settings.trustProxy + this.settings.trustProxy !== settings.trustProxy || + this.settings.httpProxy !== settings.httpProxy ); } } diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index f7aac0d96..b4fdea783 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -55,6 +55,8 @@ const messages = defineMessages('components.Settings.SettingsMain', { validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', partialRequestsEnabled: 'Allow Partial Series Requests', locale: 'Display Language', + httpProxy: 'HTTP Proxy', + httpProxyTip: 'Tooltip to write', }); const SettingsMain = () => { @@ -82,6 +84,9 @@ const SettingsMain = () => { intl.formatMessage(messages.validationApplicationUrlTrailingSlash), (value) => !value || !value.endsWith('/') ), + httpProxy: Yup.string().url( + intl.formatMessage(messages.validationApplicationUrl) + ), }); const regenerate = async () => { @@ -137,6 +142,7 @@ const SettingsMain = () => { partialRequestsEnabled: data?.partialRequestsEnabled, trustProxy: data?.trustProxy, cacheImages: data?.cacheImages, + httpProxy: data?.httpProxy, }} enableReinitialize validationSchema={MainSettingsSchema} @@ -158,6 +164,7 @@ const SettingsMain = () => { partialRequestsEnabled: values.partialRequestsEnabled, trustProxy: values.trustProxy, cacheImages: values.cacheImages, + httpProxy: values.httpProxy, }), }); if (!res.ok) throw new Error(); @@ -437,6 +444,28 @@ const SettingsMain = () => { /> +
+ +
+
+ +
+ {errors.httpProxy && + touched.httpProxy && + typeof errors.httpProxy === 'string' && ( +
{errors.httpProxy}
+ )} +
+
From daecb6b6cfaa22f36e3c81068e4f10e06f9140dc Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 19 Oct 2024 00:19:23 +0200 Subject: [PATCH 2/3] feat: add a proxy option into settings --- server/utils/bindHttpMethod.ts | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 server/utils/bindHttpMethod.ts diff --git a/server/utils/bindHttpMethod.ts b/server/utils/bindHttpMethod.ts new file mode 100644 index 000000000..28df1e258 --- /dev/null +++ b/server/utils/bindHttpMethod.ts @@ -0,0 +1,46 @@ +// from https://github.com/gajus/global-agent/blob/master/src/utilities/bindHttpMethod.ts + +import type http from 'http'; +import type https from 'https'; + +type AgentType = http.Agent | https.Agent; + +export default ( + // eslint-disable-next-line @typescript-eslint/ban-types + originalMethod: Function, + agent: AgentType +) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (...args: any[]) => { + let url; + let options; + let callback; + + if (typeof args[0] === 'string' || args[0] instanceof URL) { + url = args[0]; + + if (typeof args[1] === 'function') { + options = {}; + callback = args[1]; + } else { + options = { + ...args[1], + }; + callback = args[2]; + } + } else { + options = { + ...args[0], + }; + callback = args[1]; + } + + options.agent = agent; + + if (url) { + return originalMethod(url, options, callback); + } else { + return originalMethod(options, callback); + } + }; +}; From 4e741051f33a66d3a6cd907f2a1e5e27f9eecb0c Mon Sep 17 00:00:00 2001 From: Gauthier Date: Tue, 22 Oct 2024 23:18:51 +0200 Subject: [PATCH 3/3] fix: use undici proxy agent --- package.json | 4 +-- pnpm-lock.yaml | 60 +++++++++++++--------------------- server/index.ts | 21 ++---------- server/utils/bindHttpMethod.ts | 46 -------------------------- 4 files changed, 27 insertions(+), 104 deletions(-) delete mode 100644 server/utils/bindHttpMethod.ts diff --git a/package.json b/package.json index e38813988..f6af746db 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "express-session": "1.17.3", "formik": "^2.4.6", "gravatar-url": "3.1.0", - "https-proxy-agent": "^7.0.5", "lodash": "4.17.21", "mime": "3", "next": "^14.2.4", @@ -94,7 +93,8 @@ "sqlite3": "5.1.4", "swagger-ui-express": "4.6.2", "swr": "2.2.5", - "typeorm": "0.3.12", + "typeorm": "0.3.11", + "undici": "^6.20.1", "web-push": "3.5.0", "winston": "3.8.2", "winston-daily-rotate-file": "4.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d93710d6..8b68e8b5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: version: 2.11.0 connect-typeorm: specifier: 1.1.4 - version: 1.1.4(typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))) + version: 1.1.4(typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))) cookie-parser: specifier: 1.4.6 version: 1.4.6 @@ -95,9 +95,6 @@ importers: gravatar-url: specifier: 3.1.0 version: 3.1.0 - https-proxy-agent: - specifier: ^7.0.5 - version: 7.0.5 lodash: specifier: 4.17.21 version: 4.17.21 @@ -195,8 +192,11 @@ importers: specifier: 2.2.5 version: 2.2.5(react@18.3.1) typeorm: - specifier: 0.3.12 - version: 0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)) + specifier: 0.3.11 + version: 0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)) + undici: + specifier: ^6.20.1 + version: 6.20.1 web-push: specifier: 3.5.0 version: 3.5.0 @@ -4267,10 +4267,6 @@ packages: resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} engines: {node: '>=0.11'} - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} @@ -6557,11 +6553,6 @@ packages: engines: {node: '>=10'} hasBin: true - mkdirp@2.1.6: - resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} - engines: {node: '>=10'} - hasBin: true - modify-values@1.0.1: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} @@ -7733,9 +7724,6 @@ packages: reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} - reflect-metadata@0.1.14: - resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} - reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} @@ -8673,8 +8661,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typeorm@0.3.12: - resolution: {integrity: sha512-sYSxBmCf1nJLLTcYtwqZ+lQIRtLPyUoO93rHTOKk9vJCyT4UfRtU7oRsJvfvKP3nnZTD1hzz2SEy2zwPEN6OyA==} + typeorm@0.3.11: + resolution: {integrity: sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==} engines: {node: '>= 12.9.0'} hasBin: true peerDependencies: @@ -8685,7 +8673,7 @@ packages: ioredis: ^5.0.4 mongodb: ^3.6.0 mssql: ^7.3.0 - mysql2: ^2.2.5 || ^3.0.1 + mysql2: ^2.2.5 oracledb: ^5.1.0 pg: ^8.5.1 pg-native: ^3.0.0 @@ -8771,6 +8759,10 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici@6.20.1: + resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} + engines: {node: '>=18.17'} + unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -13827,13 +13819,13 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - connect-typeorm@1.1.4(typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))): + connect-typeorm@1.1.4(typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))): dependencies: '@types/debug': 0.0.31 '@types/express-session': 1.17.6 debug: 4.3.5(supports-color@8.1.1) express-session: 1.18.0 - typeorm: 0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)) + typeorm: 0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)) transitivePeerDependencies: - supports-color @@ -14184,10 +14176,6 @@ snapshots: date-fns@2.29.3: {} - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.24.7 - dateformat@3.0.3: {} dayjs@1.11.11: {} @@ -17152,8 +17140,6 @@ snapshots: mkdirp@1.0.4: {} - mkdirp@2.1.6: {} - modify-values@1.0.1: {} moment@2.30.1: {} @@ -18375,8 +18361,6 @@ snapshots: reflect-metadata@0.1.13: {} - reflect-metadata@0.1.14: {} - reflect.getprototypeof@1.0.6: dependencies: call-bind: 1.0.7 @@ -19434,23 +19418,23 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)): + typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 buffer: 6.0.3 chalk: 4.1.2 cli-highlight: 2.1.11 - date-fns: 2.30.0 + date-fns: 2.29.3 debug: 4.3.5(supports-color@8.1.1) dotenv: 16.4.5 - glob: 8.1.0 + glob: 7.2.3 js-yaml: 4.1.0 - mkdirp: 2.1.6 - reflect-metadata: 0.1.14 + mkdirp: 1.0.4 + reflect-metadata: 0.1.13 sha.js: 2.4.11 tslib: 2.6.3 - uuid: 9.0.1 + uuid: 8.3.2 xml2js: 0.4.23 yargs: 17.7.2 optionalDependencies: @@ -19489,6 +19473,8 @@ snapshots: undici-types@5.26.5: {} + undici@6.20.1: {} + unicode-canonical-property-names-ecmascript@2.0.0: {} unicode-emoji-utils@1.2.0: diff --git a/server/index.ts b/server/index.ts index 0b3af77dc..83c273765 100644 --- a/server/index.ts +++ b/server/index.ts @@ -22,7 +22,6 @@ import routes from '@server/routes'; import avatarproxy from '@server/routes/avatarproxy'; import imageproxy from '@server/routes/imageproxy'; import { getAppVersion } from '@server/utils/appVersion'; -import bindHttpMethod from '@server/utils/bindHttpMethod'; import restartFlag from '@server/utils/restartFlag'; import { getClientIp } from '@supercharge/request-ip'; import { TypeormStore } from 'connect-typeorm/out'; @@ -33,14 +32,12 @@ import express from 'express'; import * as OpenApiValidator from 'express-openapi-validator'; import type { Store } from 'express-session'; import session from 'express-session'; -import http from 'http'; -import https from 'https'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import next from 'next'; import dns from 'node:dns'; import net from 'node:net'; import path from 'path'; import swaggerUi from 'swagger-ui-express'; +import { ProxyAgent, setGlobalDispatcher } from 'undici'; import YAML from 'yamljs'; if (process.env.forceIpv4First === 'true') { @@ -73,21 +70,7 @@ app // Register HTTP proxy if (settings.main.httpProxy) { - const agent = new HttpsProxyAgent(settings.main.httpProxy); - - (globalThis as any)[Symbol.for('undici.globalDispatcher.1')] = agent; - - http.globalAgent = agent; - https.globalAgent = agent; - - const httpGet = http.get; - const httpRequest = http.request; - const httpsGet = https.get; - const httpsRequest = https.request; - http.get = bindHttpMethod(httpGet, agent); - http.request = bindHttpMethod(httpRequest, agent); - https.get = bindHttpMethod(httpsGet, agent); - https.request = bindHttpMethod(httpsRequest, agent); + setGlobalDispatcher(new ProxyAgent(settings.main.httpProxy)); } // Migrate library types diff --git a/server/utils/bindHttpMethod.ts b/server/utils/bindHttpMethod.ts deleted file mode 100644 index 28df1e258..000000000 --- a/server/utils/bindHttpMethod.ts +++ /dev/null @@ -1,46 +0,0 @@ -// from https://github.com/gajus/global-agent/blob/master/src/utilities/bindHttpMethod.ts - -import type http from 'http'; -import type https from 'https'; - -type AgentType = http.Agent | https.Agent; - -export default ( - // eslint-disable-next-line @typescript-eslint/ban-types - originalMethod: Function, - agent: AgentType -) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (...args: any[]) => { - let url; - let options; - let callback; - - if (typeof args[0] === 'string' || args[0] instanceof URL) { - url = args[0]; - - if (typeof args[1] === 'function') { - options = {}; - callback = args[1]; - } else { - options = { - ...args[1], - }; - callback = args[2]; - } - } else { - options = { - ...args[0], - }; - callback = args[1]; - } - - options.agent = agent; - - if (url) { - return originalMethod(url, options, callback); - } else { - return originalMethod(options, callback); - } - }; -};