fix(#P7-P8): Statische Route-Permissions auf Backend-/Manifest-Wahrheit gestellt — /communication comm:read (Phantom communication:read entfernt), /mail/settings mail:config (Backend verlangt config), /import-export import_export:read (Core-Modul, kein Plugin — Kritik-Aussage korrigiert), /activity audit:read (Phantom activity:read entfernt, Seite nutzt Audit-API), /wiki wiki:read (vorher ungeschützt); Regressionstest routePermissions.test.ts 6/6; Gates: tsc 0, Production-Build exit 0

This commit is contained in:
Agent Zero
2026-08-27 20:04:18 +02:00
parent 88d96d4a49
commit f27f0474ef
2 changed files with 61 additions and 5 deletions
@@ -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');
});
});
+5 -5
View File
@@ -252,17 +252,17 @@ const router = createBrowserRouter([
{ path: '/dms/trash', element: <PermissionRoute permission="dms:read">{withSuspense(<DmsTrashPage />)}</PermissionRoute> },
{ path: '/trash', element: <PermissionRoute permission="contacts:read">{withSuspense(<TrashPage />)}</PermissionRoute> },
{ path: '/mail', element: <PermissionRoute permission="mail:read">{withSuspense(<MailPage />)}</PermissionRoute> },
{ path: '/mail/settings', element: <PermissionRoute permission="mail:read">{withSuspense(<MailSettingsPage />)}</PermissionRoute> },
{ path: '/mail/settings', element: <PermissionRoute permission="mail:config">{withSuspense(<MailSettingsPage />)}</PermissionRoute> },
{ path: '/reports', element: <PermissionRoute permission="reports:read">{withSuspense(<ReportsPage />)}</PermissionRoute> },
{ path: '/tasks', element: <PermissionRoute permission="tasks:read">{withSuspense(<TasksPage />)}</PermissionRoute> },
{ path: '/communication', element: <PermissionRoute permission="communication:read">{withSuspense(<CommunicationPage />)}</PermissionRoute> },
{ path: '/communication', element: <PermissionRoute permission="comm:read">{withSuspense(<CommunicationPage />)}</PermissionRoute> },
{ path: '/workflows', element: <PermissionRoute permission="workflows:read">{withSuspense(<WorkflowsPage />)}</PermissionRoute> },
{ path: '/contacts/dedup', element: <PermissionRoute permission="contacts:read">{withSuspense(<DedupMergePage />)}</PermissionRoute> },
{ path: '/import-export', element: <PermissionRoute permission="contacts:read">{withSuspense(<ImportExportPage />)}</PermissionRoute> },
{ path: '/import-export', element: <PermissionRoute permission="import_export:read">{withSuspense(<ImportExportPage />)}</PermissionRoute> },
{ path: 'tags', element: <PermissionRoute permission="tags:read">{withSuspense(<TagsPage />)}</PermissionRoute> },
{ path: '/api-docs', element: <PermissionRoute permission="settings:read">{withSuspense(<ApiDocsPage />)}</PermissionRoute> },
{ path: '/activity', element: <PermissionRoute permission="activity:read">{withSuspense(<ActivityTimelinePage />)}</PermissionRoute> },
{ path: '/wiki', element: withSuspense(<WikiPage />) },
{ path: '/activity', element: <PermissionRoute permission="audit:read">{withSuspense(<ActivityTimelinePage />)}</PermissionRoute> },
{ path: '/wiki', element: <PermissionRoute permission="wiki:read">{withSuspense(<WikiPage />)}</PermissionRoute> },
{ path: '/system-dashboard', element: withSuspense(<SystemDashboardPage />) },
{ path: '/profile', element: withSuspense(<SettingsProfilePage />) },
{ path: '*', element: <ErrorBoundary>{<PluginRouteRenderer />}</ErrorBoundary> },