mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 06:50:48 +08:00
Emergency (fallback / sealed-box) admins lost the workspace sidebar and saw "sign-in failed / add API key" on DSH Desktop because the remote.mux opens anonymously at boot and every early call was rejected terminally. - gateway: anonymous workspace/follow reaches dsh-acl's empty baseline; anonymous streams are parked until the carrier aborts instead of failing; anonymous workspace/initializeDefault answers "nothing created" - request-auth: a dead UDS_SESSION cookie no longer shadows a valid WS ticket or bridge (sync + async variants used by all callers) - dsh-acl: /api/* waits (bounded 15s) for the gateway ACL during Host startup instead of 403, so the Desktop welcome read no longer fails - desktop-bootstrap: key/account projection is best-effort per reference - client: emit connection/reset after login so boot-time caches (settings describe mirror -> Settings > Models) re-read under the real principal - sessions: absolute lifetime fixed at 7 days (was min(ttl, 7d) = 30 min); bridge expiry slides with its session; MemoryStore persisted to sessions.json (bearer hash only, userData AES-256-GCM sealed) - README: session lifetime/persistence and DSH compatibility notes Also includes previously uncommitted uds-auth 0.3.x work in this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
3.5 KiB
JavaScript
69 lines
3.5 KiB
JavaScript
// Compatibility with the unmodified DSH Desktop welcome RPC sequence. These
|
|
// projections contain no account attempt, real credential reference, or config.
|
|
export const DESKTOP_BOOTSTRAP_REF = 'UDS_AUTH_DESKTOP_PROVIDER_CONFIGURED'
|
|
const namespace = 'uds-auth-desktop-bootstrap'
|
|
const endpoints = new Set(['settings/describe', 'llm/listConfigurableProviders', 'credentials/describe', 'account/getState'])
|
|
|
|
export function isDesktopBootstrapEndpoint(descriptor) {
|
|
return !descriptor.mode && endpoints.has(`${descriptor.namespace}/${descriptor.method}`)
|
|
}
|
|
|
|
function view(ns, value) {
|
|
return { ns, value, autoGenerate: false, schema: { type: 'object', dict: {} },
|
|
applies: 'live', secrets: [], revision: 0 }
|
|
}
|
|
|
|
/** Read only Host-owned metadata; request arguments can never select a secret. */
|
|
export function createDesktopBootstrap({ getService, deny }) {
|
|
const settings = () => getService('settings')?.describe?.({ redactSecrets: true }) || []
|
|
// Every read is best-effort: one failing provider or reference must not fail the
|
|
// whole welcome read (Desktop treats a failed read as "no API key").
|
|
const configured = async () => {
|
|
let rows = []
|
|
try { rows = settings() } catch { rows = [] }
|
|
const refs = new Set()
|
|
const official = rows.find(row => row.ns === 'llm-deepseek')?.value?.apiKeyEnv
|
|
if (typeof official === 'string') refs.add(official)
|
|
let providers = []
|
|
try { providers = getService('llm')?.listConfigurableProviders?.() || [] } catch { providers = [] }
|
|
for (const provider of providers) {
|
|
let value = rows.find(row => row.ns === provider?.settingsNs)?.value
|
|
for (const key of provider?.settingsPath || []) value = value && Object.hasOwn(value, key) ? value[key] : undefined
|
|
if (typeof value?.apiKeyEnv === 'string') refs.add(value.apiKeyEnv)
|
|
}
|
|
const credentials = getService('credentials')
|
|
for (const ref of refs) {
|
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(ref)) continue
|
|
try {
|
|
if ((await credentials?.describe?.(ref))?.configured === true) return true
|
|
} catch { /* try the next reference */ }
|
|
}
|
|
return false
|
|
}
|
|
return async request => {
|
|
const endpoint = `${request.namespace}/${request.method}`
|
|
switch (endpoint) {
|
|
case 'settings/describe': {
|
|
const preference = settings().find(row => row.ns === 'locale')?.value?.preference
|
|
return { writable: false, hasDocument: false, namespaces: [
|
|
view('locale', { ...(typeof preference === 'string' && /^[A-Za-z-]{2,16}$/.test(preference) ? { preference } : {}) }),
|
|
view(namespace, { apiKeyEnv: DESKTOP_BOOTSTRAP_REF }),
|
|
] }
|
|
}
|
|
case 'llm/listConfigurableProviders':
|
|
return [{ provider: namespace, displayName: 'Host provider', settingsNs: namespace, settingsPath: [] }]
|
|
case 'credentials/describe': {
|
|
const refs = request.args?.refs
|
|
if (!Array.isArray(refs) || refs.length > 1 || refs.some(ref => ref !== DESKTOP_BOOTSTRAP_REF)) deny('forbidden_settings')
|
|
return refs.length ? { [DESKTOP_BOOTSTRAP_REF]: { configured: await configured(), writable: false } } : {}
|
|
}
|
|
case 'account/getState': {
|
|
let state
|
|
try { state = await getService('deepseekAccount')?.getState?.() } catch { state = undefined }
|
|
return { status: state?.status === 'credential-stored' ? 'credential-stored' : 'signed-out', attempt: null,
|
|
links: { usageUrl: 'https://platform.deepseek.com/usage', topUpUrl: 'https://platform.deepseek.com/top_up' } }
|
|
}
|
|
default: return deny('login_required')
|
|
}
|
|
}
|
|
}
|