summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-29 21:34:52 -0400
committerGitHub <[email protected]>2026-04-29 21:34:52 -0400
commit38adc13295471de6a8c84bc73d2c94b8e294905e (patch)
tree91163af350df961895edc1739cd43e4eea18e55b
parent4fe14abb8cd34b8af20d31b656715fd140fa4eed (diff)
downloadopencode-38adc13295471de6a8c84bc73d2c94b8e294905e.tar.gz
opencode-38adc13295471de6a8c84bc73d2c94b8e294905e.zip
test: cover HttpApi authorization middleware (#25033)
-rw-r--r--packages/opencode/test/server/httpapi-authorization.test.ts137
1 files changed, 137 insertions, 0 deletions
diff --git a/packages/opencode/test/server/httpapi-authorization.test.ts b/packages/opencode/test/server/httpapi-authorization.test.ts
new file mode 100644
index 000000000..7dec89916
--- /dev/null
+++ b/packages/opencode/test/server/httpapi-authorization.test.ts
@@ -0,0 +1,137 @@
+import { NodeHttpServer } from "@effect/platform-node"
+import { Flag } from "@opencode-ai/core/flag/flag"
+import { describe, expect } from "bun:test"
+import { Effect, Layer, Schema } from "effect"
+import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
+import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"
+import { Authorization, authorizationLayer } from "../../src/server/routes/instance/httpapi/middleware/authorization"
+import { testEffect } from "../lib/effect"
+
+const Api = HttpApi.make("test-authorization").add(
+ HttpApiGroup.make("test")
+ .add(
+ HttpApiEndpoint.get("probe", "/probe", {
+ success: Schema.String,
+ }),
+ )
+ .middleware(Authorization),
+)
+
+const handlers = HttpApiBuilder.group(Api, "test", (handlers) => handlers.handle("probe", () => Effect.succeed("ok")))
+
+const apiLayer = HttpRouter.serve(
+ HttpApiBuilder.layer(Api).pipe(Layer.provide(handlers), Layer.provide(authorizationLayer)),
+ { disableListenLog: true, disableLogger: true },
+).pipe(Layer.provideMerge(NodeHttpServer.layerTest))
+
+const testStateLayer = Layer.effectDiscard(
+ Effect.gen(function* () {
+ const original = {
+ OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
+ OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
+ }
+ Flag.OPENCODE_SERVER_PASSWORD = undefined
+ Flag.OPENCODE_SERVER_USERNAME = undefined
+ yield* Effect.addFinalizer(() =>
+ Effect.sync(() => {
+ Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
+ Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
+ }),
+ )
+ }),
+)
+
+const it = testEffect(apiLayer.pipe(Layer.provideMerge(testStateLayer)))
+
+const basic = (username: string, password: string) =>
+ `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
+
+const token = (username: string, password: string) => Buffer.from(`${username}:${password}`).toString("base64")
+
+const useAuth = (input: { password: string; username?: string }) =>
+ Effect.acquireRelease(
+ Effect.sync(() => {
+ const original = {
+ OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
+ OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
+ }
+ Flag.OPENCODE_SERVER_PASSWORD = input.password
+ Flag.OPENCODE_SERVER_USERNAME = input.username
+ return original
+ }),
+ (original) =>
+ Effect.sync(() => {
+ Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
+ Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
+ }),
+ )
+
+const getProbe = (headers?: Record<string, string>) =>
+ HttpClientRequest.get("/probe").pipe(
+ headers ? HttpClientRequest.setHeaders(headers) : (request) => request,
+ HttpClient.execute,
+ )
+
+describe("HttpApi authorization middleware", () => {
+ it.live("allows requests when server password is not configured", () =>
+ Effect.gen(function* () {
+ const response = yield* getProbe()
+
+ expect(response.status).toBe(200)
+ expect(yield* response.json).toBe("ok")
+ }),
+ )
+
+ it.live("requires configured password for basic auth", () =>
+ Effect.gen(function* () {
+ yield* useAuth({ password: "secret" })
+
+ const [missing, badPassword, good] = yield* Effect.all(
+ [
+ getProbe(),
+ getProbe({ authorization: basic("opencode", "wrong") }),
+ getProbe({ authorization: basic("opencode", "secret") }),
+ ],
+ { concurrency: "unbounded" },
+ )
+
+ expect(missing.status).toBe(401)
+ expect(badPassword.status).toBe(401)
+ expect(good.status).toBe(200)
+ }),
+ )
+
+ it.live("respects configured basic auth username", () =>
+ Effect.gen(function* () {
+ yield* useAuth({ username: "kit", password: "secret" })
+
+ const [defaultUser, configuredUser] = yield* Effect.all(
+ [getProbe({ authorization: basic("opencode", "secret") }), getProbe({ authorization: basic("kit", "secret") })],
+ { concurrency: "unbounded" },
+ )
+
+ expect(defaultUser.status).toBe(401)
+ expect(configuredUser.status).toBe(200)
+ }),
+ )
+
+ it.live("accepts auth token query credentials", () =>
+ Effect.gen(function* () {
+ yield* useAuth({ password: "secret" })
+
+ const response = yield* HttpClient.get(`/probe?auth_token=${encodeURIComponent(token("opencode", "secret"))}`)
+
+ expect(response.status).toBe(200)
+ }),
+ )
+
+ it.live("rejects malformed auth token query credentials", () =>
+ Effect.gen(function* () {
+ yield* useAuth({ password: "secret" })
+
+ const response = yield* HttpClient.get("/probe?auth_token=not-base64")
+
+ expect(response.status).toBe(401)
+ }),
+ )
+})