mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-16 03:10:47 +08:00
Fix workspace visibility: registry inject and WebSocket UDS identity.
This commit is contained in:
parent
ee0a49f381
commit
a283b5cdc6
1 changed files with 136 additions and 9 deletions
|
|
@ -3,9 +3,9 @@
|
||||||
*/
|
*/
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import { withUserContext, getUserContext, runWithUserContext } from './context.js'
|
import { withUserContext, getUserContext, runWithUserContext } from './context.js'
|
||||||
|
import { computePermissions, ROLES } from './roles.js'
|
||||||
|
|
||||||
const require = createRequire(import.meta.url)
|
const require = createRequire(import.meta.url)
|
||||||
import { computePermissions, ROLES } from './roles.js'
|
|
||||||
|
|
||||||
function parseCookie(header, name) {
|
function parseCookie(header, name) {
|
||||||
if (!header || typeof header !== 'string') return null
|
if (!header || typeof header !== 'string') return null
|
||||||
|
|
@ -55,13 +55,142 @@ function bindWebSocketListenersToIdentity(ws, getIdentity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Keep UDS identity on remote.mux WebSocket messages (ALS dies after upgrade). */
|
||||||
* remote.mux streams fire on WebSocket messages after upgrade returns — ALS is gone.
|
function patchWebSocketServerForUdsIdentity(resolveIdentity) {
|
||||||
* Resolve UDS identity from the upgrade request (cookies) on each connection.
|
if (patchWebSocketServerForUdsIdentity.done) return
|
||||||
*/
|
patchWebSocketServerForUdsIdentity.done = true
|
||||||
function patchWebSocketServerForUdsIdentity(resolveIdentity)
|
let WebSocketServer
|
||||||
|
try {
|
||||||
|
const ws = require('ws')
|
||||||
|
WebSocketServer = ws.WebSocketServer || ws.Server
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!WebSocketServer?.prototype?.handleUpgrade) return
|
||||||
|
const orig = WebSocketServer.prototype.handleUpgrade
|
||||||
|
WebSocketServer.prototype.handleUpgrade = function udsAuthHandleUpgrade(req, socket, head, cb) {
|
||||||
|
const getIdentity = () => {
|
||||||
|
if (req && Object.prototype.hasOwnProperty.call(req, '__udsAuthIdentity')) {
|
||||||
|
return req.__udsAuthIdentity
|
||||||
|
}
|
||||||
|
if (socket && upgradeSocketIdentity.has(socket)) {
|
||||||
|
return upgradeSocketIdentity.get(socket)
|
||||||
|
}
|
||||||
|
if (typeof resolveIdentity !== 'function') return null
|
||||||
|
return Promise.resolve(resolveIdentity(req)).then((identity) => {
|
||||||
|
try { if (req) req.__udsAuthIdentity = identity } catch { /* ignore */ }
|
||||||
|
if (socket) upgradeSocketIdentity.set(socket, identity)
|
||||||
|
return identity
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const wrappedCb = typeof cb === 'function'
|
||||||
|
? (wsSocket) => {
|
||||||
|
bindWebSocketListenersToIdentity(wsSocket, getIdentity)
|
||||||
|
return cb(wsSocket)
|
||||||
|
}
|
||||||
|
: cb
|
||||||
|
return orig.call(this, req, socket, head, wrappedCb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* wrap existing upgrades */
|
export async function resolveIdentityFromRequest(req, deps) {
|
||||||
|
const cookie = req?.headers?.cookie || ''
|
||||||
|
const empNo = parseCookie(cookie, 'PORTALSSOUser')
|
||||||
|
|| parseCookie(cookie, 'ZTEDPGSSOUser')
|
||||||
|
|| parseCookie(cookie, 'UDS_FALLBACK_USER')
|
||||||
|
if (!empNo) return null
|
||||||
|
|
||||||
|
const { sessionStore, rolesStore } = deps
|
||||||
|
let userContext = null
|
||||||
|
try {
|
||||||
|
userContext = await sessionStore.get(empNo)
|
||||||
|
} catch {
|
||||||
|
userContext = null
|
||||||
|
}
|
||||||
|
if (!userContext) return null
|
||||||
|
|
||||||
|
// bootstrap / resolve role
|
||||||
|
let role = rolesStore.getRole(empNo)
|
||||||
|
if (empNo !== 'administrator' && typeof rolesStore.bootstrapFirstUser === 'function') {
|
||||||
|
try {
|
||||||
|
const r = await rolesStore.bootstrapFirstUser(empNo)
|
||||||
|
role = r.role
|
||||||
|
} catch { /* keep getRole */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = computePermissions(role)
|
||||||
|
return {
|
||||||
|
empNo: String(empNo),
|
||||||
|
role,
|
||||||
|
permissions,
|
||||||
|
userContext,
|
||||||
|
kind: empNo === 'administrator' ? 'fallback' : 'uds',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch webServer so every route handler runs inside UDS ALS.
|
||||||
|
* @param {any} server
|
||||||
|
* @param {(req: any) => Promise<object|null>} resolveIdentity
|
||||||
|
*/
|
||||||
|
export function patchWebServerWithIdentity(server, resolveIdentity) {
|
||||||
|
if (!server || server.__udsAuthPatched) return () => {}
|
||||||
|
server.__udsAuthPatched = true
|
||||||
|
|
||||||
|
const wrap = (handler) => {
|
||||||
|
if (typeof handler !== 'function' || handler.__udsWrapped) return handler
|
||||||
|
const wrapped = async (req, res, ...rest) => {
|
||||||
|
const identity = await resolveIdentity(req)
|
||||||
|
try {
|
||||||
|
const pathname = new URL(req.url || '/', 'http://x').pathname
|
||||||
|
if (
|
||||||
|
pathname.startsWith('/dsh-ops-cron')
|
||||||
|
&& pathname !== '/dsh-ops-cron/health'
|
||||||
|
&& !identity?.empNo
|
||||||
|
) {
|
||||||
|
res.writeHead(401, { 'content-type': 'application/json; charset=utf-8' })
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
ok: false,
|
||||||
|
error: 'login_required',
|
||||||
|
message: '登录后才能使用定时任务',
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch { /* fall through to handler */ }
|
||||||
|
return withUserContext(identity, () => handler(req, res, ...rest))
|
||||||
|
}
|
||||||
|
wrapped.__udsWrapped = true
|
||||||
|
return wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
const patchTable = (table) => {
|
||||||
|
if (!table || typeof table.entries !== 'function') return
|
||||||
|
for (const [path, route] of table.entries()) {
|
||||||
|
if (!route?.handler) continue
|
||||||
|
table.set(path, { ...route, handler: wrap(route.handler) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patchTable(server.exact)
|
||||||
|
patchTable(server.prefixes)
|
||||||
|
if (typeof server.fallback === 'function') {
|
||||||
|
server.fallback = wrap(server.fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
const origRegister = server.register.bind(server)
|
||||||
|
server.register = (route) => origRegister({
|
||||||
|
...route,
|
||||||
|
handler: wrap(route.handler),
|
||||||
|
})
|
||||||
|
|
||||||
|
const origFallback = server.registerFallback?.bind(server)
|
||||||
|
if (origFallback) {
|
||||||
|
server.registerFallback = (handler) => origFallback(wrap(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
patchWebSocketServerForUdsIdentity(resolveIdentity)
|
||||||
|
|
||||||
|
// Wrap upgrades already registered (gateway may load before us).
|
||||||
try {
|
try {
|
||||||
const table = server.upgrades
|
const table = server.upgrades
|
||||||
if (table && typeof table.entries === 'function') {
|
if (table && typeof table.entries === 'function') {
|
||||||
|
|
@ -93,8 +222,6 @@ function patchWebSocketServerForUdsIdentity(resolveIdentity)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
/* leave patched — reload recreates webServer fiber */
|
/* leave patched — reload recreates webServer fiber */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue