c2ebc8daa0
- Proxmox-Schreibwege und Handelsware-Einstellungen auf @ModuleManage umgestellt - DKV-Fleet: ganze Klasse Verwalten-Stufe, Benutzen allein bleibt ohne Zugriff - Metadaten-Test belegt umgestellte und bewusst Administratoren vorbehaltene Handler - Webseiten (Proxmox, Handelsware, Widget) folgen canManage, DKV-Zugriffsseite erklärt die Stufe Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { checkModuleAccess, getModuleAccessLevel } from '@/lib/module-access-actions';
|
|
import { getTranslations } from 'next-intl/server';
|
|
import type { ReactNode } from 'react';
|
|
import { ModuleAccessDenied } from './module-access-denied';
|
|
|
|
/**
|
|
* Module, die als Ganzes Verwalten-Stufe verlangen (261002-icv): die API
|
|
* traegt dort `@ModuleManage` auf der ganzen Klasse (DkvController). Benutzer
|
|
* mit nur "Benutzen" bekaemen von der API ohnehin 403 — die Seite zeigt ihnen
|
|
* stattdessen eine erklaerende Zugriffsseite.
|
|
*/
|
|
const MANAGE_ONLY_MODULE_SLUGS = new Set(['dkv-fleet']);
|
|
|
|
interface ModuleAccessGateProps {
|
|
moduleSlug: string;
|
|
children: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Reusable server-side access gate for module routes (D-07, PERM-04).
|
|
*
|
|
* Calls checkModuleAccess(moduleSlug) — the same ModuleAccessService
|
|
* resolution the sidebar and ModuleGuard use (D-01), no separate role
|
|
* logic in the frontend. Renders the children only when access resolves
|
|
* to explicitly `true`; every other outcome (denied, or the access check
|
|
* throwing) renders the shared 403 markup instead.
|
|
*
|
|
* checkModuleAccess already fails closed itself and does not throw
|
|
* (T-15-29) — the try/catch here is a second line of defense so a future
|
|
* change to that function cannot silently flip this gate open. The
|
|
* condition is deliberately "only pass through on explicit grant", never
|
|
* "only block on explicit denial".
|
|
*
|
|
* No redirect and no not-found: the 403 rendering here is the server
|
|
* response itself (D-07) — the user learns the module exists and they
|
|
* lack a grant, they aren't bounced elsewhere or left thinking it's
|
|
* missing.
|
|
*/
|
|
export async function ModuleAccessGate({ moduleSlug, children }: ModuleAccessGateProps) {
|
|
if (MANAGE_ONLY_MODULE_SLUGS.has(moduleSlug)) {
|
|
let level: 'none' | 'use' | 'manage' = 'none';
|
|
try {
|
|
level = await getModuleAccessLevel(moduleSlug);
|
|
} catch {
|
|
level = 'none';
|
|
}
|
|
if (level === 'manage') {
|
|
return children;
|
|
}
|
|
const tm = await getTranslations('modules');
|
|
return (
|
|
<ModuleAccessDenied
|
|
title={tm('accessDenied.title')}
|
|
body={level === 'use' ? tm('accessDenied.manageRequiredBody') : tm('accessDenied.body')}
|
|
backToDashboard={tm('accessDenied.backToDashboard')}
|
|
/>
|
|
);
|
|
}
|
|
|
|
let hasAccess = false;
|
|
|
|
try {
|
|
hasAccess = await checkModuleAccess(moduleSlug);
|
|
} catch {
|
|
hasAccess = false;
|
|
}
|
|
|
|
if (hasAccess === true) {
|
|
return children;
|
|
}
|
|
|
|
const t = await getTranslations('modules');
|
|
|
|
return (
|
|
<ModuleAccessDenied
|
|
title={t('accessDenied.title')}
|
|
body={t('accessDenied.body')}
|
|
backToDashboard={t('accessDenied.backToDashboard')}
|
|
/>
|
|
);
|
|
}
|