diff --git a/uds-auth/lib/client.js b/uds-auth/lib/client.js index 5aa5b50a..1e2a970d 100644 --- a/uds-auth/lib/client.js +++ b/uds-auth/lib/client.js @@ -100,7 +100,7 @@ window.__ModuleLoader__.load({ "ui.networkError": "网络错误...", "ui.qrGenerateFailed": "生成二维码失败", "ui.cronLoginRequired": "登录后才能使用定时任务", - "ui.workspaceCreateForbidden": "只有超级管理员可以创建工作区", + "ui.workspaceCreateForbidden": "只有管理员可以创建工作区", "ui.workspaceLoginRequired": "登录后才能使用工作区", "err.not_logged_in": "未登录", "err.forbidden_settings": "当前账号无设置权限", @@ -138,7 +138,7 @@ window.__ModuleLoader__.load({ "err.session_forbidden": "无权访问该会话", "err.session_workspace_only": "只能在自己的工作区创建会话", "err.workspace_path_only": "只能打开自己的工作区路径", - "err.workspace_create_forbidden": "只有超级管理员可以创建工作区", + "err.workspace_create_forbidden": "只有管理员可以创建工作区", "err.no_skill_credentials": "请先完成 UAC 扫码登录", "err.loopback_only_credentials": "agent-credentials 仅允许本机访问", "err.loopback_only_outbound": "outbound 仅允许本机访问", @@ -240,7 +240,7 @@ window.__ModuleLoader__.load({ "ui.networkError": "Network error...", "ui.qrGenerateFailed": "Failed to generate QR", "ui.cronLoginRequired": "Sign in to use scheduled tasks", - "ui.workspaceCreateForbidden": "Only super admins can create workspaces", + "ui.workspaceCreateForbidden": "Only admins can create workspaces", "ui.workspaceLoginRequired": "Sign in to use workspaces", "err.not_logged_in": "Not signed in", "err.forbidden_settings": "No settings permission", @@ -278,7 +278,7 @@ window.__ModuleLoader__.load({ "err.session_forbidden": "No access to this session", "err.session_workspace_only": "Sessions can only be created in your own workspace", "err.workspace_path_only": "You can only open your own workspace path", - "err.workspace_create_forbidden": "Only super admins can create workspaces", + "err.workspace_create_forbidden": "Only admins can create workspaces", "err.no_skill_credentials": "Complete UAC QR sign-in first", "err.loopback_only_credentials": "agent-credentials is loopback-only", "err.loopback_only_outbound": "outbound is loopback-only", @@ -1852,7 +1852,7 @@ function reloadAfterLogin() { ctx.effect(() => { - // Open/choose workspace is super_admin-only (canCreateWorkspace). + // Open/choose workspace: admin / super_admin / fallback_admin (canCreateWorkspace). // Everyone else uses the auto-provisioned per-user workspace and must not open the picker. const CHOOSER = hostAriaSel('chooseWorkspace') // Inert composer: onClick lives on the card (cardWorkspaceTrigger), not the labeled node. diff --git a/uds-auth/lib/dsh-acl.js b/uds-auth/lib/dsh-acl.js index fd7553c2..d0289353 100644 --- a/uds-auth/lib/dsh-acl.js +++ b/uds-auth/lib/dsh-acl.js @@ -929,7 +929,10 @@ ctx.inject(['workspaceController'], (wctx) => { wc.create = async (request) => { const identity = getUserContext() if (identity?._internalProvision) return origCreate(request) - if (!identity?.permissions?.canCreateWorkspace) { + // No ALS identity: Host-side plugins (IM / cron) create workspaces without a + // browser login. Authenticated browser calls always run under user context; + // there admin / super_admin / fallback_admin must have canCreateWorkspace. + if (identity && !identity.permissions?.canCreateWorkspace) { throwForbidden('workspace_create_forbidden') } return origCreate(request) @@ -1060,7 +1063,7 @@ ctx.inject(['workspaceController'], (wctx) => { } }) - // directory picker: only super_admin may pick/create dirs (others get auto workspaces) + // directory picker: admin / super_admin / fallback_admin (canCreateWorkspace) ctx.inject(['directoryPicker'], (dctx) => { const dp = dctx.directoryPicker if (!dp || dp.__udsAcl) return diff --git a/uds-auth/lib/i18n.js b/uds-auth/lib/i18n.js index 0fc376c4..38d10d42 100644 --- a/uds-auth/lib/i18n.js +++ b/uds-auth/lib/i18n.js @@ -101,7 +101,7 @@ export const MESSAGES = { // client gates 'ui.cronLoginRequired': '登录后才能使用定时任务', - 'ui.workspaceCreateForbidden': '只有超级管理员可以创建工作区', + 'ui.workspaceCreateForbidden': '只有管理员可以创建工作区', 'ui.workspaceLoginRequired': '登录后才能使用工作区', // API / ACL errors (stable codes) @@ -137,7 +137,7 @@ export const MESSAGES = { 'err.session_forbidden': '无权访问该会话', 'err.session_workspace_only': '只能在自己的工作区创建会话', 'err.workspace_path_only': '只能打开自己的工作区路径', - 'err.workspace_create_forbidden': '只有超级管理员可以创建工作区', + 'err.workspace_create_forbidden': '只有管理员可以创建工作区', 'err.no_skill_credentials': '请先完成 UAC 扫码登录', 'err.loopback_only_credentials': 'agent-credentials 仅允许本机访问', 'err.loopback_only_outbound': 'outbound 仅允许本机访问', @@ -250,7 +250,7 @@ export const MESSAGES = { 'ui.qrGenerateFailed': 'Failed to generate QR', 'ui.cronLoginRequired': 'Sign in to use scheduled tasks', - 'ui.workspaceCreateForbidden': 'Only super admins can create workspaces', + 'ui.workspaceCreateForbidden': 'Only admins can create workspaces', 'ui.workspaceLoginRequired': 'Sign in to use workspaces', 'err.not_logged_in': 'Not signed in', @@ -285,7 +285,7 @@ export const MESSAGES = { 'err.session_forbidden': 'No access to this session', 'err.session_workspace_only': 'Sessions can only be created in your own workspace', 'err.workspace_path_only': 'You can only open your own workspace path', - 'err.workspace_create_forbidden': 'Only super admins can create workspaces', + 'err.workspace_create_forbidden': 'Only admins can create workspaces', 'err.no_skill_credentials': 'Complete UAC QR sign-in first', 'err.loopback_only_credentials': 'agent-credentials is loopback-only', 'err.loopback_only_outbound': 'outbound is loopback-only', diff --git a/uds-auth/lib/roles.js b/uds-auth/lib/roles.js index 5226cd4f..ffb96a7b 100644 --- a/uds-auth/lib/roles.js +++ b/uds-auth/lib/roles.js @@ -4,11 +4,12 @@ * 角色: * super_admin 所有权限 + 用户管理;默认可见全部会话(可在设置中关闭) * fallback_admin 等同 super_admin(兜底 administrator) - * admin 无设置齿轮;仅看自己会话(含 @);可见渠道/系统会话 + * admin 无设置齿轮;仅看自己会话(含 @);可见渠道/系统会话;可创建工作区 * user 仅看自己会话,无设置 * * 超管/应急默认全览开启;prefs.viewAllSessions === false 时关闭。 * 设置齿轮仅超管/应急(canAccessSettings)。 + * 创建工作区:super_admin / fallback_admin / admin(扫码不可用时现场通常只有 admin)。 * 持久化: roles.json (roles + prefs + fallbackPasswordHash) */ import { createHash, randomBytes } from 'node:crypto' @@ -61,12 +62,12 @@ export function computePermissions(role, opts = {}) { case ROLES.ADMIN: return { canManageUsers: false, - // 设置齿轮仅超管/应急;admin 仍可看渠道/系统会话 + // 设置齿轮仅超管/应急;admin 仍可看渠道/系统会话,并可创建工作区 canAccessSettings: false, canToggleViewAllSessions: false, canViewAllSessions: false, canViewSystemSessions: true, - canCreateWorkspace: false, + canCreateWorkspace: true, } default: // user / undefined return { diff --git a/uds-auth/package.json b/uds-auth/package.json index 9059dd20..fcc408f8 100644 --- a/uds-auth/package.json +++ b/uds-auth/package.json @@ -1,6 +1,6 @@ { "name": "uds-auth", - "version": "0.2.3", + "version": "0.2.4", "description": "UDS authentication plugin for DeepSeek Harness with multi-tenant session isolation", "type": "module", "main": "lib/index.js", diff --git a/uds-auth/test/session-access.test.js b/uds-auth/test/session-access.test.js index 75d1474a..49bf6c89 100644 --- a/uds-auth/test/session-access.test.js +++ b/uds-auth/test/session-access.test.js @@ -192,4 +192,11 @@ describe('RolesStore view-all prefs', () => { assert.throws(() => store.setViewAllSessions('u1', true), (err) => err.code === 'forbidden_view_all_sessions') assert.throws(() => store.setViewAllSessions('a1', true), (err) => err.code === 'forbidden_view_all_sessions') }) + + it('lets admin, super_admin, and fallback_admin create workspaces', () => { + assert.equal(computePermissions(ROLES.SUPER_ADMIN).canCreateWorkspace, true) + assert.equal(computePermissions(ROLES.FALLBACK_ADMIN).canCreateWorkspace, true) + assert.equal(computePermissions(ROLES.ADMIN).canCreateWorkspace, true) + assert.equal(computePermissions(ROLES.USER).canCreateWorkspace, false) + }) })