From f27f0474ef8e3fac5da42bd82a69d5cb2d37391c Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 27 Aug 2026 20:04:18 +0200 Subject: [PATCH] =?UTF-8?q?fix(#P7-P8):=20Statische=20Route-Permissions=20?= =?UTF-8?q?auf=20Backend-/Manifest-Wahrheit=20gestellt=20=E2=80=94=20/comm?= =?UTF-8?q?unication=20comm:read=20(Phantom=20communication:read=20entfern?= =?UTF-8?q?t),=20/mail/settings=20mail:config=20(Backend=20verlangt=20conf?= =?UTF-8?q?ig),=20/import-export=20import=5Fexport:read=20(Core-Modul,=20k?= =?UTF-8?q?ein=20Plugin=20=E2=80=94=20Kritik-Aussage=20korrigiert),=20/act?= =?UTF-8?q?ivity=20audit:read=20(Phantom=20activity:read=20entfernt,=20Sei?= =?UTF-8?q?te=20nutzt=20Audit-API),=20/wiki=20wiki:read=20(vorher=20ungesc?= =?UTF-8?q?h=C3=BCtzt);=20Regressionstest=20routePermissions.test.ts=206/6?= =?UTF-8?q?;=20Gates:=20tsc=200,=20Production-Build=20exit=200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/shell/routePermissions.test.ts | 56 +++++++++++++++++++ frontend/src/routes/index.tsx | 10 ++-- 2 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 frontend/src/__tests__/shell/routePermissions.test.ts diff --git a/frontend/src/__tests__/shell/routePermissions.test.ts b/frontend/src/__tests__/shell/routePermissions.test.ts new file mode 100644 index 0000000..373528b --- /dev/null +++ b/frontend/src/__tests__/shell/routePermissions.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * Welle 3 (Kritikpunkt 7/8): Statische Route-Permissions müssen die + * Backend-/Manifest-Wahrheit spiegeln. Beweist per Source-Inspektion: + * - /communication → comm:read (Backend verlangt comm:read; communication:read + * war eine Phantom-Permission, die nirgends registriert war) + * - /mail/settings → mail:config (Backend mail-settings verlangt mail:config) + * - /import-export → import_export:read (Backend verlangt import_export:read; + * vorher fälschlich contacts:read) + * - /activity → audit:read (Seite nutzt Audit-API; activity:read war Phantom) + * - /wiki → wiki:read (Backend verlangt wiki:read; vorher ungeschützt) + */ +const routerSource = readFileSync( + join(__dirname, '..', '..', 'routes', 'index.tsx'), + 'utf-8', +); + +function routePermission(path: string): string | null { + const marker = `path: '${path}'`; + const idx = routerSource.indexOf(marker); + if (idx === -1) return null; + const lineEnd = routerSource.indexOf('\n', idx); + const line = routerSource.slice(idx, lineEnd); + const match = line.match(/permission="([^"]+)"/); + return match ? match[1] : null; +} + +describe('static route permissions match backend/manifest truth', () => { + it('communication uses comm:read (registered permission)', () => { + expect(routePermission('/communication')).toBe('comm:read'); + }); + + it('mail/settings uses mail:config (backend requires mail:config)', () => { + expect(routePermission('/mail/settings')).toBe('mail:config'); + }); + + it('import-export uses import_export:read (backend requires it)', () => { + expect(routePermission('/import-export')).toBe('import_export:read'); + }); + + it('activity uses audit:read (page calls audit API)', () => { + expect(routePermission('/activity')).toBe('audit:read'); + }); + + it('wiki is protected with wiki:read (backend requires wiki:read)', () => { + expect(routePermission('/wiki')).toBe('wiki:read'); + }); + + it('phantom permissions are gone from the router', () => { + expect(routerSource).not.toContain('communication:read'); + expect(routerSource).not.toContain('activity:read'); + }); +}); diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx index ae7b13d..30593bf 100644 --- a/frontend/src/routes/index.tsx +++ b/frontend/src/routes/index.tsx @@ -252,17 +252,17 @@ const router = createBrowserRouter([ { path: '/dms/trash', element: {withSuspense()} }, { path: '/trash', element: {withSuspense()} }, { path: '/mail', element: {withSuspense()} }, - { path: '/mail/settings', element: {withSuspense()} }, + { path: '/mail/settings', element: {withSuspense()} }, { path: '/reports', element: {withSuspense()} }, { path: '/tasks', element: {withSuspense()} }, - { path: '/communication', element: {withSuspense()} }, + { path: '/communication', element: {withSuspense()} }, { path: '/workflows', element: {withSuspense()} }, { path: '/contacts/dedup', element: {withSuspense()} }, - { path: '/import-export', element: {withSuspense()} }, + { path: '/import-export', element: {withSuspense()} }, { path: 'tags', element: {withSuspense()} }, { path: '/api-docs', element: {withSuspense()} }, - { path: '/activity', element: {withSuspense()} }, - { path: '/wiki', element: withSuspense() }, + { path: '/activity', element: {withSuspense()} }, + { path: '/wiki', element: {withSuspense()} }, { path: '/system-dashboard', element: withSuspense() }, { path: '/profile', element: withSuspense() }, { path: '*', element: {} },