Files
tessera-ctl/apps/web/src/components/modules/module-access-gate.tsx
T
schalli c2ebc8daa0 feat(module-grants): Proxmox, Handelsware und DKV mit Freigabestufe Verwalten
- 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>
2026-10-02 13:31:08 +02:00

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')}
/>
);
}