From b283ed1830cc02e9b728a8dedd1273fcd1e1c321 Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:10:45 +0200 Subject: [PATCH] fix(api): use shared SOCKS5 flag reader in settings routes (#13646) Both settings routes use the shared `isSocks5ProxyEnabled()` reader instead of a copied check (identical logic, no behavior change). Maintainer rework before merge (kept the idea, no default behavior change): - The source-grep tests were replaced by behavioral tests of both routes across the flag on/off matrix (`GET /api/settings/proxies` reports `socks5Enabled`; `PUT /api/settings/proxy` accepts socks5 or returns 400). Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis! --- changelog.d/fixes/13646-socks-flag-reader.md | 1 + src/app/api/settings/proxies/route.ts | 7 +- src/app/api/settings/proxy/route.ts | 17 ++-- tests/unit/settings-socks-flag-reader.test.ts | 98 +++++++++++++++++++ 4 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 changelog.d/fixes/13646-socks-flag-reader.md create mode 100644 tests/unit/settings-socks-flag-reader.test.ts diff --git a/changelog.d/fixes/13646-socks-flag-reader.md b/changelog.d/fixes/13646-socks-flag-reader.md new file mode 100644 index 0000000000..af2032e4f8 --- /dev/null +++ b/changelog.d/fixes/13646-socks-flag-reader.md @@ -0,0 +1 @@ +- **fix(api):** share the single SOCKS5 flag reader across the settings proxy routes so the dashboard and the dispatcher stay consistent ([#13646](https://github.com/diegosouzapw/OmniRoute/pull/13646)) — thanks @maxmad64bis diff --git a/src/app/api/settings/proxies/route.ts b/src/app/api/settings/proxies/route.ts index fe3153629d..35031d4aa4 100644 --- a/src/app/api/settings/proxies/route.ts +++ b/src/app/api/settings/proxies/route.ts @@ -1,3 +1,4 @@ +import { isSocks5ProxyEnabled } from "@omniroute/open-sse/utils/proxyDispatcher"; import { listProxies } from "@/lib/db/proxies"; import { handleProxyCreate, @@ -42,10 +43,8 @@ export async function GET(request: Request) { // #5890: coarse relay health pulse for the dashboard — how many relay // probes have run, and how many came back alive. relayProbeStats: getRelayProbeStats(), - // Default ON (opt-out): only an explicit falsey value disables SOCKS5. - socks5Enabled: !["false", "0", "no", "off"].includes( - (process.env.ENABLE_SOCKS5_PROXY ?? "").trim().toLowerCase() - ), + // SOCKS5 defaults ON — see isSocks5ProxyEnabled(). + socks5Enabled: isSocks5ProxyEnabled(), }); } catch (error) { return createErrorResponseFromUnknown(error, "Failed to load proxies"); diff --git a/src/app/api/settings/proxy/route.ts b/src/app/api/settings/proxy/route.ts index d05d0eb4c2..01d07e6331 100755 --- a/src/app/api/settings/proxy/route.ts +++ b/src/app/api/settings/proxy/route.ts @@ -6,7 +6,10 @@ import { resolveProxyForConnection, } from "@/lib/db/settings"; import { getProxyAssignments, getProxyById } from "@/lib/db/proxies"; -import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; +import { + clearDispatcherCache, + isSocks5ProxyEnabled, +} from "@omniroute/open-sse/utils/proxyDispatcher"; import { updateProxyConfigSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { @@ -29,21 +32,15 @@ const PROXY_LEVEL_TO_REGISTRY_SCOPE = { key: "account", } as const; -function isSocks5Enabled() { - // Default ON (opt-out): only an explicit falsey value disables SOCKS5. - const raw = (process.env.ENABLE_SOCKS5_PROXY ?? "").trim().toLowerCase(); - return !["false", "0", "no", "off"].includes(raw); -} - function getSupportedProxyTypes() { - if (isSocks5Enabled()) { + if (isSocks5ProxyEnabled()) { return new Set([...BASE_SUPPORTED_PROXY_TYPES, "socks5"]); } return BASE_SUPPORTED_PROXY_TYPES; } function supportedTypesMessage() { - return isSocks5Enabled() ? "http, https, or socks5" : "http or https"; + return isSocks5ProxyEnabled() ? "http, https, or socks5" : "http or https"; } function createInvalidProxyError(message: string): ApiRouteError { @@ -104,7 +101,7 @@ function normalizeAndValidateProxy( } const type = String(proxy.type || "http").toLowerCase() as NonNullable; - if (type === "socks5" && !isSocks5Enabled()) { + if (type === "socks5" && !isSocks5ProxyEnabled()) { throw createInvalidProxyError( "SOCKS5 proxy is disabled (remove ENABLE_SOCKS5_PROXY=false to enable — it is ON by default)" ); diff --git a/tests/unit/settings-socks-flag-reader.test.ts b/tests/unit/settings-socks-flag-reader.test.ts new file mode 100644 index 0000000000..9c36410c3e --- /dev/null +++ b/tests/unit/settings-socks-flag-reader.test.ts @@ -0,0 +1,98 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-socks-flag-routes-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const { isSocks5ProxyEnabled } = await import("../../open-sse/utils/proxyDispatcher.ts"); +const proxiesRoute = await import("../../src/app/api/settings/proxies/route.ts"); +const proxyRoute = await import("../../src/app/api/settings/proxy/route.ts"); + +// ENABLE_SOCKS5_PROXY is opt-out: only an explicit falsey value disables SOCKS5. +const MATRIX: Array<[string | undefined, boolean]> = [ + [undefined, true], + ["", true], + ["true", true], + ["1", true], + ["yes", true], + ["false", false], + ["0", false], + ["no", false], + ["off", false], + [" OFF ", false], + ["False", false], +]; + +async function withSocksFlag(value: string | undefined, fn: () => Promise | T): Promise { + const previous = process.env.ENABLE_SOCKS5_PROXY; + if (value === undefined) delete process.env.ENABLE_SOCKS5_PROXY; + else process.env.ENABLE_SOCKS5_PROXY = value; + try { + return await fn(); + } finally { + if (previous === undefined) delete process.env.ENABLE_SOCKS5_PROXY; + else process.env.ENABLE_SOCKS5_PROXY = previous; + } +} + +function putProxy(body: unknown) { + return proxyRoute.PUT( + new Request("http://localhost/api/settings/proxy", { + method: "PUT", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + }) + ); +} + +test.before(() => { + delete process.env.INITIAL_PASSWORD; + core.resetDbInstance(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("flag reader honors the opt-out matrix (unset defaults ON)", async () => { + for (const [value, expected] of MATRIX) { + await withSocksFlag(value, () => { + assert.equal(isSocks5ProxyEnabled(), expected, `ENABLE_SOCKS5_PROXY=${String(value)}`); + }); + } +}); + +test("GET /api/settings/proxies reports socks5Enabled exactly as the flag reader", async () => { + for (const [value, expected] of MATRIX) { + await withSocksFlag(value, async () => { + const response = await proxiesRoute.GET(new Request("http://localhost/api/settings/proxies")); + assert.equal(response.status, 200); + const body = (await response.json()) as { socks5Enabled: boolean }; + assert.equal(body.socks5Enabled, expected, `ENABLE_SOCKS5_PROXY=${String(value)}`); + }); + } +}); + +test("PUT /api/settings/proxy accepts or rejects socks5 following the flag", async () => { + for (const [value, expected] of MATRIX) { + await withSocksFlag(value, async () => { + const response = await putProxy({ + level: "global", + proxy: { type: "socks5", host: "127.0.0.1", port: 1080 }, + }); + const body = (await response.json()) as { error?: { message?: string } }; + if (expected) { + assert.equal(response.status, 200, `ENABLE_SOCKS5_PROXY=${String(value)}`); + } else { + assert.equal(response.status, 400, `ENABLE_SOCKS5_PROXY=${String(value)}`); + assert.match(body.error?.message ?? "", /SOCKS5 proxy is disabled/); + } + }); + } +});