mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-20 19:42:58 +00:00
fix(auth): respect server username in clients (#25596)
This commit is contained in:
59
packages/opencode/test/server/auth.test.ts
Normal file
59
packages/opencode/test/server/auth.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Option, Redacted } from "effect"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { ServerAuth } from "../../src/server/auth"
|
||||
|
||||
const original = {
|
||||
OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
|
||||
OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
|
||||
Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
|
||||
})
|
||||
|
||||
describe("ServerAuth", () => {
|
||||
test("does not emit auth headers without a password", () => {
|
||||
Flag.OPENCODE_SERVER_PASSWORD = undefined
|
||||
Flag.OPENCODE_SERVER_USERNAME = "alice"
|
||||
|
||||
expect(ServerAuth.header()).toBeUndefined()
|
||||
expect(ServerAuth.headers()).toBeUndefined()
|
||||
})
|
||||
|
||||
test("defaults to the opencode username", () => {
|
||||
Flag.OPENCODE_SERVER_PASSWORD = "secret"
|
||||
Flag.OPENCODE_SERVER_USERNAME = undefined
|
||||
|
||||
expect(ServerAuth.headers()).toEqual({
|
||||
Authorization: `Basic ${Buffer.from("opencode:secret").toString("base64")}`,
|
||||
})
|
||||
})
|
||||
|
||||
test("uses the configured username", () => {
|
||||
Flag.OPENCODE_SERVER_PASSWORD = "secret"
|
||||
Flag.OPENCODE_SERVER_USERNAME = "alice"
|
||||
|
||||
expect(ServerAuth.headers()).toEqual({
|
||||
Authorization: `Basic ${Buffer.from("alice:secret").toString("base64")}`,
|
||||
})
|
||||
})
|
||||
|
||||
test("prefers explicit credentials", () => {
|
||||
Flag.OPENCODE_SERVER_PASSWORD = "secret"
|
||||
Flag.OPENCODE_SERVER_USERNAME = "alice"
|
||||
|
||||
expect(ServerAuth.headers({ password: "cli-secret", username: "bob" })).toEqual({
|
||||
Authorization: `Basic ${Buffer.from("bob:cli-secret").toString("base64")}`,
|
||||
})
|
||||
})
|
||||
|
||||
test("validates decoded credentials against effect config", () => {
|
||||
const config = { password: Option.some("secret"), username: "alice" }
|
||||
|
||||
expect(ServerAuth.required(config)).toBe(true)
|
||||
expect(ServerAuth.authorized({ username: "alice", password: Redacted.make("secret") }, config)).toBe(true)
|
||||
expect(ServerAuth.authorized({ username: "opencode", password: Redacted.make("secret") }, config)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -3,11 +3,8 @@ import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer, Option, Schema } from "effect"
|
||||
import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"
|
||||
import {
|
||||
Authorization,
|
||||
ServerAuthConfig,
|
||||
authorizationLayer,
|
||||
} from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
||||
import { ServerAuth } from "../../src/server/auth"
|
||||
import { Authorization, authorizationLayer } from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const Api = HttpApi.make("test-authorization").add(
|
||||
@@ -27,9 +24,9 @@ const apiLayer = HttpRouter.serve(
|
||||
{ disableListenLog: true, disableLogger: true },
|
||||
).pipe(Layer.provideMerge(NodeHttpServer.layerTest))
|
||||
|
||||
const noAuthLayer = ServerAuthConfig.layer({ password: Option.none(), username: "opencode" })
|
||||
const secretLayer = ServerAuthConfig.layer({ password: Option.some("secret"), username: "opencode" })
|
||||
const kitSecretLayer = ServerAuthConfig.layer({ password: Option.some("secret"), username: "kit" })
|
||||
const noAuthLayer = ServerAuth.Config.layer({ password: Option.none(), username: "opencode" })
|
||||
const secretLayer = ServerAuth.Config.layer({ password: Option.some("secret"), username: "opencode" })
|
||||
const kitSecretLayer = ServerAuth.Config.layer({ password: Option.some("secret"), username: "kit" })
|
||||
|
||||
const it = testEffect(apiLayer.pipe(Layer.provide(noAuthLayer)))
|
||||
const itSecret = testEffect(apiLayer.pipe(Layer.provide(secretLayer)))
|
||||
|
||||
@@ -12,10 +12,8 @@ import {
|
||||
HttpServerResponse,
|
||||
} from "effect/unstable/http"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import {
|
||||
ServerAuthConfig,
|
||||
authorizationRouterMiddleware,
|
||||
} from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
||||
import { ServerAuth } from "../../src/server/auth"
|
||||
import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||
import { serveUIEffect } from "../../src/server/shared/ui"
|
||||
import { Server } from "../../src/server/server"
|
||||
@@ -81,7 +79,7 @@ function uiApp(input?: { password?: string; username?: string; client?: Layer.La
|
||||
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
|
||||
}),
|
||||
).pipe(
|
||||
Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))),
|
||||
Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
|
||||
Layer.provide([
|
||||
AppFileSystem.defaultLayer,
|
||||
input?.client ?? httpClient(new Response("ui")),
|
||||
|
||||
Reference in New Issue
Block a user