Fix uds-auth settings save on frozen DSH config objects.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-09 22:38:37 +08:00
parent fd0df9b890
commit e48b8bd082

View file

@ -121,6 +121,11 @@ function mergeConfig(custom = {}) {
return { ...CONFIG_DEFAULTS, ...custom } return { ...CONFIG_DEFAULTS, ...custom }
} }
/** DSH/schemastery config objects may be frozen — always clone before mutate. */
function asMutableConfig(config) {
return { ...(config && typeof config === 'object' ? config : {}) }
}
// Store references // Store references
let _sessionStore = null let _sessionStore = null
let _authMiddleware = null let _authMiddleware = null
@ -523,7 +528,7 @@ async function saveRuntimeConfig(partial) {
err.code = 'config_not_ready' err.code = 'config_not_ready'
throw err throw err
} }
// 只允许修改 4 个可配置字段 // 只允许修改可配置字段;写到新对象上(host 传入的 config 可能是只读的)
const allowed = [ const allowed = [
'uacBaseUrl', 'uacBaseUrl',
'userSearchUrl', 'userSearchUrl',
@ -534,17 +539,19 @@ async function saveRuntimeConfig(partial) {
'skillCredentialTtlSeconds', 'skillCredentialTtlSeconds',
'outboundAllowedHosts', 'outboundAllowedHosts',
] ]
const next = asMutableConfig(_currentConfig)
for (const k of allowed) { for (const k of allowed) {
if (partial[k] !== undefined) { if (partial[k] !== undefined) {
if (k === 'retainSkillCredentialsOnLogout') { if (k === 'retainSkillCredentialsOnLogout') {
_currentConfig[k] = partial[k] === true || partial[k] === 'true' next[k] = partial[k] === true || partial[k] === 'true'
} else if (k === 'skillCredentialTtlSeconds') { } else if (k === 'skillCredentialTtlSeconds') {
_currentConfig[k] = Number(partial[k]) || CONFIG_DEFAULTS.skillCredentialTtlSeconds next[k] = Number(partial[k]) || CONFIG_DEFAULTS.skillCredentialTtlSeconds
} else { } else {
_currentConfig[k] = typeof partial[k] === 'string' ? partial[k] : String(partial[k]) next[k] = typeof partial[k] === 'string' ? partial[k] : String(partial[k])
} }
} }
} }
_currentConfig = next
// 写运行时配置文件(不覆盖原始 config.default.yaml) // 写运行时配置文件(不覆盖原始 config.default.yaml)
try { try {
await writeFile(RUNTIME_CONFIG_FILE, JSON.stringify(_currentConfig, null, 2), 'utf-8') await writeFile(RUNTIME_CONFIG_FILE, JSON.stringify(_currentConfig, null, 2), 'utf-8')
@ -747,7 +754,7 @@ function installSettingsSection(ctx, entry, hooks) {
const hooksArg = { const hooksArg = {
setSource: (current) => { setSource: (current) => {
hooks.setSource?.(current) hooks.setSource?.(current)
_currentConfig = current _currentConfig = asMutableConfig(current)
}, },
onChange: () => { onChange: () => {
hooks.onChange?.() hooks.onChange?.()
@ -838,7 +845,7 @@ async function rebindPendingUserWorkspaces() {
export async function apply(ctx, config = {}) { export async function apply(ctx, config = {}) {
let source = () => mergeConfig(config) let source = () => mergeConfig(config)
_currentConfig = source() _currentConfig = asMutableConfig(source())
_logger = ctx.logger || console _logger = ctx.logger || console
ctx.logger?.info?.('[uds-auth] Loading...') ctx.logger?.info?.('[uds-auth] Loading...')
@ -846,10 +853,10 @@ export async function apply(ctx, config = {}) {
installSettingsSection(ctx, source(), { installSettingsSection(ctx, source(), {
setSource: (current) => { setSource: (current) => {
source = typeof current === 'function' ? current : () => current source = typeof current === 'function' ? current : () => current
_currentConfig = source() _currentConfig = asMutableConfig(source())
}, },
onChange: () => { onChange: () => {
_currentConfig = source() _currentConfig = asMutableConfig(source())
ctx.logger?.info?.('[uds-auth] settings updated') ctx.logger?.info?.('[uds-auth] settings updated')
} }
}) })
@ -879,15 +886,13 @@ async function initServices(ctx, config) {
const { createAgentAuthHandlers } = await import('./agent-auth.js') const { createAgentAuthHandlers } = await import('./agent-auth.js')
_pluginCtx = ctx _pluginCtx = ctx
_currentConfig = config _currentConfig = asMutableConfig(config)
try { try {
const raw = await readFile(RUNTIME_CONFIG_FILE, 'utf-8') const raw = await readFile(RUNTIME_CONFIG_FILE, 'utf-8')
const rt = JSON.parse(raw) const rt = JSON.parse(raw)
if (rt && typeof rt === 'object') { if (rt && typeof rt === 'object') {
for (const k of Object.keys(rt)) { _currentConfig = { ..._currentConfig, ...rt }
_currentConfig[k] = rt[k]
}
} }
} catch { /* runtime config optional */ } } catch { /* runtime config optional */ }