From 57cc3325d8a8dfe44ec89a572fb0d766d9a17ec7 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 7 Feb 2026 01:30:49 -0800 Subject: [PATCH] Fix: OPENCODE Auth not passing through properly (#347) --- packages/web/server/index.js | 53 ++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/web/server/index.js b/packages/web/server/index.js index 518c7f52..6ca3d6ad 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -2086,6 +2086,24 @@ const ENV_SKIP_OPENCODE_START = process.env.OPENCODE_SKIP_START === 'true' || process.env.OPENCHAMBER_SKIP_OPENCODE_START === 'true'; const ENV_DESKTOP_NOTIFY = process.env.OPENCHAMBER_DESKTOP_NOTIFY === 'true'; +// OpenCode server authentication (Basic Auth with username "opencode") +const ENV_OPENCODE_SERVER_PASSWORD = (() => { + const pwd = process.env.OPENCODE_SERVER_PASSWORD; + return typeof pwd === 'string' && pwd.length > 0 ? pwd : null; +})(); + +/** + * Returns auth headers for OpenCode server requests if OPENCODE_SERVER_PASSWORD is set. + * Uses Basic Auth with username "opencode" and the password from the env variable. + */ +function getOpenCodeAuthHeaders() { + if (!ENV_OPENCODE_SERVER_PASSWORD) { + return {}; + } + const credentials = Buffer.from(`opencode:${ENV_OPENCODE_SERVER_PASSWORD}`).toString('base64'); + return { Authorization: `Basic ${credentials}` }; +} + const ENV_CONFIGURED_API_PREFIX = normalizeApiPrefix( process.env.OPENCODE_API_PREFIX || process.env.OPENCHAMBER_API_PREFIX || '' ); @@ -2591,6 +2609,7 @@ const startGlobalEventWatcher = async () => { Accept: 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', + ...getOpenCodeAuthHeaders(), }, signal, }); @@ -2789,7 +2808,10 @@ async function waitForReady(url, timeoutMs = 10000) { const timeout = setTimeout(() => controller.abort(), 3000); const res = await fetch(`${url.replace(/\/+$/, '')}/global/health`, { method: 'GET', - headers: { Accept: 'application/json' }, + headers: { + Accept: 'application/json', + ...getOpenCodeAuthHeaders(), + }, signal: controller.signal }); clearTimeout(timeout); @@ -3042,7 +3064,10 @@ const fetchSessionParentId = async (sessionId) => { try { const response = await fetch(buildOpenCodeUrl('/session', ''), { method: 'GET', - headers: { Accept: 'application/json' }, + headers: { + Accept: 'application/json', + ...getOpenCodeAuthHeaders(), + }, signal: AbortSignal.timeout(2000), }); if (!response.ok) { @@ -3564,11 +3589,11 @@ async function waitForOpenCodeReady(timeoutMs = 20000, intervalMs = 400) { const [configResult, agentResult] = await Promise.all([ fetch(buildOpenCodeUrl('/config', ''), { method: 'GET', - headers: { Accept: 'application/json' } + headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() } }).catch((error) => error), fetch(buildOpenCodeUrl('/agent', ''), { method: 'GET', - headers: { Accept: 'application/json' } + headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() } }).catch((error) => error) ]); @@ -3631,7 +3656,7 @@ async function waitForAgentPresence(agentName, timeoutMs = 15000, intervalMs = 3 try { const response = await fetch(buildOpenCodeUrl('/agent'), { method: 'GET', - headers: { Accept: 'application/json' } + headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() } }); if (response.ok) { @@ -3657,7 +3682,7 @@ async function fetchAgentsSnapshot() { const response = await fetch(buildOpenCodeUrl('/agent'), { method: 'GET', - headers: { Accept: 'application/json' } + headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() } }); if (!response.ok) { @@ -3678,7 +3703,7 @@ async function fetchProvidersSnapshot() { const response = await fetch(buildOpenCodeUrl('/provider'), { method: 'GET', - headers: { Accept: 'application/json' } + headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() } }); if (!response.ok) { @@ -3699,7 +3724,7 @@ async function fetchModelsSnapshot() { const response = await fetch(buildOpenCodeUrl('/model'), { method: 'GET', - headers: { Accept: 'application/json' } + headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() } }); if (!response.ok) { @@ -3834,6 +3859,11 @@ function setupProxy(app) { onProxyReq: (proxyReq, req, res) => { console.log(`Proxying ${req.method} ${req.path} to OpenCode`); + const authHeaders = getOpenCodeAuthHeaders(); + if (authHeaders.Authorization) { + proxyReq.setHeader('Authorization', authHeaders.Authorization); + } + if (req.headers.accept && req.headers.accept.includes('text/event-stream')) { console.log(`[SSE] Setting up SSE proxy for ${req.method} ${req.path}`); proxyReq.setHeader('Accept', 'text/event-stream'); @@ -3849,7 +3879,6 @@ function setupProxy(app) { proxyRes.headers['Content-Type'] = 'text/event-stream'; proxyRes.headers['Cache-Control'] = 'no-cache'; proxyRes.headers['Connection'] = 'keep-alive'; - proxyRes.headers['X-Accel-Buffering'] = 'no'; proxyRes.headers['X-Content-Type-Options'] = 'nosniff'; } @@ -4426,7 +4455,8 @@ async function main(options = {}) { const headers = { Accept: 'text/event-stream', 'Cache-Control': 'no-cache', - Connection: 'keep-alive' + Connection: 'keep-alive', + ...getOpenCodeAuthHeaders(), }; const lastEventId = req.header('Last-Event-ID'); @@ -4558,7 +4588,8 @@ async function main(options = {}) { const headers = { Accept: 'text/event-stream', 'Cache-Control': 'no-cache', - Connection: 'keep-alive' + Connection: 'keep-alive', + ...getOpenCodeAuthHeaders(), }; const lastEventId = req.header('Last-Event-ID');