diff --git a/config/sidebar.php b/config/sidebar.php index 4c14db7..9119b48 100644 --- a/config/sidebar.php +++ b/config/sidebar.php @@ -6,21 +6,46 @@ declare(strict_types=1); * Sidebar configuration. * * This file defines the sidebar sections displayed in the frontend. - * Each item references the module that owns it via the `module` key. - * Items whose module is not active (see config/modules.php) are filtered out. - * Items may also declare a `permission` key (RBAC permission code) : the item - * is hidden from users who do not hold that permission. + * + * Each SECTION may declare : + * - `label` (required) : i18n key resolved by the frontend + * - `icon` (required) : MDI icon name + * - `items` (required) : list of items (see below) + * - `permission` (opt.) : RBAC permission code ; when set, the whole + * section (and every one of its items) is hidden + * from users who do not hold that permission. + * Use this for "umbrella" sections like + * Administration where you want to gate the + * entire group behind one coarse permission. + * + * Each ITEM may declare : + * - `label` (required) : i18n key + * - `to` (required) : Nuxt route + * - `icon` (required) : MDI icon name + * - `module` (required) : owner module ID ; item is hidden if the + * module is not listed in config/modules.php + * - `permission` (opt.) : RBAC permission code ; finer-grained gate + * applied in addition to the section-level one + * + * Precedence : section-level `permission` is evaluated first. If it fails, + * the whole section is skipped and every item's `to` is added to the + * `disabledRoutes` payload of /api/sidebar (so the front middleware can + * redirect any direct navigation). Individual items without their own + * permission are implicitly protected by the section-level one. * * This config is decoupled from the modules themselves: you can freely * move an item from one section to another without touching the module code. - * - * Label keys are i18n keys resolved by the frontend (see frontend/i18n/locales/). */ return [ + // Section "Administration" : regroupe toutes les pages de configuration + // applicative (RBAC, users, sites, audit log). Gate implicite : si l'user + // n'a aucune des permissions item, la section se vide et disparait. + // Pour imposer un gate explicite (ex: "seuls les membres du groupe support + // voient l'administration"), ajouter ici : 'permission' => 'core.admin.access'. [ - 'label' => 'sidebar.general.section', - 'icon' => 'mdi:view-dashboard-outline', + 'label' => 'sidebar.administration.section', + 'icon' => 'mdi:cog-outline', 'items' => [ [ 'label' => 'sidebar.general.dashboard', @@ -28,12 +53,6 @@ return [ 'icon' => 'mdi:view-dashboard-outline', 'module' => 'core', ], - [ - 'label' => 'sidebar.general.admin', - 'to' => '/admin', - 'icon' => 'mdi:cog-outline', - 'module' => 'core', - ], [ 'label' => 'sidebar.core.roles', 'to' => '/admin/roles', @@ -62,12 +81,6 @@ return [ 'module' => 'core', 'permission' => 'core.audit_log.view', ], - [ - 'label' => 'sidebar.general.logout', - 'to' => '/logout', - 'icon' => 'mdi:logout', - 'module' => 'core', - ], ], ], [ @@ -82,4 +95,19 @@ return [ ], ], ], + // Section "Mon compte" : espace personnel. Accessible a tout user authentifie + // (aucune permission RBAC requise, tous les items restent dans `core` pour + // rester toujours presents meme quand les modules metier sont desactives). + [ + 'label' => 'sidebar.account.section', + 'icon' => 'mdi:account-circle-outline', + 'items' => [ + [ + 'label' => 'sidebar.account.logout', + 'to' => '/logout', + 'icon' => 'mdi:logout', + 'module' => 'core', + ], + ], + ], ]; diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index f85270b..6dbacb0 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -13,12 +13,16 @@ "actions": "Actions" }, "sidebar": { - "general": { - "section": "Général", - "dashboard": "Tableau de bord", - "admin": "Administration", + "administration": { + "section": "Administration" + }, + "account": { + "section": "Mon compte", "logout": "Déconnexion" }, + "general": { + "dashboard": "Tableau de bord" + }, "commercial": { "section": "Commercial", "suppliers": "Répertoire fournisseurs" diff --git a/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php b/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php index 8600ae9..4ba9392 100644 --- a/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php +++ b/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php @@ -17,7 +17,7 @@ class SidebarProvider implements ProviderInterface /** @var list */ private readonly array $activeModuleIds; - /** @var list}> */ + /** @var list}> */ private readonly array $sidebarConfig; public function __construct(private readonly Security $security) @@ -47,6 +47,23 @@ class SidebarProvider implements ProviderInterface $disabledRoutes = []; foreach ($this->sidebarConfig as $section) { + // Gate de section (optionnel) : si la section declare une permission + // et que l'utilisateur ne la possede pas, la section entiere est + // masquee. Toutes les routes de ses items basculent dans + // `disabledRoutes` pour que le middleware front redirige toute + // navigation directe, y compris si l'item n'a pas de permission + // individuelle (la section agit comme un umbrella gate). + $sectionPermission = $section['permission'] ?? null; + if (null !== $sectionPermission && !$this->security->isGranted($sectionPermission)) { + foreach ($section['items'] ?? [] as $item) { + if (isset($item['to'])) { + $disabledRoutes[] = $item['to']; + } + } + + continue; + } + $items = []; foreach ($section['items'] ?? [] as $item) { $isActive = in_array($item['module'] ?? null, $this->activeModuleIds, true);