Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
fix: forward the effective user to the Agent API in Vite
Vite has no nginx auth_request, so the Agent tab 401'd on
profile/switch. Send X-Remote-User from /api/agent/session's username
(including impersonation) and replay it on SSE via the proxy.

Co-authored-by: Venkat SF <[email protected]>
  • Loading branch information
cursoragent and venkateshsakamuri-lab committed Aug 18, 2026
commit c99e72cdafe669546a362198f605b4adfa8995df
21 changes: 20 additions & 1 deletion src/lib/api/agentClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,26 @@ const CSRF_HEADER = "X-Hermes-CSRF-Token";
/** Cached CSRF token for the agent API (required once trusted-auth is on). */
let agentCsrfToken = null;

/**
* Effective DeepSQL username from the last `/api/agent/session` bootstrap.
* Vite has no nginx `auth_request` to stamp `X-Remote-User`, so the browser
* must send it. Never hardcode a user — impersonation ("View as") changes this.
*/
let agentRemoteUser = null;

function withAgentAuthHeaders(headers = {}) {
if (agentRemoteUser) {
headers["X-Remote-User"] = agentRemoteUser;
}
return headers;
}

async function ensureAgentCsrf() {
if (agentCsrfToken) return agentCsrfToken;
const res = await fetch(`${AGENT_BASE}/api/auth/status`, { credentials: "include" });
const res = await fetch(`${AGENT_BASE}/api/auth/status`, {
credentials: "include",
headers: withAgentAuthHeaders(),
});
if (!res.ok) return null;
const data = await res.json().catch(() => ({}));
agentCsrfToken = data?.csrf_token || null;
Expand All @@ -35,6 +52,7 @@ async function postJson(url, body, _retried = false) {
// enables the agent auth gate, unsafe POSTs need the session CSRF token or
// the agent answers 403 "Session expired - reload the page".
if (url.startsWith(AGENT_BASE) || url.includes("/agent-api/")) {
withAgentAuthHeaders(headers);
const csrf = await ensureAgentCsrf();
if (csrf) headers[CSRF_HEADER] = csrf;
}
Expand Down Expand Up @@ -99,6 +117,7 @@ export const agentChatAPI = {
/** Resolve/provision the current user's agent profile (via Spring → cookie auth). */
async bootstrap(connectionId) {
const data = await postJson("/api/agent/session", { connectionId });
if (data?.username) agentRemoteUser = data.username;
// Must happen before any session/new / resume path that hits /agent-api.
try {
await switchAgentProfile(data?.profile);
Expand Down
16 changes: 16 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ export default defineConfig({
rewrite: (p) => p.replace(/^\/agent-api/, ''),
timeout: 300000,
proxyTimeout: 300000,
// Production nginx stamps X-Remote-User via auth_request. Vite has no
// equivalent, so the browser sends the effective username (including
// impersonation). EventSource cannot set headers — remember the last
// value and attach it to SSE / other proxied calls.
configure: (proxy) => {
let lastRemoteUser
proxy.on('proxyReq', (proxyReq, req) => {
const incoming = req.headers['x-remote-user']
if (typeof incoming === 'string' && incoming.trim()) {
lastRemoteUser = incoming.trim()
}
if (lastRemoteUser) {
proxyReq.setHeader('X-Remote-User', lastRemoteUser)
}
})
},
},
},
},
Expand Down
Loading