feat(quick-260907-e8k-01): gemeinsame 403-Komponente und ModuleAccessGate
Zieht das bisher inline in [category]/[moduleSlug]/page.tsx stehende 403-Markup in ModuleAccessDenied (uebersetzungsfrei, nimmt fertige Texte als Props) und legt mit ModuleAccessGate eine wiederverwendbare Server-Component-Pruefung an, die checkModuleAccess aufruft und bei jeder Ausnahme ebenfalls als "kein Zugriff" wertet (zweite Verteidigungslinie ueber das bereits geschlossen ausfallende checkModuleAccess, T-15-29). Vier Testfaelle decken Durchlassen, Verweigern, Ausnahme und Slug-Weitergabe ab. Bereitet Task 2 vor: die vier Modul-Layouts und die generische Route werden auf dieses Gate umgestellt (WINDOWS #10, PERM-04). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { checkModuleAccess } from '@/lib/module-access-actions';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import type { ReactNode } from 'react';
|
||||
import { ModuleAccessDenied } from './module-access-denied';
|
||||
|
||||
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) {
|
||||
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')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user