oclaw/uds-auth/test/agent-auth-owner.test.js
oliver f0b4b6cd74 fix(uds-auth): explain why skills get no credentials for a session (0.3.18)
Skill credentials follow the DSH session owner, so a chat created under
the emergency account stays owned by administrator after a QR scan and
skills cannot get a UAC token there. The generic "scan QR first" error
hid this.

- Return session_owner_no_uac when the owner is administrator / a local
  account, telling the user to start a new session after QR sign-in.
- Name the owner in no_skill_credentials for UAC owners.
- Audit denied outbound calls with session id and owner.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 18:52:31 +08:00

53 lines
2 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createAgentAuthHandlers } from '../lib/agent-auth.js'
const makeRes = () => ({
statusCode: 0, headers: {}, body: '',
setHeader(k, v) { this.headers[k] = v },
end(s) { this.body = s },
})
const loopbackReq = (payload) => ({
method: 'POST',
socket: { remoteAddress: '127.0.0.1' },
headers: { 'content-type': 'application/json', 'x-dsh-session-id': 's1' },
async *[Symbol.asyncIterator]() { yield Buffer.from(JSON.stringify(payload)) },
})
function handlersFor(owner, creds) {
return createAgentAuthHandlers({
resolveCredentialsForSession: async () => creds,
getSessionOwner: () => owner,
resolveCallerSession: async () => ({ empNo: owner }),
outboundHosts: ['icenterapi.zte.com.cn'],
})
}
test('skill outbound on an emergency-owned session names the owner and the way out', async () => {
const res = makeRes()
await handlersFor('administrator', null).handleOutbound(
loopbackReq({ url: 'https://icenterapi.zte.com.cn/x' }), res)
assert.equal(res.statusCode, 401)
const body = JSON.parse(res.body)
assert.equal(body.error, 'session_owner_no_uac')
assert.match(body.message, /administrator/)
assert.doesNotMatch(body.message, /\{owner\}/)
})
test('skill outbound for a UAC owner with no cached token keeps no_skill_credentials, with owner', async () => {
const res = makeRes()
await handlersFor('10329667', null).handleOutbound(
loopbackReq({ url: 'https://icenterapi.zte.com.cn/x' }), res)
assert.equal(res.statusCode, 401)
const body = JSON.parse(res.body)
assert.equal(body.error, 'no_skill_credentials')
assert.match(body.message, /10329667/)
})
test('credentials never cross owners: another account\'s token is refused', async () => {
const res = makeRes()
await handlersFor('10329667', { empNo: '99999999', token: 't' }).handleOutbound(
loopbackReq({ url: 'https://icenterapi.zte.com.cn/x' }), res)
assert.equal(res.statusCode, 401)
assert.equal(JSON.parse(res.body).error, 'no_skill_credentials')
})