oclaw/uds-auth/lib/task-environment.js

63 lines
2.9 KiB
JavaScript

/** Host shellEnv adapter. Values are rebuilt after ordinary environment entries. */
export function createTaskEnvironment({ getOwner, rolesStore, capabilities, getWebServer, allowRawCredentials }) {
const issued = new Map()
const release = execution => {
const key = execution?.token || execution
const row = issued.get(key)
if (!row) return
capabilities.revoke(row.id)
row.signal?.removeEventListener('abort', row.abort)
clearTimeout(row.timer)
row.off?.()
issued.delete(key)
}
const contributor = {
name: 'uds-auth',
variables: {
DSH_UDS_TASK_CAPABILITY: { description: 'Short-lived session-bound UDS capability; never print or persist.' },
DSH_UDS_AUTH_BASE: { description: 'Owned loopback uds-auth endpoint of this Host.' },
},
resolve(execution) {
release(execution)
const empty = { DSH_UDS_TASK_CAPABILITY: '', DSH_UDS_AUTH_BASE: '' }
const sid = execution?.agent?.session?.header?.id
const owner = sid ? getOwner(String(sid)) : null
const server = getWebServer()
const port = Number(server?.port)
if (!owner || rolesStore.isDisabled(owner) || !Number.isInteger(port) || port <= 0 || port > 65535
|| execution?.signal?.aborted) return empty
const scopes = typeof allowRawCredentials === 'function' && allowRawCredentials()
? ['outbound', 'credentials']
: ['outbound']
const cap = capabilities.mint({
empNo: owner, dshSessionId: String(sid), audience: 'uds-auth-agent',
scopes, ttlSeconds: 600, mintedBy: 'host:shellEnv',
})
const row = { id: cap.id, sessionId: String(sid), signal: execution.signal, abort: () => release(execution) }
issued.set(execution.token || execution, row)
row.signal?.addEventListener('abort', row.abort, { once: true })
row.timer = setTimeout(() => release(execution), cap.expiresAt - Date.now())
row.timer.unref?.()
return { DSH_UDS_TASK_CAPABILITY: cap.token, DSH_UDS_AUTH_BASE: `http://127.0.0.1:${port}/uds-auth` }
},
}
return { contributor, release, finish(execution, result, jobs) {
const row = issued.get(execution?.token || execution)
const value = result?.value
if (row && !result?.isError && ['background', 'promoted'].includes(value?.kind)
&& typeof value.jobId === 'string' && jobs?.events?.subscribe) {
const job = jobs.list(row.sessionId).find(job => job.id === value.jobId)
if (job && ['running', 'stopping'].includes(job.status)) {
row.signal?.removeEventListener('abort', row.abort)
row.signal = null
row.off = jobs.events.subscribe({ owner: row.sessionId }, event => {
if (event.job?.id === value.jobId && ['settled', 'stopping', 'removed'].includes(event.type)) release(execution)
})
return
}
}
release(execution)
}, dispose() {
for (const key of [...issued.keys()]) release({ token: key })
} }
}