mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-06 08:21:50 +00:00
fix(auth): respect server username in clients (#25596)
Some checks are pending
deploy / deploy (push) Waiting to run
generate / generate (push) Waiting to run
nix-eval / nix-eval (push) Waiting to run
publish / version (push) Waiting to run
publish / build-cli (push) Blocked by required conditions
publish / sign-cli-windows (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:aarch64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Blocked by required conditions
publish / publish (push) Blocked by required conditions
test / unit (linux) (push) Waiting to run
test / unit (windows) (push) Waiting to run
test / e2e (linux) (push) Waiting to run
test / e2e (windows) (push) Waiting to run
typecheck / typecheck (push) Waiting to run
Some checks are pending
deploy / deploy (push) Waiting to run
generate / generate (push) Waiting to run
nix-eval / nix-eval (push) Waiting to run
publish / version (push) Waiting to run
publish / build-cli (push) Blocked by required conditions
publish / sign-cli-windows (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:aarch64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Blocked by required conditions
publish / publish (push) Blocked by required conditions
test / unit (linux) (push) Waiting to run
test / unit (windows) (push) Waiting to run
test / e2e (linux) (push) Waiting to run
test / e2e (windows) (push) Waiting to run
typecheck / typecheck (push) Waiting to run
This commit is contained in:
parent
0a7d02c87c
commit
8694c5b68f
11 changed files with 148 additions and 88 deletions
|
|
@ -4,9 +4,9 @@ import { effectCmd } from "../effect-cmd"
|
|||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||
import { ACP } from "@/acp/agent"
|
||||
import { Server } from "@/server/server"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
|
||||
const log = Log.create({ service: "acp-command" })
|
||||
|
||||
|
|
@ -27,13 +27,7 @@ export const AcpCommand = effectCmd({
|
|||
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: `http://${server.hostname}:${server.port}`,
|
||||
headers: Flag.OPENCODE_SERVER_PASSWORD
|
||||
? {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`,
|
||||
).toString("base64")}`,
|
||||
}
|
||||
: undefined,
|
||||
headers: ServerAuth.headers(),
|
||||
})
|
||||
|
||||
const input = new WritableStream<Uint8Array>({
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Effect } from "effect"
|
|||
import { UI } from "../ui"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { EOL } from "os"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
|
||||
|
|
@ -656,13 +657,7 @@ export const RunCommand = effectCmd({
|
|||
}
|
||||
|
||||
if (args.attach) {
|
||||
const headers = (() => {
|
||||
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
|
||||
if (!password) return undefined
|
||||
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode"
|
||||
const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
||||
return { Authorization: auth }
|
||||
})()
|
||||
const headers = ServerAuth.headers({ password: args.password })
|
||||
const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers })
|
||||
return await execute(sdk)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
|||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { validateSession } from "./validate-session"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
|
||||
export const AttachCommand = cmd({
|
||||
command: "attach <url>",
|
||||
|
|
@ -38,6 +39,11 @@ export const AttachCommand = cmd({
|
|||
alias: ["p"],
|
||||
type: "string",
|
||||
describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)",
|
||||
})
|
||||
.option("username", {
|
||||
alias: ["u"],
|
||||
type: "string",
|
||||
describe: "basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')",
|
||||
}),
|
||||
handler: async (args) => {
|
||||
const unguard = win32InstallCtrlCGuard()
|
||||
|
|
@ -60,12 +66,7 @@ export const AttachCommand = cmd({
|
|||
return args.dir
|
||||
}
|
||||
})()
|
||||
const headers = (() => {
|
||||
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
|
||||
if (!password) return undefined
|
||||
const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
|
||||
return { Authorization: auth }
|
||||
})()
|
||||
const headers = ServerAuth.headers({ password: args.password, username: args.username })
|
||||
const config = await TuiConfig.get()
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Rpc } from "@/util/rpc"
|
|||
import { upgrade } from "@/cli/upgrade"
|
||||
import { Config } from "@/config/config"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { writeHeapSnapshot } from "node:v8"
|
||||
import { Heap } from "@/cli/heap"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
|
|
@ -50,7 +50,7 @@ let server: Awaited<ReturnType<typeof Server.listen>> | undefined
|
|||
export const rpc = {
|
||||
async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) {
|
||||
const headers = { ...input.headers }
|
||||
const auth = getAuthorizationHeader()
|
||||
const auth = ServerAuth.header()
|
||||
if (auth && !headers["authorization"] && !headers["Authorization"]) {
|
||||
headers["Authorization"] = auth
|
||||
}
|
||||
|
|
@ -102,10 +102,3 @@ export const rpc = {
|
|||
}
|
||||
|
||||
Rpc.listen(rpc)
|
||||
|
||||
function getAuthorizationHeader(): string | undefined {
|
||||
const password = Flag.OPENCODE_SERVER_PASSWORD
|
||||
if (!password) return undefined
|
||||
const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
|
||||
return `Basic ${btoa(`${username}:${password}`)}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Bus } from "../bus"
|
|||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { CodexAuthPlugin } from "./codex"
|
||||
import { Session } from "@/session/session"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
|
|
@ -124,11 +125,7 @@ export const layer = Layer.effect(
|
|||
const client = createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
directory: ctx.directory,
|
||||
headers: Flag.OPENCODE_SERVER_PASSWORD
|
||||
? {
|
||||
Authorization: `Basic ${Buffer.from(`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`).toString("base64")}`,
|
||||
}
|
||||
: undefined,
|
||||
headers: ServerAuth.headers(),
|
||||
fetch: async (...args) => Server.Default().app.fetch(...args),
|
||||
})
|
||||
const cfg = yield* config.get()
|
||||
|
|
|
|||
48
packages/opencode/src/server/auth.ts
Normal file
48
packages/opencode/src/server/auth.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
export * as ServerAuth from "./auth"
|
||||
|
||||
import { ConfigService } from "@/effect/config-service"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Config as EffectConfig, Context, Option, Redacted } from "effect"
|
||||
|
||||
export type Credentials = {
|
||||
password?: string
|
||||
username?: string
|
||||
}
|
||||
|
||||
export type DecodedCredentials = {
|
||||
readonly username: string
|
||||
readonly password: Redacted.Redacted
|
||||
}
|
||||
|
||||
export class Config extends ConfigService.Service<Config>()("@opencode/ServerAuthConfig", {
|
||||
password: EffectConfig.string("OPENCODE_SERVER_PASSWORD").pipe(EffectConfig.option),
|
||||
username: EffectConfig.string("OPENCODE_SERVER_USERNAME").pipe(EffectConfig.withDefault("opencode")),
|
||||
}) {}
|
||||
|
||||
export type Info = Context.Service.Shape<typeof Config>
|
||||
|
||||
export function required(config: Info) {
|
||||
return Option.isSome(config.password) && config.password.value !== ""
|
||||
}
|
||||
|
||||
export function authorized(credentials: DecodedCredentials, config: Info) {
|
||||
return (
|
||||
Option.isSome(config.password) &&
|
||||
credentials.username === config.username &&
|
||||
Redacted.value(credentials.password) === config.password.value
|
||||
)
|
||||
}
|
||||
|
||||
export function header(credentials?: Credentials) {
|
||||
const password = credentials?.password ?? Flag.OPENCODE_SERVER_PASSWORD
|
||||
if (!password) return undefined
|
||||
|
||||
const username = credentials?.username ?? Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
|
||||
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
||||
}
|
||||
|
||||
export function headers(credentials?: Credentials) {
|
||||
const authorization = header(credentials)
|
||||
if (!authorization) return undefined
|
||||
return { Authorization: authorization }
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { ConfigService } from "@/effect/config-service"
|
||||
import { Config, Context, Effect, Encoding, Layer, Option, Redacted } from "effect"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { Effect, Encoding, Layer, Redacted } from "effect"
|
||||
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiError, HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi"
|
||||
|
||||
|
|
@ -18,41 +18,18 @@ export class Authorization extends HttpApiMiddleware.Service<Authorization>()(
|
|||
},
|
||||
) {}
|
||||
|
||||
export class ServerAuthConfig extends ConfigService.Service<ServerAuthConfig>()(
|
||||
"@opencode/ExperimentalHttpApiServerAuthConfig",
|
||||
{
|
||||
password: Config.string("OPENCODE_SERVER_PASSWORD").pipe(Config.option),
|
||||
username: Config.string("OPENCODE_SERVER_USERNAME").pipe(Config.withDefault("opencode")),
|
||||
},
|
||||
) {}
|
||||
|
||||
function validateCredential<A, E, R>(
|
||||
effect: Effect.Effect<A, E, R>,
|
||||
credential: { readonly username: string; readonly password: Redacted.Redacted },
|
||||
config: Context.Service.Shape<typeof ServerAuthConfig>,
|
||||
credential: ServerAuth.DecodedCredentials,
|
||||
config: ServerAuth.Info,
|
||||
) {
|
||||
return Effect.gen(function* () {
|
||||
if (!isAuthRequired(config)) return yield* effect
|
||||
if (!isCredentialAuthorized(credential, config)) return yield* new HttpApiError.Unauthorized({})
|
||||
if (!ServerAuth.required(config)) return yield* effect
|
||||
if (!ServerAuth.authorized(credential, config)) return yield* new HttpApiError.Unauthorized({})
|
||||
return yield* effect
|
||||
})
|
||||
}
|
||||
|
||||
function isAuthRequired(config: Context.Service.Shape<typeof ServerAuthConfig>) {
|
||||
return Option.isSome(config.password) && config.password.value !== ""
|
||||
}
|
||||
|
||||
function isCredentialAuthorized(
|
||||
credential: { readonly username: string; readonly password: Redacted.Redacted },
|
||||
config: Context.Service.Shape<typeof ServerAuthConfig>,
|
||||
) {
|
||||
return (
|
||||
Option.isSome(config.password) &&
|
||||
credential.username === config.username &&
|
||||
Redacted.value(credential.password) === config.password.value
|
||||
)
|
||||
}
|
||||
|
||||
function decodeCredential(input: string) {
|
||||
const emptyCredential = {
|
||||
username: "",
|
||||
|
|
@ -78,11 +55,11 @@ function decodeCredential(input: string) {
|
|||
|
||||
function validateRawCredential<A, E, R>(
|
||||
effect: Effect.Effect<A, E, R>,
|
||||
credential: { readonly username: string; readonly password: Redacted.Redacted },
|
||||
config: Context.Service.Shape<typeof ServerAuthConfig>,
|
||||
credential: ServerAuth.DecodedCredentials,
|
||||
config: ServerAuth.Info,
|
||||
) {
|
||||
if (!isAuthRequired(config)) return effect
|
||||
if (!isCredentialAuthorized(credential, config))
|
||||
if (!ServerAuth.required(config)) return effect
|
||||
if (!ServerAuth.authorized(credential, config))
|
||||
return Effect.succeed(
|
||||
HttpServerResponse.empty({
|
||||
status: UNAUTHORIZED,
|
||||
|
|
@ -94,8 +71,8 @@ function validateRawCredential<A, E, R>(
|
|||
|
||||
export const authorizationRouterMiddleware = HttpRouter.middleware()(
|
||||
Effect.gen(function* () {
|
||||
const config = yield* ServerAuthConfig
|
||||
if (!isAuthRequired(config)) return (effect) => effect
|
||||
const config = yield* ServerAuth.Config
|
||||
if (!ServerAuth.required(config)) return (effect) => effect
|
||||
|
||||
return (effect) =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -122,7 +99,7 @@ export const authorizationRouterMiddleware = HttpRouter.middleware()(
|
|||
export const authorizationLayer = Layer.effect(
|
||||
Authorization,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* ServerAuthConfig
|
||||
const config = yield* ServerAuth.Config
|
||||
return Authorization.of({
|
||||
basic: (effect, { credential }) => validateCredential(effect, credential, config),
|
||||
authToken: (effect, { credential }) =>
|
||||
|
|
|
|||
|
|
@ -46,8 +46,9 @@ import { Worktree } from "@/worktree"
|
|||
import { Workspace } from "@/control-plane/workspace"
|
||||
import { isAllowedCorsOrigin, type CorsOptions } from "@/server/cors"
|
||||
import { serveUIEffect } from "@/server/shared/ui"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { InstanceHttpApi, RootHttpApi } from "./api"
|
||||
import { ServerAuthConfig, authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
|
||||
import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
|
||||
import { EventApi, eventHandlers } from "./event"
|
||||
import { configHandlers } from "./handlers/config"
|
||||
import { controlHandlers } from "./handlers/control"
|
||||
|
|
@ -97,7 +98,7 @@ const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(Layer.provide([cont
|
|||
const instanceRouterLayer = authorizationRouterMiddleware
|
||||
.combine(instanceRouterMiddleware)
|
||||
.combine(workspaceRouterMiddleware)
|
||||
.layer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(ServerAuthConfig.defaultLayer))
|
||||
.layer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const eventApiRoutes = HttpApiBuilder.layer(EventApi).pipe(
|
||||
Layer.provide(eventHandlers),
|
||||
Layer.provide(instanceRouterLayer),
|
||||
|
|
@ -125,7 +126,7 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
|
|||
const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
|
||||
const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
|
||||
Layer.provide([
|
||||
authorizationLayer.pipe(Layer.provide(ServerAuthConfig.defaultLayer)),
|
||||
authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)),
|
||||
workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
|
||||
instanceContextLayer,
|
||||
]),
|
||||
|
|
@ -137,7 +138,7 @@ const uiRoute = HttpRouter.use((router) =>
|
|||
const client = yield* HttpClient.HttpClient
|
||||
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
|
||||
}),
|
||||
).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))))
|
||||
).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))))
|
||||
|
||||
export function createRoutes(corsOptions?: CorsOptions) {
|
||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
||||
|
|
|
|||
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")),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue