mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 06:43:17 +08:00
fix(uds-auth): isolate trusted Host calls from browser identity
This commit is contained in:
parent
3ba4461566
commit
fa4d5d6d97
7 changed files with 83 additions and 3 deletions
|
|
@ -10,6 +10,8 @@ dsh plugin --profile web add -w "D:/project/chatgpt/oclaw/uds-auth"
|
|||
|
||||
Restart Harness after install. The badge mounts on `sidebar.footer.action` (root scope).
|
||||
|
||||
Version `0.2.21` provides `ctx.get('udsAuth').runAsHost(callback)` for trusted Host plugins. It isolates UDS browser identity during synchronous and asynchronous background operations and restores the caller afterward. Use with `dsh-im-ops >= 4.9.1-ops.38` to fix WhatsApp background Session creation rejected as signed out; update both plugins and restart the Host. The callback adds no remote endpoint and preserves browser login and workspace ACL checks.
|
||||
|
||||
## Config
|
||||
|
||||
```yaml
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ dsh plugin --profile web add -w "D:/project/chatgpt/oclaw/uds-auth"
|
|||
|
||||
安装后重启 Harness。登录徽章在 `shell.overlay`(右上角);用户管理/部署配置在 **设置 → UDS 认证**(`settings.section`)。
|
||||
|
||||
`0.2.21` 提供仅供 Host 插件使用的 `ctx.get('udsAuth').runAsHost(callback)`:在回调及其异步操作内使用 Host 后台上下文,并恢复调用者的网页身份。配合 `dsh-im-ops >= 4.9.1-ops.38` 修复 WhatsApp 后台建会话误报“登录后才能创建会话”;更新两者后重启 Host。该方法不增加远程端点,网页登录及工作区 ACL 保持生效。
|
||||
|
||||
## 配置
|
||||
|
||||
在 **设置 → UDS 认证** 或 `cordis.patch.yml` 中修改:
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ export function runWithUserContext(userContext, fn) {
|
|||
return userContextStorage.run(userContext, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a trusted in-process Host operation without a browser identity.
|
||||
* Only Host plugins receive this callback API; it is not a remote endpoint.
|
||||
* @param {Function} fn
|
||||
* @returns {*} The operation's result, preserving synchronous and async calls.
|
||||
*/
|
||||
export function runAsHost(fn) {
|
||||
if (typeof fn !== 'function') throw new TypeError('Host operation must be a function')
|
||||
return userContextStorage.run(undefined, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist ALS for the rest of this async execution chain.
|
||||
* Needed for Gateway Remote stream mux: message handlers are sync but start
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
LOCAL_ADMIN_BOX_ENV,
|
||||
} from './local-admin.js'
|
||||
import { SessionBridgeStore } from './session-bridge.js'
|
||||
import { runAsHost } from './context.js'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const require = createRequire(import.meta.url)
|
||||
|
|
@ -1267,6 +1268,7 @@ async function initServices(ctx, config) {
|
|||
}
|
||||
|
||||
const udsAuth = {
|
||||
runAsHost,
|
||||
async resolveRequestIdentity(req) {
|
||||
const identity = await resolveIdentity(req)
|
||||
if (!identity?.empNo) return null
|
||||
|
|
|
|||
4
uds-auth/package-lock.json
generated
4
uds-auth/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "uds-auth",
|
||||
"version": "0.2.20",
|
||||
"version": "0.2.21",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "uds-auth",
|
||||
"version": "0.2.20",
|
||||
"version": "0.2.21",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"qrcode": "1.5.4"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uds-auth",
|
||||
"version": "0.2.20",
|
||||
"version": "0.2.21",
|
||||
"description": "UDS authentication plugin for DeepSeek Harness with multi-tenant session isolation",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
|
|
|
|||
63
uds-auth/test/host-context.test.js
Normal file
63
uds-auth/test/host-context.test.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { getUserContext, runAsHost, withUserContext } from '../lib/context.js'
|
||||
import { installDshAcl } from '../lib/dsh-acl.js'
|
||||
|
||||
test('Host callback context is isolated across concurrent browser identities and exceptions', async () => {
|
||||
const identities = [null, { empNo: 'u1' }, { empNo: 'u2' }]
|
||||
await Promise.all(identities.map(identity => withUserContext(identity, async () => {
|
||||
assert.equal(await runAsHost(async () => {
|
||||
await Promise.resolve()
|
||||
assert.equal(getUserContext(), undefined)
|
||||
return 'host-result'
|
||||
}), 'host-result')
|
||||
assert.equal(getUserContext(), identity)
|
||||
assert.throws(() => runAsHost(() => { throw new Error('failed') }), /failed/)
|
||||
assert.equal(getUserContext(), identity)
|
||||
})))
|
||||
assert.equal(runAsHost(() => 42), 42)
|
||||
assert.throws(() => runAsHost(null), TypeError)
|
||||
})
|
||||
|
||||
test('trusted Host creation works from browser ALS while unauthenticated browser creation stays denied', async () => {
|
||||
let creations = 0
|
||||
const owners = []
|
||||
const controller = {
|
||||
async list() { return { items: [] } },
|
||||
async search() { return { items: [] } },
|
||||
async create(request) {
|
||||
creations += 1
|
||||
return { sessionId: `session-${creations}`, request }
|
||||
},
|
||||
}
|
||||
installDshAcl({
|
||||
on() { return () => {} },
|
||||
get() { return undefined },
|
||||
inject(names, callback) {
|
||||
if (names[0] === 'sessionController') callback({ sessionController: controller })
|
||||
},
|
||||
}, {
|
||||
sessionAcl: { getOwner() { return null }, setOwner: (...args) => owners.push(args) },
|
||||
userWorkspaces: { isUserPath: (empNo, path) => path === `/ws/${empNo}`, get() { return null } },
|
||||
getWorkspaceRoot: () => '/ws',
|
||||
getWorkspaceRegistry: () => undefined,
|
||||
})
|
||||
await withUserContext(null, async () => {
|
||||
await assert.rejects(controller.create({ cwd: '/bot' }), error => (
|
||||
error.details.udsError === 'login_required_create_session'
|
||||
))
|
||||
const result = await runAsHost(() => controller.create({ cwd: '/bot', agentPreset: 'netxops' }))
|
||||
assert.deepEqual(result.request, { cwd: '/bot', agentPreset: 'netxops' })
|
||||
assert.equal(getUserContext(), null)
|
||||
await assert.rejects(controller.create({ cwd: '/bot' }))
|
||||
})
|
||||
assert.equal(creations, 1)
|
||||
assert.deepEqual(owners, [], 'background sessions must not inherit a browser owner')
|
||||
await withUserContext({ empNo: 'u1', permissions: {} }, async () => {
|
||||
await assert.rejects(controller.create({ cwd: '/other' }), error => (
|
||||
error.details.udsError === 'session_workspace_only'
|
||||
))
|
||||
await controller.create({ cwd: '/ws/u1' })
|
||||
})
|
||||
assert.deepEqual(owners, [['session-2', 'u1']])
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue