import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; const DIR = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(DIR, '../..'); type BasicAuth = { username: string; password: string }; function stripTrailingSlashes(p: string): string { const out = p.replace(/\/+$/, ''); return out || '/'; } function readApiReadToken(): string | undefined { if (process.env.API_READ_TOKEN) return process.env.API_READ_TOKEN; const p = path.join(ROOT, 'tokens', 'read.json'); if (!fs.existsSync(p)) return undefined; try { const raw = fs.readFileSync(p, 'utf8'); const json = JSON.parse(raw) as { token?: string }; return json?.token ? String(json.token) : undefined; } catch { return undefined; } } function parseBasicAuth(value: string | undefined): BasicAuth | undefined { const raw = String(value || '').trim(); if (!raw) return undefined; const idx = raw.indexOf(':'); if (idx <= 0) return undefined; const username = raw.slice(0, idx).trim(); const password = raw.slice(idx + 1); if (!username || !password) return undefined; return { username, password }; } function readProxyBasicAuth(): BasicAuth | undefined { const fromEnv = parseBasicAuth(process.env.API_PROXY_BASIC_AUTH); if (fromEnv) return fromEnv; const fileRaw = String(process.env.API_PROXY_BASIC_AUTH_FILE || '').trim(); if (!fileRaw) return undefined; const p = path.isAbsolute(fileRaw) ? fileRaw : path.join(ROOT, fileRaw); if (!fs.existsSync(p)) return undefined; try { const raw = fs.readFileSync(p, 'utf8'); const json = JSON.parse(raw) as { username?: string; password?: string }; const username = typeof json?.username === 'string' ? json.username.trim() : ''; const password = typeof json?.password === 'string' ? json.password : ''; if (!username || !password) return undefined; return { username, password }; } catch { return undefined; } } const apiReadToken = readApiReadToken(); const proxyBasicAuth = readProxyBasicAuth(); const apiProxyTarget = process.env.API_PROXY_TARGET || 'http://localhost:8787'; function parseUrl(v: string): URL | undefined { try { return new URL(v); } catch { return undefined; } } const apiProxyTargetUrl = parseUrl(apiProxyTarget); const apiProxyTargetPath = stripTrailingSlashes(apiProxyTargetUrl?.pathname || '/'); const apiProxyTargetEndsWithApi = apiProxyTargetPath.endsWith('/api'); function inferUiProxyTarget(apiTarget: string): string | undefined { try { const u = new URL(apiTarget); const p = stripTrailingSlashes(u.pathname || '/'); if (!p.endsWith('/api')) return undefined; const basePath = p.slice(0, -'/api'.length) || '/'; u.pathname = basePath; u.search = ''; u.hash = ''; const out = u.toString(); return out.endsWith('/') ? out.slice(0, -1) : out; } catch { return undefined; } } const uiProxyTarget = process.env.FRONTEND_PROXY_TARGET || process.env.UI_PROXY_TARGET || process.env.AUTH_PROXY_TARGET || inferUiProxyTarget(apiProxyTarget) || (apiProxyTargetUrl && apiProxyTargetPath === '/' ? stripTrailingSlashes(apiProxyTargetUrl.toString()) : undefined); function applyProxyBasicAuth(proxyReq: any) { if (!proxyBasicAuth) return false; const b64 = Buffer.from(`${proxyBasicAuth.username}:${proxyBasicAuth.password}`, 'utf8').toString('base64'); proxyReq.setHeader('Authorization', `Basic ${b64}`); return true; } function rewriteSetCookieForLocalDevHttp(proxyRes: any) { const v = proxyRes?.headers?.['set-cookie']; if (!v) return; const rewrite = (cookie: string) => { let out = cookie.replace(/;\s*secure\b/gi, ''); out = out.replace(/;\s*domain=[^;]+/gi, ''); out = out.replace(/;\s*samesite=none\b/gi, '; SameSite=Lax'); return out; }; proxyRes.headers['set-cookie'] = Array.isArray(v) ? v.map(rewrite) : rewrite(String(v)); } const proxy: Record = { '/api': { target: apiProxyTarget, changeOrigin: true, rewrite: (p: string) => (apiProxyTargetEndsWithApi ? p.replace(/^\/api/, '') : p), configure: (p: any) => { p.on('proxyReq', (proxyReq: any) => { if (applyProxyBasicAuth(proxyReq)) return; if (apiReadToken) proxyReq.setHeader('Authorization', `Bearer ${apiReadToken}`); }); }, }, }; if (uiProxyTarget) { for (const prefix of ['/whoami', '/auth', '/logout']) { proxy[prefix] = { target: uiProxyTarget, changeOrigin: true, configure: (p: any) => { p.on('proxyReq', (proxyReq: any) => { applyProxyBasicAuth(proxyReq); }); p.on('proxyRes', (proxyRes: any) => { rewriteSetCookieForLocalDevHttp(proxyRes); }); }, }; } } export default defineConfig({ plugins: [react()], server: { port: 5173, strictPort: true, proxy, }, });