From f7a264ca93093f280462ec826d9d34f8aa702fc3 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Fri, 15 May 2026 12:15:26 +0800 Subject: [PATCH] fix(rbac): hide UI mutation surfaces for Viewer / non-creator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several mutation entry points stayed visible to users who lacked the backend-required role, so Viewers (and non-creator Contributors) hit 403 on click instead of seeing the disabled state. Surgical fixes, all matching the existing backend matrix: - KnowledgeBaseList: gate the entire 3-dot popup (both the 'all' and 'mine' tabs) behind canManageKBCard. Pin/unpin used to be unguarded; settings/delete were per-item gated but the trigger still showed. Now non-managers don't see the popup at all. - AgentList: the popup trigger now requires at least one menu item to be visible (edit OR copy OR disable OR delete), via a compound v-if on the popup itself. Disable/enable on own custom agents and shared agents both gain a hasRole('admin') guard to match POST /shared-agents/disabled, which writes a tenant-level preference and is Admin-gated. - AgentEditorModal: new readOnly prop hides the '确定' (save) button and replaces 'Cancel' with 'Close'. AgentList passes readOnly when the user can't manage the agent (built-in agents and others' custom agents for Viewers). Form-input disabling is intentionally NOT wired here yet — the modal has 3000+ lines of inputs and the only mutation surface that 403s is the save button. Hiding it removes the broken path without rewiring the whole form. - doc-content: generated-question delete button gated by a new canEditKB prop driven by KnowledgeBase.vue's existing canEdit computed (KB creator OR Admin OR org-share-edit). Defaults to Admin-only when the prop is missing — fail-closed since the backend matrix is now stricter (see follow-up commit). The server still enforces the same matrix on every mutation; these gates only stop the user from staring at a 'failed' toast. --- frontend/src/components/doc-content.vue | 21 +++++++++++++++---- frontend/src/views/agent/AgentEditorModal.vue | 10 +++++++-- frontend/src/views/agent/AgentList.vue | 12 ++++++----- .../src/views/knowledge/KnowledgeBase.vue | 2 +- .../src/views/knowledge/KnowledgeBaseList.vue | 6 ++++-- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/doc-content.vue b/frontend/src/components/doc-content.vue index 6ea7bb2a..b8d8f991 100644 --- a/frontend/src/components/doc-content.vue +++ b/frontend/src/components/doc-content.vue @@ -13,9 +13,21 @@ import { MessagePlugin, DialogPlugin } from "tdesign-vue-next"; import { sanitizeHTML, safeMarkdownToHTML, createSafeImage, isValidImageURL, hydrateProtectedFileImages, isValidURL } from '@/utils/security'; import { openMermaidFullscreen } from '@/utils/mermaidViewer'; import { useI18n } from 'vue-i18n'; +import { useAuthStore } from '@/stores/auth'; import DocumentPreview from '@/components/document-preview.vue'; const { t } = useI18n(); +const authStore = useAuthStore(); + +// canDeleteGeneratedQuestion 对应后端 DELETE /chunks/by-id/:id/questions +// 的 OwnedChunkKBOrAdminFromChunkID 守卫——KB 创建者或租户 Admin+ +// 才允许删除。父组件 KnowledgeBase.vue 通过 :canEditKB 把 KB 级权限 +// 传下来(包含 KB creator / Admin / 组织分享 editor 三种来源),未 +// 传时按更严格的 Admin 兜底,避免 Viewer 看到一个会 403 的入口。 +const canDeleteGeneratedQuestion = computed(() => { + if (props.canEditKB === true) return true; + return authStore.hasRole('admin'); +}); // Mermaid 初始化计数器,用于生成唯一ID let mermaidRenderCount = 0; @@ -48,7 +60,7 @@ mermaid.initialize({ topPadding: 50 } }); -const props = defineProps(["visible", "details", "knowledgeType", "sourceInfo"]); +const props = defineProps(["visible", "details", "knowledgeType", "sourceInfo", "canEditKB"]); const emit = defineEmits(["closeDoc", "getDoc", "questionDeleted"]); marked.use({ @@ -955,9 +967,10 @@ const handleDetailsScroll = () => { > {{ question.question }} - @@ -1458,6 +1458,12 @@ const props = defineProps<{ mode: 'create' | 'edit'; agent?: CustomAgent | null; initialSection?: string; + // readOnly hides the save button so a Viewer who clicks an agent + // card to inspect its config doesn't see a "确定" that 403s on the + // backend update endpoint. Field-level disable is intentionally NOT + // wired here yet (the modal has 3000+ lines of form inputs); instead + // we just remove the only mutation surface — the footer button. + readOnly?: boolean; }>(); const emit = defineEmits<{ diff --git a/frontend/src/views/agent/AgentList.vue b/frontend/src/views/agent/AgentList.vue index 435642d7..f29c8a49 100644 --- a/frontend/src/views/agent/AgentList.vue +++ b/frontend/src/views/agent/AgentList.vue @@ -89,7 +89,7 @@ {{ agent.name }} - {{ $t('common.copy') }} -