diff --git a/README.md b/README.md index 7f12b538..5bd73c87 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,14 @@ To use another remote API route: $env:VITE_API_PROXY_BASE_PATH="/canary/api"; npm run dev ``` +TLS certificate validation is enabled for proxied HTTPS APIs by default. If you +are using a trusted local HTTPS API with a self-signed certificate, you can opt +out explicitly: + +```powershell +$env:VITE_API_PROXY_TARGET="https://local-api.test"; $env:VITE_API_PROXY_SECURE="false"; npm run dev +``` + For compatible local gateways that expect the `/api` prefix to be preserved: ```powershell diff --git a/tests/unit/vite-api-proxy.spec.js b/tests/unit/vite-api-proxy.spec.js index 65ad24f4..2dd23591 100644 --- a/tests/unit/vite-api-proxy.spec.js +++ b/tests/unit/vite-api-proxy.spec.js @@ -7,7 +7,7 @@ describe("Vite API proxy", () => { expect(options.target).toBe("https://api-v2.truckwash.io"); expect(options.changeOrigin).toBe(true); - expect(options.secure).toBe(false); + expect(options.secure).toBe(true); expect(options.rewrite("/api/ping")).toBe("/master/api/ping"); expect(options.rewrite("/api/release/runtime")).toBe("/master/api/release/runtime"); expect(options.rewrite("/api")).toBe("/master/api"); @@ -32,6 +32,16 @@ describe("Vite API proxy", () => { expect(options.rewrite("/api")).toBe("/canary/api"); }); + it("allows disabling TLS verification for explicitly trusted local gateways", () => { + const options = createApiProxyOptions({ + VITE_API_PROXY_TARGET: "https://local-api.test", + VITE_API_PROXY_SECURE: "false", + }); + + expect(options.target).toBe("https://local-api.test"); + expect(options.secure).toBe(false); + }); + it("allows disabling prefix stripping for compatible local gateways", () => { const options = createApiProxyOptions({ VITE_API_PROXY_TARGET: "http://localhost", diff --git a/vite.config.js b/vite.config.js index 4c0fd066..c1a983ee 100644 --- a/vite.config.js +++ b/vite.config.js @@ -389,6 +389,10 @@ function normalizeProxyBasePath(value) { return normalized ? `/${normalized}` : '' } +function parseProxySecure(value) { + return String(value || '').trim().toLowerCase() !== 'false' +} + function rewriteApiProxyPath(requestPath, basePath = '') { const pathWithoutApiPrefix = String(requestPath || '/').replace(/^\/api(?=\/|\?|$)/, '') || '/' if (!basePath) { @@ -404,6 +408,7 @@ export function createApiProxyOptions(env = process.env) { const stripPrefix = env.VITE_API_PROXY_STRIP_PREFIX !== 'false' const configuredTarget = String(env.VITE_API_PROXY_TARGET || '').trim() const target = configuredTarget || DEFAULT_API_PROXY_TARGET + const secure = parseProxySecure(env.VITE_API_PROXY_SECURE) const basePath = env.VITE_API_PROXY_BASE_PATH !== undefined ? normalizeProxyBasePath(env.VITE_API_PROXY_BASE_PATH) : configuredTarget @@ -413,7 +418,7 @@ export function createApiProxyOptions(env = process.env) { return { target, changeOrigin: true, - secure: false, + secure, ...(stripPrefix ? { rewrite: (requestPath) => rewriteApiProxyPath(requestPath, basePath)