fix(server): respect configured OpenCode auth (#366)

Fixes #315

## Summary
- stop overwriting configured `OPENCODE_SERVER_USERNAME` and
`OPENCODE_SERVER_PASSWORD` when CodeNomad launches managed OpenCode
servers
- reuse user-provided OpenCode auth from workspace environment or
process env before falling back to generated credentials
- add focused tests for configured, inherited, and generated auth paths

## Testing
- `npx tsx --test
"packages/server/src/workspaces/opencode-auth.test.ts"`
- `npx tsc --noEmit --target ES2020 --module ESNext --moduleResolution
Node --strict --esModuleInterop --types node
"packages/server/src/workspaces/opencode-auth.ts"
"packages/server/src/workspaces/opencode-auth.test.ts"`
- `git diff --check`

## Notes
- full server workspace typecheck still has unrelated baseline failures
in this branch (`commander` typings and missing `fuzzysort` types)
This commit is contained in:
Pascal André
2026-04-26 16:49:42 +02:00
committed by GitHub
parent 28a2df20ca
commit f5b32f2c0b
3 changed files with 72 additions and 4 deletions

View File

@@ -13,10 +13,9 @@ import { Logger } from "../logger"
import { getOpencodeConfigDir } from "../opencode-config.js"
import {
buildOpencodeBasicAuthHeader,
DEFAULT_OPENCODE_USERNAME,
generateOpencodeServerPassword,
OPENCODE_SERVER_PASSWORD_ENV,
OPENCODE_SERVER_USERNAME_ENV,
resolveOpencodeServerAuth,
} from "./opencode-auth"
const STARTUP_STABILITY_DELAY_MS = 1500
@@ -124,8 +123,10 @@ export class WorkspaceManager {
const envVars = (serverConfig as any)?.environmentVariables
const userEnvironment = envVars && typeof envVars === "object" && !Array.isArray(envVars) ? (envVars as any) : {}
const opencodeUsername = DEFAULT_OPENCODE_USERNAME
const opencodePassword = generateOpencodeServerPassword()
const { username: opencodeUsername, password: opencodePassword } = resolveOpencodeServerAuth({
userEnvironment,
processEnv: process.env,
})
const authorization = buildOpencodeBasicAuthHeader({ username: opencodeUsername, password: opencodePassword })
if (!authorization) {
throw new Error("Failed to build OpenCode auth header")

View File

@@ -0,0 +1,41 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { resolveOpencodeServerAuth } from "./opencode-auth"
describe("resolveOpencodeServerAuth", () => {
it("uses configured OpenCode auth from workspace environment", () => {
const auth = resolveOpencodeServerAuth({
userEnvironment: {
OPENCODE_SERVER_USERNAME: "alice",
OPENCODE_SERVER_PASSWORD: "secret",
},
processEnv: {},
generatePassword: () => "generated",
})
assert.deepEqual(auth, { username: "alice", password: "secret" })
})
it("uses process environment when workspace environment does not provide credentials", () => {
const auth = resolveOpencodeServerAuth({
userEnvironment: {},
processEnv: {
OPENCODE_SERVER_PASSWORD: "process-secret",
},
generatePassword: () => "generated",
})
assert.deepEqual(auth, { username: "codenomad", password: "process-secret" })
})
it("falls back to generated credentials", () => {
const auth = resolveOpencodeServerAuth({
userEnvironment: {},
processEnv: {},
generatePassword: () => "generated",
})
assert.deepEqual(auth, { username: "codenomad", password: "generated" })
})
})

View File

@@ -9,6 +9,32 @@ export function generateOpencodeServerPassword(): string {
return crypto.randomBytes(32).toString("base64url")
}
function readConfiguredValue(key: string, ...sources: Array<Record<string, unknown> | undefined>): string | undefined {
for (const source of sources) {
const value = source?.[key]
if (typeof value === "string" && value.trim().length > 0) {
return value
}
}
return undefined
}
export function resolveOpencodeServerAuth(options: {
userEnvironment?: Record<string, unknown>
processEnv?: NodeJS.ProcessEnv
generatePassword?: () => string
} = {}): { username: string; password: string } {
const generatePassword = options.generatePassword ?? generateOpencodeServerPassword
const username =
readConfiguredValue(OPENCODE_SERVER_USERNAME_ENV, options.userEnvironment, options.processEnv) ??
DEFAULT_OPENCODE_USERNAME
const password =
readConfiguredValue(OPENCODE_SERVER_PASSWORD_ENV, options.userEnvironment, options.processEnv) ??
generatePassword()
return { username, password }
}
export function buildOpencodeBasicAuthHeader(params: { username?: string; password?: string }): string | undefined {
const username = params.username
const password = params.password