Allow admin (and Host plugins) to create workspaces.

super_admin/fallback_admin already could; admin now has canCreateWorkspace, and Host-side creates without browser ALS (IM) are no longer blocked as forbidden.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-16 21:52:14 +08:00
parent 050b99426f
commit 89700ef4f9
6 changed files with 26 additions and 15 deletions

View file

@ -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.

View file

@ -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

View file

@ -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',

View file

@ -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 {

View file

@ -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",

View file

@ -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)
})
})