fix(server): include cors headers on 404s
This commit is contained in:
parent
08ea08e830
commit
efaeda00a1
2 changed files with 61 additions and 3 deletions
|
|
@ -190,17 +190,45 @@ const docRoute = HttpRouter.use((router) => router.add("GET", "/doc", () => Effe
|
||||||
Layer.provide(authOnlyRouterLayer),
|
Layer.provide(authOnlyRouterLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
const uiRoute = HttpRouter.use((router) =>
|
const uiRoute = (corsOptions?: CorsOptions) => HttpRouter.use((router) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const client = yield* HttpClient.HttpClient
|
const client = yield* HttpClient.HttpClient
|
||||||
const flags = yield* RuntimeFlags.Service
|
const flags = yield* RuntimeFlags.Service
|
||||||
yield* router.add("*", "/*", (request) =>
|
yield* router.add("*", "/*", (request) =>
|
||||||
serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }),
|
serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }).pipe(
|
||||||
|
Effect.map((response) => withNotFoundCors(response, request.headers.origin, corsOptions)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
).pipe(Layer.provide(authOnlyRouterLayer))
|
).pipe(Layer.provide(authOnlyRouterLayer))
|
||||||
|
|
||||||
|
const apiNotFoundRoute = (corsOptions?: CorsOptions) =>
|
||||||
|
HttpRouter.use((router) =>
|
||||||
|
router.add("*", "/api/*", (request) =>
|
||||||
|
Effect.succeed(
|
||||||
|
withNotFoundCors(
|
||||||
|
HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 }),
|
||||||
|
request.headers.origin,
|
||||||
|
corsOptions,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
function withNotFoundCors(
|
||||||
|
response: HttpServerResponse.HttpServerResponse,
|
||||||
|
origin: string | undefined,
|
||||||
|
opts?: CorsOptions,
|
||||||
|
) {
|
||||||
|
if (response.status !== 404 || !origin || !isAllowedCorsOrigin(origin, opts)) return response
|
||||||
|
const vary = response.headers["vary"]
|
||||||
|
const next = HttpServerResponse.setHeader(response, "access-control-allow-origin", origin)
|
||||||
|
if (!vary) return HttpServerResponse.setHeader(next, "vary", "Origin")
|
||||||
|
if (vary.split(",").some((value) => value.trim().toLowerCase() === "origin")) return next
|
||||||
|
return HttpServerResponse.setHeader(next, "vary", `${vary}, Origin`)
|
||||||
|
}
|
||||||
|
|
||||||
type RouteRequirements =
|
type RouteRequirements =
|
||||||
| HttpRouter.HttpRouter
|
| HttpRouter.HttpRouter
|
||||||
| HttpRouter.Request<"Error", unknown>
|
| HttpRouter.Request<"Error", unknown>
|
||||||
|
|
@ -278,7 +306,8 @@ export function createRoutes(
|
||||||
instanceRoutes,
|
instanceRoutes,
|
||||||
serverRoutes,
|
serverRoutes,
|
||||||
docRoute,
|
docRoute,
|
||||||
uiRoute,
|
apiNotFoundRoute(corsOptions),
|
||||||
|
uiRoute(corsOptions),
|
||||||
).pipe(
|
).pipe(
|
||||||
Layer.provide([
|
Layer.provide([
|
||||||
errorLayer,
|
errorLayer,
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,35 @@ describe("HttpApi CORS", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.live("adds CORS headers to not found responses", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const handler = HttpRouter.toWebHandler(
|
||||||
|
HttpApiApp.createRoutes().pipe(
|
||||||
|
Layer.provide(
|
||||||
|
ConfigProvider.layer(
|
||||||
|
ConfigProvider.fromUnknown({
|
||||||
|
OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
{ disableLogger: true },
|
||||||
|
).handler
|
||||||
|
const response = yield* Effect.promise(() =>
|
||||||
|
handler(
|
||||||
|
new Request(new URL("/api/health", "http://localhost"), {
|
||||||
|
method: "POST",
|
||||||
|
headers: { origin: "https://app.opencode.ai" },
|
||||||
|
}),
|
||||||
|
HttpApiApp.context,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(404)
|
||||||
|
expect(response.headers.get("access-control-allow-origin")).toBe("https://app.opencode.ai")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("uses custom CORS origins passed to the server", () =>
|
it.live("uses custom CORS origins passed to the server", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const listener = yield* Effect.acquireRelease(
|
const listener = yield* Effect.acquireRelease(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue