mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-20 19:42:58 +00:00
test: cover HttpApi authorization middleware (#25033)
This commit is contained in:
137
packages/opencode/test/server/httpapi-authorization.test.ts
Normal file
137
packages/opencode/test/server/httpapi-authorization.test.ts
Normal file
@@ -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)
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user