Ver Fonte

refactor(server): clarify HttpApi route auth layers (#26372)

Kit Langton há 4 meses atrás
pai
commit
3052a79b32

+ 5 - 1
packages/opencode/src/server/routes/instance/httpapi/api.ts

@@ -20,13 +20,17 @@ import { SyncApi } from "./groups/sync"
 import { TuiApi } from "./groups/tui"
 import { WorkspaceApi } from "./groups/workspace"
 import { V2Api } from "./groups/v2"
+import { Authorization } from "./middleware/authorization"
 
 // SSE event schemas built from the same BusEvent/SyncEvent registries that
 // the Hono spec uses, so both specs emit identical Event/SyncEvent components.
 const EventSchema = Schema.Union(BusEvent.effectPayloads()).annotate({ identifier: "Event" })
 const SyncEventSchemas = SyncEvent.effectPayloads()
 
-export const RootHttpApi = HttpApi.make("opencode-root").addHttpApi(ControlApi).addHttpApi(GlobalApi)
+export const RootHttpApi = HttpApi.make("opencode-root")
+  .addHttpApi(ControlApi)
+  .addHttpApi(GlobalApi)
+  .middleware(Authorization)
 
 export const InstanceHttpApi = HttpApi.make("opencode-instance")
   .addHttpApi(ConfigApi)

+ 10 - 3
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -96,9 +96,16 @@ const cors = (corsOptions?: CorsOptions) =>
     { global: true },
   )
 
+// Route tree:
+// - rootApiRoutes: typed /global/* and control routes; auth is declared by RootHttpApi.
+// - eventApiRoutes/rawInstanceRoutes: raw instance routes; auth and workspace routing happen as router middleware.
+// - instanceApiRoutes: schema routes; auth is declared on each group and workspace context is provided below.
+// - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it.
+const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
+const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
 const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(
   Layer.provide([controlHandlers, globalHandlers]),
-  Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
+  Layer.provide(httpApiAuthLayer),
 )
 const instanceRouterLayer = authorizationRouterMiddleware
   .combine(instanceRouterMiddleware)
@@ -131,7 +138,7 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
 const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
 const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
   Layer.provide([
-    authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)),
+    httpApiAuthLayer,
     workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
     instanceContextLayer,
   ]),
@@ -143,7 +150,7 @@ const uiRoute = HttpRouter.use((router) =>
     const client = yield* HttpClient.HttpClient
     yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
   }),
-).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))))
+).pipe(Layer.provide(authOnlyRouterLayer))
 
 export function createRoutes(corsOptions?: CorsOptions) {
   return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(

+ 5 - 1
packages/opencode/test/server/httpapi-bridge.test.ts

@@ -363,12 +363,16 @@ describe("HttpApi server", () => {
     const auth = { authorization: authorization("opencode", "secret") }
     const wrongAuth = { authorization: authorization("opencode", "wrong") }
 
-    const [missingConfig, wrongConfig, goodConfig] = await Promise.all([
+    const [missingHealth, goodHealth, missingConfig, wrongConfig, goodConfig] = await Promise.all([
+      server.request(GlobalPaths.health),
+      server.request(GlobalPaths.health, { headers: auth }),
       server.request(GlobalPaths.config),
       server.request(GlobalPaths.config, { headers: wrongAuth }),
       server.request(GlobalPaths.config, { headers: auth }),
     ])
 
+    expect(missingHealth.status).toBe(401)
+    expect(goodHealth.status).toBe(200)
     expect(missingConfig.status).toBe(401)
     expect(wrongConfig.status).toBe(401)
     expect(goodConfig.status).toBe(200)