fix(client): bridge same-origin proxy via Vite + dev token fallback — Patch 021

Cross-origin (5173 vs 4000) blocked the auth cookie from reaching the
bridge — every /database slash command request 401'd silently. Two
changes:

1. vite.config.ts: add /bridge -> http://localhost:4000 proxy with
   path rewrite + /bridge-events -> /api/events for SSE. Same-origin =
   browser sends the auth cookie automatically.
2. bridge-client.ts: resolveBridgeUrl() defaults to /bridge instead
   of absolute http://localhost:4000. Also adds a VITE_BRIDGE_TOKEN env
   fallback for dev (the bridge requires Bearer auth and the Docmost
   session cookie is HttpOnly so JS cannot read it).

Patch 021.
This commit is contained in:
Corentin JOGUET 2026-05-08 12:32:33 +02:00
parent 5b512e6324
commit aef912f9a4
2 changed files with 25 additions and 10 deletions

View file

@ -17,17 +17,16 @@
import axios, { AxiosInstance } from "axios"; import axios, { AxiosInstance } from "axios";
/** Resolved bridge base URL: per-instance override > env var > default. */ /** Resolved bridge base URL: per-instance override > env var > same-origin proxy default.
*
* In dev, the Vite server proxies `/bridge/*` to `http://localhost:4000/*` (see vite.config.ts).
* Same-origin = the auth cookie is sent automatically without CORS gymnastics.
* In prod, set VITE_BRIDGE_URL at build time to an absolute URL behind your reverse proxy
* (or keep `/bridge` and proxy server-side).
*/
export function resolveBridgeUrl(bridgeUrlOverride?: string | null): string { export function resolveBridgeUrl(bridgeUrlOverride?: string | null): string {
// Vite exposes import.meta.env at build time. The double cast is required
// because ImportMeta has no index signature in TypeScript's strict mode,
// but at runtime Vite replaces the env values.
const metaEnv = (import.meta as unknown as { env?: { VITE_BRIDGE_URL?: string } }).env; const metaEnv = (import.meta as unknown as { env?: { VITE_BRIDGE_URL?: string } }).env;
return ( return bridgeUrlOverride ?? metaEnv?.VITE_BRIDGE_URL ?? "/bridge";
bridgeUrlOverride ??
metaEnv?.VITE_BRIDGE_URL ??
"http://localhost:4000"
);
} }
/** Attempt to read the auth token from JS-accessible cookie or jotai storage. */ /** Attempt to read the auth token from JS-accessible cookie or jotai storage. */
@ -58,7 +57,11 @@ export function createBridgeClient(bridgeUrl: string): AxiosInstance {
}); });
instance.interceptors.request.use((config) => { instance.interceptors.request.use((config) => {
const token = readTokenFromCookie(); // Priority: cookie token (prod) > VITE_BRIDGE_TOKEN env (dev fallback)
const cookieToken = readTokenFromCookie();
const metaEnv = (import.meta as unknown as { env?: { VITE_BRIDGE_TOKEN?: string } }).env;
const envToken = metaEnv?.VITE_BRIDGE_TOKEN;
const token = cookieToken || envToken;
if (token) { if (token) {
config.headers["Authorization"] = `Bearer ${token}`; config.headers["Authorization"] = `Bearer ${token}`;
} }

View file

@ -16,6 +16,7 @@ export default defineConfig(({ mode }) => {
BILLING_TRIAL_DAYS, BILLING_TRIAL_DAYS,
POSTHOG_HOST, POSTHOG_HOST,
POSTHOG_KEY, POSTHOG_KEY,
VITE_BRIDGE_TOKEN,
} = loadEnv(mode, envPath, ""); } = loadEnv(mode, envPath, "");
return { return {
@ -31,6 +32,7 @@ export default defineConfig(({ mode }) => {
BILLING_TRIAL_DAYS, BILLING_TRIAL_DAYS,
POSTHOG_HOST, POSTHOG_HOST,
POSTHOG_KEY, POSTHOG_KEY,
VITE_BRIDGE_TOKEN,
}, },
APP_VERSION: JSON.stringify(process.env.npm_package_version), APP_VERSION: JSON.stringify(process.env.npm_package_version),
}, },
@ -70,6 +72,16 @@ export default defineConfig(({ mode }) => {
ws: true, ws: true,
rewriteWsOrigin: true, rewriteWsOrigin: true,
}, },
"/bridge": {
target: process.env.VITE_BRIDGE_PROXY_TARGET || "http://localhost:4000",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/bridge/, ""),
},
"/bridge-events": {
target: process.env.VITE_BRIDGE_PROXY_TARGET || "http://localhost:4000",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/bridge-events/, "/api/events"),
},
}, },
}, },
}; };