Skip to content

Commit de62975

Browse files
committed
Allow web UI shell without auth
1 parent 243bb6f commit de62975

2 files changed

Lines changed: 31 additions & 13 deletions

File tree

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
// Static UI assets the browser fetches without app-managed credentials, e.g.
2-
// the manifest link in <head>. These bypass auth so the page can install/render
3-
// the manifest icons even when a server password is configured.
1+
// Static UI shell and assets the browser fetches before app-managed credentials
2+
// are available. API routes stay protected; the loaded app supplies credentials
3+
// from its server connection config.
44
export const PUBLIC_UI_PATHS = new Set<string>([
5+
"/",
6+
"/index.html",
57
"/site.webmanifest",
68
"/web-app-manifest-192x192.png",
79
"/web-app-manifest-512x512.png",
810
])
911

1012
export function isPublicUIPath(method: string, pathname: string) {
11-
return method === "GET" && PUBLIC_UI_PATHS.has(pathname)
13+
if (method !== "GET" && method !== "HEAD") return false
14+
if (PUBLIC_UI_PATHS.has(pathname)) return true
15+
return pathname.startsWith("/assets/")
1216
}

packages/opencode/test/server/httpapi-ui.test.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -301,11 +301,25 @@ describe("HttpApi UI fallback", () => {
301301
expect(response.status).toBe(404)
302302
})
303303

304-
test("requires server password for the web UI", async () => {
304+
test("serves the web UI shell without auth even when a server password is set", async () => {
305305
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
306306
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
307307

308-
const response = await uiApp({ password: "secret", username: "opencode" }).request("/")
308+
const response = await uiApp({
309+
password: "secret",
310+
username: "opencode",
311+
client: httpClient(new Response("<html>opencode</html>", { headers: { "content-type": "text/html" } })),
312+
}).request("/")
313+
314+
expect(response.status).toBe(200)
315+
expect(await response.text()).toBe("<html>opencode</html>")
316+
})
317+
318+
test("keeps non-public UI fallback paths protected without auth", async () => {
319+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
320+
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
321+
322+
const response = await uiApp({ password: "secret", username: "opencode" }).request("/session")
309323

310324
expect(response.status).toBe(401)
311325
expect(response.headers.get("www-authenticate")).toBe('Basic realm="Secure Area"')
@@ -336,16 +350,16 @@ describe("HttpApi UI fallback", () => {
336350
expect(response.status).toBe(200)
337351
})
338352

339-
// Regression for #25698 (Ope): the browser fetches the PWA manifest and
340-
// its icons via flows that don't carry app-managed credentials (the
341-
// `<link rel="manifest">` request is not under page-auth control), so the
342-
// server returning 401 breaks PWA install. These specific public assets
343-
// should bypass auth.
344-
test("serves the PWA manifest without auth even when a server password is set", async () => {
353+
test("serves public UI assets without auth even when a server password is set", async () => {
345354
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
346355
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
347356

348-
for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) {
357+
for (const path of [
358+
"/assets/app.js",
359+
"/site.webmanifest",
360+
"/web-app-manifest-192x192.png",
361+
"/web-app-manifest-512x512.png",
362+
]) {
349363
const response = await uiApp({
350364
password: "secret",
351365
username: "opencode",

0 commit comments

Comments
 (0)