refactor(server): clarify HttpApi route auth layers (#26372)
This commit is contained in:
@@ -20,13 +20,17 @@ import { SyncApi } from "./groups/sync"
|
|||||||
import { TuiApi } from "./groups/tui"
|
import { TuiApi } from "./groups/tui"
|
||||||
import { WorkspaceApi } from "./groups/workspace"
|
import { WorkspaceApi } from "./groups/workspace"
|
||||||
import { V2Api } from "./groups/v2"
|
import { V2Api } from "./groups/v2"
|
||||||
|
import { Authorization } from "./middleware/authorization"
|
||||||
|
|
||||||
// SSE event schemas built from the same BusEvent/SyncEvent registries that
|
// SSE event schemas built from the same BusEvent/SyncEvent registries that
|
||||||
// the Hono spec uses, so both specs emit identical Event/SyncEvent components.
|
// the Hono spec uses, so both specs emit identical Event/SyncEvent components.
|
||||||
const EventSchema = Schema.Union(BusEvent.effectPayloads()).annotate({ identifier: "Event" })
|
const EventSchema = Schema.Union(BusEvent.effectPayloads()).annotate({ identifier: "Event" })
|
||||||
const SyncEventSchemas = SyncEvent.effectPayloads()
|
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")
|
export const InstanceHttpApi = HttpApi.make("opencode-instance")
|
||||||
.addHttpApi(ConfigApi)
|
.addHttpApi(ConfigApi)
|
||||||
|
|||||||
@@ -96,9 +96,16 @@ const cors = (corsOptions?: CorsOptions) =>
|
|||||||
{ global: true },
|
{ 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(
|
const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(
|
||||||
Layer.provide([controlHandlers, globalHandlers]),
|
Layer.provide([controlHandlers, globalHandlers]),
|
||||||
Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
|
Layer.provide(httpApiAuthLayer),
|
||||||
)
|
)
|
||||||
const instanceRouterLayer = authorizationRouterMiddleware
|
const instanceRouterLayer = authorizationRouterMiddleware
|
||||||
.combine(instanceRouterMiddleware)
|
.combine(instanceRouterMiddleware)
|
||||||
@@ -131,7 +138,7 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
|
|||||||
const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
|
const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
|
||||||
const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
|
const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
|
||||||
Layer.provide([
|
Layer.provide([
|
||||||
authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)),
|
httpApiAuthLayer,
|
||||||
workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
|
workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
|
||||||
instanceContextLayer,
|
instanceContextLayer,
|
||||||
]),
|
]),
|
||||||
@@ -143,7 +150,7 @@ const uiRoute = HttpRouter.use((router) =>
|
|||||||
const client = yield* HttpClient.HttpClient
|
const client = yield* HttpClient.HttpClient
|
||||||
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
|
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) {
|
export function createRoutes(corsOptions?: CorsOptions) {
|
||||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
||||||
|
|||||||
@@ -363,12 +363,16 @@ describe("HttpApi server", () => {
|
|||||||
const auth = { authorization: authorization("opencode", "secret") }
|
const auth = { authorization: authorization("opencode", "secret") }
|
||||||
const wrongAuth = { authorization: authorization("opencode", "wrong") }
|
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),
|
||||||
server.request(GlobalPaths.config, { headers: wrongAuth }),
|
server.request(GlobalPaths.config, { headers: wrongAuth }),
|
||||||
server.request(GlobalPaths.config, { headers: auth }),
|
server.request(GlobalPaths.config, { headers: auth }),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
expect(missingHealth.status).toBe(401)
|
||||||
|
expect(goodHealth.status).toBe(200)
|
||||||
expect(missingConfig.status).toBe(401)
|
expect(missingConfig.status).toBe(401)
|
||||||
expect(wrongConfig.status).toBe(401)
|
expect(wrongConfig.status).toBe(401)
|
||||||
expect(goodConfig.status).toBe(200)
|
expect(goodConfig.status).toBe(200)
|
||||||
|
|||||||
Reference in New Issue
Block a user