fix(auth): respect server username in clients (#25596)

This commit is contained in:
Shoubhit Dash
2026-05-03 19:28:31 +05:30
committed by GitHub
parent 0a7d02c87c
commit 8694c5b68f
11 changed files with 148 additions and 88 deletions
+2 -8
View File
@@ -4,9 +4,9 @@ import { effectCmd } from "../effect-cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
import { ACP } from "@/acp/agent" import { ACP } from "@/acp/agent"
import { Server } from "@/server/server" import { Server } from "@/server/server"
import { ServerAuth } from "@/server/auth"
import { createOpencodeClient } from "@opencode-ai/sdk/v2" import { createOpencodeClient } from "@opencode-ai/sdk/v2"
import { withNetworkOptions, resolveNetworkOptions } from "../network" import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "@opencode-ai/core/flag/flag"
const log = Log.create({ service: "acp-command" }) const log = Log.create({ service: "acp-command" })
@@ -27,13 +27,7 @@ export const AcpCommand = effectCmd({
const sdk = createOpencodeClient({ const sdk = createOpencodeClient({
baseUrl: `http://${server.hostname}:${server.port}`, baseUrl: `http://${server.hostname}:${server.port}`,
headers: Flag.OPENCODE_SERVER_PASSWORD headers: ServerAuth.headers(),
? {
Authorization: `Basic ${Buffer.from(
`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`,
).toString("base64")}`,
}
: undefined,
}) })
const input = new WritableStream<Uint8Array>({ const input = new WritableStream<Uint8Array>({
+2 -7
View File
@@ -5,6 +5,7 @@ import { Effect } from "effect"
import { UI } from "../ui" import { UI } from "../ui"
import { effectCmd } from "../effect-cmd" import { effectCmd } from "../effect-cmd"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { ServerAuth } from "@/server/auth"
import { EOL } from "os" import { EOL } from "os"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2" import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
@@ -656,13 +657,7 @@ export const RunCommand = effectCmd({
} }
if (args.attach) { if (args.attach) {
const headers = (() => { const headers = ServerAuth.headers({ password: args.password })
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 sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers }) const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers })
return await execute(sdk) return await execute(sdk)
} }
+7 -6
View File
@@ -5,6 +5,7 @@ import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
import { TuiConfig } from "@/cli/cmd/tui/config/tui" import { TuiConfig } from "@/cli/cmd/tui/config/tui"
import { errorMessage } from "@/util/error" import { errorMessage } from "@/util/error"
import { validateSession } from "./validate-session" import { validateSession } from "./validate-session"
import { ServerAuth } from "@/server/auth"
export const AttachCommand = cmd({ export const AttachCommand = cmd({
command: "attach <url>", command: "attach <url>",
@@ -38,6 +39,11 @@ export const AttachCommand = cmd({
alias: ["p"], alias: ["p"],
type: "string", type: "string",
describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)", 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) => { handler: async (args) => {
const unguard = win32InstallCtrlCGuard() const unguard = win32InstallCtrlCGuard()
@@ -60,12 +66,7 @@ export const AttachCommand = cmd({
return args.dir return args.dir
} }
})() })()
const headers = (() => { const headers = ServerAuth.headers({ password: args.password, username: args.username })
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 config = await TuiConfig.get() const config = await TuiConfig.get()
try { try {
+2 -9
View File
@@ -7,7 +7,7 @@ import { Rpc } from "@/util/rpc"
import { upgrade } from "@/cli/upgrade" import { upgrade } from "@/cli/upgrade"
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { GlobalBus } from "@/bus/global" import { GlobalBus } from "@/bus/global"
import { Flag } from "@opencode-ai/core/flag/flag" import { ServerAuth } from "@/server/auth"
import { writeHeapSnapshot } from "node:v8" import { writeHeapSnapshot } from "node:v8"
import { Heap } from "@/cli/heap" import { Heap } from "@/cli/heap"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
@@ -50,7 +50,7 @@ let server: Awaited<ReturnType<typeof Server.listen>> | undefined
export const rpc = { export const rpc = {
async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) { async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) {
const headers = { ...input.headers } const headers = { ...input.headers }
const auth = getAuthorizationHeader() const auth = ServerAuth.header()
if (auth && !headers["authorization"] && !headers["Authorization"]) { if (auth && !headers["authorization"] && !headers["Authorization"]) {
headers["Authorization"] = auth headers["Authorization"] = auth
} }
@@ -102,10 +102,3 @@ export const rpc = {
} }
Rpc.listen(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}`)}`
}
+2 -5
View File
@@ -10,6 +10,7 @@ import { Bus } from "../bus"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { createOpencodeClient } from "@opencode-ai/sdk" import { createOpencodeClient } from "@opencode-ai/sdk"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { ServerAuth } from "@/server/auth"
import { CodexAuthPlugin } from "./codex" import { CodexAuthPlugin } from "./codex"
import { Session } from "@/session/session" import { Session } from "@/session/session"
import { NamedError } from "@opencode-ai/core/util/error" import { NamedError } from "@opencode-ai/core/util/error"
@@ -124,11 +125,7 @@ export const layer = Layer.effect(
const client = createOpencodeClient({ const client = createOpencodeClient({
baseUrl: "http://localhost:4096", baseUrl: "http://localhost:4096",
directory: ctx.directory, directory: ctx.directory,
headers: Flag.OPENCODE_SERVER_PASSWORD headers: ServerAuth.headers(),
? {
Authorization: `Basic ${Buffer.from(`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`).toString("base64")}`,
}
: undefined,
fetch: async (...args) => Server.Default().app.fetch(...args), fetch: async (...args) => Server.Default().app.fetch(...args),
}) })
const cfg = yield* config.get() const cfg = yield* config.get()
+48
View 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 { ServerAuth } from "@/server/auth"
import { Config, Context, Effect, Encoding, Layer, Option, Redacted } from "effect" import { Effect, Encoding, Layer, Redacted } from "effect"
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { HttpApiError, HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" 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>( function validateCredential<A, E, R>(
effect: Effect.Effect<A, E, R>, effect: Effect.Effect<A, E, R>,
credential: { readonly username: string; readonly password: Redacted.Redacted }, credential: ServerAuth.DecodedCredentials,
config: Context.Service.Shape<typeof ServerAuthConfig>, config: ServerAuth.Info,
) { ) {
return Effect.gen(function* () { return Effect.gen(function* () {
if (!isAuthRequired(config)) return yield* effect if (!ServerAuth.required(config)) return yield* effect
if (!isCredentialAuthorized(credential, config)) return yield* new HttpApiError.Unauthorized({}) if (!ServerAuth.authorized(credential, config)) return yield* new HttpApiError.Unauthorized({})
return yield* effect 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) { function decodeCredential(input: string) {
const emptyCredential = { const emptyCredential = {
username: "", username: "",
@@ -78,11 +55,11 @@ function decodeCredential(input: string) {
function validateRawCredential<A, E, R>( function validateRawCredential<A, E, R>(
effect: Effect.Effect<A, E, R>, effect: Effect.Effect<A, E, R>,
credential: { readonly username: string; readonly password: Redacted.Redacted }, credential: ServerAuth.DecodedCredentials,
config: Context.Service.Shape<typeof ServerAuthConfig>, config: ServerAuth.Info,
) { ) {
if (!isAuthRequired(config)) return effect if (!ServerAuth.required(config)) return effect
if (!isCredentialAuthorized(credential, config)) if (!ServerAuth.authorized(credential, config))
return Effect.succeed( return Effect.succeed(
HttpServerResponse.empty({ HttpServerResponse.empty({
status: UNAUTHORIZED, status: UNAUTHORIZED,
@@ -94,8 +71,8 @@ function validateRawCredential<A, E, R>(
export const authorizationRouterMiddleware = HttpRouter.middleware()( export const authorizationRouterMiddleware = HttpRouter.middleware()(
Effect.gen(function* () { Effect.gen(function* () {
const config = yield* ServerAuthConfig const config = yield* ServerAuth.Config
if (!isAuthRequired(config)) return (effect) => effect if (!ServerAuth.required(config)) return (effect) => effect
return (effect) => return (effect) =>
Effect.gen(function* () { Effect.gen(function* () {
@@ -122,7 +99,7 @@ export const authorizationRouterMiddleware = HttpRouter.middleware()(
export const authorizationLayer = Layer.effect( export const authorizationLayer = Layer.effect(
Authorization, Authorization,
Effect.gen(function* () { Effect.gen(function* () {
const config = yield* ServerAuthConfig const config = yield* ServerAuth.Config
return Authorization.of({ return Authorization.of({
basic: (effect, { credential }) => validateCredential(effect, credential, config), basic: (effect, { credential }) => validateCredential(effect, credential, config),
authToken: (effect, { credential }) => authToken: (effect, { credential }) =>
@@ -46,8 +46,9 @@ import { Worktree } from "@/worktree"
import { Workspace } from "@/control-plane/workspace" import { Workspace } from "@/control-plane/workspace"
import { isAllowedCorsOrigin, type CorsOptions } from "@/server/cors" import { isAllowedCorsOrigin, type CorsOptions } from "@/server/cors"
import { serveUIEffect } from "@/server/shared/ui" import { serveUIEffect } from "@/server/shared/ui"
import { ServerAuth } from "@/server/auth"
import { InstanceHttpApi, RootHttpApi } from "./api" import { InstanceHttpApi, RootHttpApi } from "./api"
import { ServerAuthConfig, authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization" import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
import { EventApi, eventHandlers } from "./event" import { EventApi, eventHandlers } from "./event"
import { configHandlers } from "./handlers/config" import { configHandlers } from "./handlers/config"
import { controlHandlers } from "./handlers/control" import { controlHandlers } from "./handlers/control"
@@ -97,7 +98,7 @@ const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(Layer.provide([cont
const instanceRouterLayer = authorizationRouterMiddleware const instanceRouterLayer = authorizationRouterMiddleware
.combine(instanceRouterMiddleware) .combine(instanceRouterMiddleware)
.combine(workspaceRouterMiddleware) .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( const eventApiRoutes = HttpApiBuilder.layer(EventApi).pipe(
Layer.provide(eventHandlers), Layer.provide(eventHandlers),
Layer.provide(instanceRouterLayer), Layer.provide(instanceRouterLayer),
@@ -125,7 +126,7 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer)) const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe( const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
Layer.provide([ Layer.provide([
authorizationLayer.pipe(Layer.provide(ServerAuthConfig.defaultLayer)), authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)),
workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)), workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
instanceContextLayer, instanceContextLayer,
]), ]),
@@ -137,7 +138,7 @@ const uiRoute = HttpRouter.use((router) =>
const client = yield* HttpClient.HttpClient const client = yield* HttpClient.HttpClient
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client })) 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) { export function createRoutes(corsOptions?: CorsOptions) {
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe( return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
@@ -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 { Effect, Layer, Option, Schema } from "effect"
import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http" import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"
import { import { ServerAuth } from "../../src/server/auth"
Authorization, import { Authorization, authorizationLayer } from "../../src/server/routes/instance/httpapi/middleware/authorization"
ServerAuthConfig,
authorizationLayer,
} from "../../src/server/routes/instance/httpapi/middleware/authorization"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
const Api = HttpApi.make("test-authorization").add( const Api = HttpApi.make("test-authorization").add(
@@ -27,9 +24,9 @@ const apiLayer = HttpRouter.serve(
{ disableListenLog: true, disableLogger: true }, { disableListenLog: true, disableLogger: true },
).pipe(Layer.provideMerge(NodeHttpServer.layerTest)) ).pipe(Layer.provideMerge(NodeHttpServer.layerTest))
const noAuthLayer = ServerAuthConfig.layer({ password: Option.none(), username: "opencode" }) const noAuthLayer = ServerAuth.Config.layer({ password: Option.none(), username: "opencode" })
const secretLayer = ServerAuthConfig.layer({ password: Option.some("secret"), username: "opencode" }) const secretLayer = ServerAuth.Config.layer({ password: Option.some("secret"), username: "opencode" })
const kitSecretLayer = ServerAuthConfig.layer({ password: Option.some("secret"), username: "kit" }) const kitSecretLayer = ServerAuth.Config.layer({ password: Option.some("secret"), username: "kit" })
const it = testEffect(apiLayer.pipe(Layer.provide(noAuthLayer))) const it = testEffect(apiLayer.pipe(Layer.provide(noAuthLayer)))
const itSecret = testEffect(apiLayer.pipe(Layer.provide(secretLayer))) const itSecret = testEffect(apiLayer.pipe(Layer.provide(secretLayer)))
@@ -12,10 +12,8 @@ import {
HttpServerResponse, HttpServerResponse,
} from "effect/unstable/http" } from "effect/unstable/http"
import { AppFileSystem } from "@opencode-ai/core/filesystem" import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { import { ServerAuth } from "../../src/server/auth"
ServerAuthConfig, import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
authorizationRouterMiddleware,
} from "../../src/server/routes/instance/httpapi/middleware/authorization"
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
import { serveUIEffect } from "../../src/server/shared/ui" import { serveUIEffect } from "../../src/server/shared/ui"
import { Server } from "../../src/server/server" 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 })) yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
}), }),
).pipe( ).pipe(
Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))), Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
Layer.provide([ Layer.provide([
AppFileSystem.defaultLayer, AppFileSystem.defaultLayer,
input?.client ?? httpClient(new Response("ui")), input?.client ?? httpClient(new Response("ui")),