diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index ef966036a9..54b02669d9 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -96,7 +96,10 @@ const cors = (corsOptions?: CorsOptions) => { global: true }, ) -const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(Layer.provide([controlHandlers, globalHandlers])) +const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe( + Layer.provide([controlHandlers, globalHandlers]), + Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))), +) const instanceRouterLayer = authorizationRouterMiddleware .combine(instanceRouterMiddleware) .combine(workspaceRouterMiddleware) diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts index 615899f2b4..de228d8b26 100644 --- a/packages/opencode/test/server/httpapi-bridge.test.ts +++ b/packages/opencode/test/server/httpapi-bridge.test.ts @@ -358,6 +358,60 @@ describe("HttpApi server", () => { expect(good.status).toBe(200) }) + test("requires credentials for root routes when auth is enabled", async () => { + const server = app({ password: "secret" }) + const auth = { authorization: authorization("opencode", "secret") } + const wrongAuth = { authorization: authorization("opencode", "wrong") } + + const [missingConfig, wrongConfig, goodConfig] = await Promise.all([ + server.request(GlobalPaths.config), + server.request(GlobalPaths.config, { headers: wrongAuth }), + server.request(GlobalPaths.config, { headers: auth }), + ]) + + expect(missingConfig.status).toBe(401) + expect(wrongConfig.status).toBe(401) + expect(goodConfig.status).toBe(200) + + const missingDispose = await server.request(GlobalPaths.dispose, { method: "POST" }) + expect(missingDispose.status).toBe(401) + + const missingUpgrade = await server.request(GlobalPaths.upgrade, { + method: "POST", + headers: { "content-type": "application/json" }, + body: "not-json", + }) + expect(missingUpgrade.status).toBe(401) + + const invalidUpgrade = await server.request(GlobalPaths.upgrade, { + method: "POST", + headers: { ...auth, "content-type": "application/json" }, + body: "not-json", + }) + expect(invalidUpgrade.status).toBe(400) + + const missingLog = await server.request(ControlPaths.log, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ service: "httpapi-auth-test", level: "info", message: "hello" }), + }) + expect(missingLog.status).toBe(401) + + const missingAuth = await server.request(ControlPaths.auth.replace(":providerID", "test"), { + method: "PUT", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ type: "api", key: "secret" }), + }) + expect(missingAuth.status).toBe(401) + + const invalidAuth = await server.request(ControlPaths.auth.replace(":providerID", "test"), { + method: "PUT", + headers: { ...auth, "content-type": "application/json" }, + body: JSON.stringify({ type: "api" }), + }) + expect(invalidAuth.status).toBe(400) + }) + test("accepts auth_token query credentials", async () => { await using tmp = await tmpdir({ git: true }) await Bun.write(`${tmp.path}/hello.txt`, "hello")