Make uds-auth optional: standalone local mode for cron.

Without udsAuth, trusted HTTP uses a synthetic __local__ viewer that sees all jobs; with uds-auth, keep multi-user empNo isolation and admin folders. Document that IM channels are not default UDS accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-08 17:39:54 +08:00
parent 1c504eb1fd
commit 933bad9201
8 changed files with 132 additions and 27 deletions

View file

@ -29,6 +29,8 @@ import {
claimUnassignedForViewer,
filterJobsForIdentity,
filterRunsForJobs,
isMultiUserIdentity,
localIdentity,
migrateJobOwners,
normalizeOwnerEmpNo,
UNASSIGNED_OWNER,
@ -151,16 +153,16 @@ async function requireIdentity(request, write, getUdsAuth) {
return null
}
const uds = typeof getUdsAuth === 'function' ? getUdsAuth() : null
// Soft dep: without uds-auth, run in standalone single-tenant mode.
if (!uds || typeof uds.resolveRequestIdentity !== 'function') {
write(503, { ok: false, error: 'auth_unavailable', message: 'uds-auth 未就绪,无法使用定时任务' })
return null
return localIdentity()
}
const identity = await resolveBrowserIdentity(request, getUdsAuth)
if (!identity?.empNo) {
write(401, { ok: false, error: 'login_required', message: '登录后才能使用定时任务' })
return null
}
return identity
return { ...identity, mode: 'multi' }
}
function sanitizeJobCwd(cwd, identity, getUdsAuth, { forOwnerEmpNo } = {}) {
@ -311,14 +313,17 @@ export function createHostService(options = {}) {
const t = now()
// Ignore client-supplied ids on create — otherwise POST/tools can overwrite.
const { id: _ignoredId, ownerEmpNo: _ignoreOwner, ...safeInput } = input && typeof input === 'object' ? input : {}
let ownerIdentity = identity?.empNo ? identity : inferIdentityFromJobInput(safeInput, getUdsAuth)
if (ownerIdentity?.empNo) {
let ownerIdentity = isMultiUserIdentity(identity)
? identity
: inferIdentityFromJobInput(safeInput, getUdsAuth)
if (isMultiUserIdentity(ownerIdentity)) {
safeInput.ownerEmpNo = ownerIdentity.empNo
safeInput.ownerDisplayName = ownerIdentity.displayName || ownerIdentity.empNo
safeInput.cwd = sanitizeJobCwd(safeInput.cwd, ownerIdentity, getUdsAuth, { forOwnerEmpNo: ownerIdentity.empNo })
} else if (safeInput.ownerEmpNo) {
safeInput.ownerEmpNo = normalizeOwnerEmpNo(safeInput.ownerEmpNo)
} else {
// Standalone / no inferred owner: leave unassigned (visible to everyone in local mode).
safeInput.ownerEmpNo = UNASSIGNED_OWNER
}
let created
@ -803,7 +808,7 @@ export function createHostService(options = {}) {
} catch (error) {
const code = error && error.code
if (code === 'NOT_FOUND') return write(404, { ok: false, error: error.message })
if (code === 'LOGIN_REQUIRED') return write(401, { ok: false, error: 'login_required', message: error.message })
write(401, { ok: false, error: 'login_required', message: '登录后才能使用定时任务' })
if (code === 'INVALID_CRON' || code === 'INVALID_AT' || code === 'INVALID_SCHEDULE' || code === 'INVALID_JOB' || code === 'INVALID_TIMEZONE' || code === 'INVALID_CWD') {
return write(400, { ok: false, error: error.message, code })
}