From b2b24df82e51839416ff5e8c330158f39942e7af Mon Sep 17 00:00:00 2001 From: veypi Date: Wed, 18 Feb 2026 00:15:38 +0800 Subject: [PATCH] refactor(ui): Rebuild permission system in vbase.js with scope support - Add scope parameter to VBase constructor for multi-tenant support - Replace hasPermission with checkPerm, checkPermOnResource, checkPermAny, checkPermAll - Implement _isAdmin check for global wildcard permissions (*:*) - Add _matchPermission with wildcard support (resource:*, *:*) - Remove default 404 page from vrouter in root.html --- ui/root.html | 4 +- ui/vbase.js | 110 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 11 deletions(-) diff --git a/ui/root.html b/ui/root.html index e6736e7..53cb882 100644 --- a/ui/root.html +++ b/ui/root.html @@ -13,9 +13,7 @@ - - - + diff --git a/ui/vbase.js b/ui/vbase.js index f95990c..f5d80a4 100644 --- a/ui/vbase.js +++ b/ui/vbase.js @@ -1,7 +1,11 @@ class VBase { - constructor(baseURL) { - this.baseURL = baseURL || ''; + constructor(baseURL, scope) { + if (!baseURL) throw new Error('VBase: baseURL is required'); + if (!scope) throw new Error('VBase: scope is required'); + + this.baseURL = baseURL; + this.scope = scope; this.tokenKey = 'vbase_access_token'; this.refreshTokenKey = 'vbase_refresh_token'; this.userKey = 'vbase_user_info'; @@ -140,18 +144,108 @@ class VBase { return headers; } + // 检查是否有全局管理员权限 (*:* 或 scope:*:*) + _isAdmin() { + const perms = this.user?.permissions || []; + for (const p of perms) { + const permID = p.permission_id || p; + const resourceID = p.resource_id || '*'; + // 必须是全局权限 (resource_id 为 * 或空) 且是通配符权限 + if (resourceID === '*' && (permID === '*:*' || permID === `${this.scope}:*:*`)) { + return true; + } + } + return false; + } + // Permission Check - hasPermission(permission) { + // 基础权限检查 (permissionID 格式: "resource:action") + checkPerm(permissionID) { if (!this.user) return false; - if (this.user.is_admin) return true; - if (!permission) return true; - const userPerms = this.user.permissions || []; - return userPerms.includes(permission); + if (!permissionID) return true; + // 全局管理员直接通过 + if (this._isAdmin()) return true; + + const perms = this.user.permissions || []; + for (const p of perms) { + if (this._matchPermission(p.permission_id || p, permissionID)) { + return true; + } + } + return false; + } + + // 检查对特定资源的权限 + // permissionID: "resource:action" + // resourceID: 资源实例ID + checkPermOnResource(permissionID, resourceID) { + if (!this.user) return false; + if (!permissionID) return true; + // 全局管理员直接通过 + if (this._isAdmin()) return true; + if (!resourceID) return this.checkPerm(permissionID); + + const perms = this.user.permissions || []; + for (const p of perms) { + const permID = p.permission_id || p; + const permResourceID = p.resource_id || '*'; + + if (this._matchPermission(permID, permissionID)) { + if (permResourceID === '*' || permResourceID === resourceID) { + return true; + } + } + } + return false; + } + + // 满足任一权限即可 + checkPermAny(...permissionIDs) { + for (const pid of permissionIDs) { + if (this.checkPerm(pid)) return true; + } + return false; + } + + // 满足所有权限 + checkPermAll(...permissionIDs) { + for (const pid of permissionIDs) { + if (!this.checkPerm(pid)) return false; + } + return true; + } + + // 内部方法: 权限匹配 + // 支持通配符: *:*, resource:* + // 后端权限可能是 "scope:resource:action" 或 "resource:action" + _matchPermission(storedPerm, wantPerm) { + if (storedPerm === wantPerm) return true; + if (storedPerm === '*:*') return true; + + // 从后端存储的权限中提取 resource:action 部分 + let havePerm = storedPerm; + const haveParts = storedPerm.split(':'); + if (haveParts.length === 3) { + // scope:resource:action -> resource:action + havePerm = `${haveParts[1]}:${haveParts[2]}`; + } + + if (havePerm === wantPerm) return true; + + // 通配符匹配 (resource:* 匹配 resource:any_action) + const [haveRes, haveAct] = havePerm.split(':'); + const [wantRes, wantAct] = wantPerm.split(':'); + + if (haveRes === '*' || (haveRes === wantRes && (haveAct === '*' || haveAct === wantAct))) { + return true; + } + return false; } hasRole(role) { if (!this.user) return false; - if (this.user.is_admin) return true; + // 全局管理员拥有所有角色 + if (this._isAdmin()) return true; const userRoles = this.user.roles || []; return userRoles.includes(role); }