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";
/** 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 {
// 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;
return (
bridgeUrlOverride ??
metaEnv?.VITE_BRIDGE_URL ??
"http://localhost:4000"
);
return bridgeUrlOverride ?? metaEnv?.VITE_BRIDGE_URL ?? "/bridge";
}
/** 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) => {
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) {
config.headers["Authorization"] = `Bearer ${token}`;
}

View file

@ -16,6 +16,7 @@ export default defineConfig(({ mode }) => {
BILLING_TRIAL_DAYS,
POSTHOG_HOST,
POSTHOG_KEY,
VITE_BRIDGE_TOKEN,
} = loadEnv(mode, envPath, "");
return {
@ -31,6 +32,7 @@ export default defineConfig(({ mode }) => {
BILLING_TRIAL_DAYS,
POSTHOG_HOST,
POSTHOG_KEY,
VITE_BRIDGE_TOKEN,
},
APP_VERSION: JSON.stringify(process.env.npm_package_version),
},
@ -70,6 +72,16 @@ export default defineConfig(({ mode }) => {
ws: 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"),
},
},
},
};