# Rapport — Phase 3 : Backoffice Filament (contenu)

> Migration HR CONSULTING & CO (Next.js → Laravel). Branche : `phase-3` (basée sur `phase-2`).
> Date : 2026-06-07.

## 1. Résumé

| | |
|---|---|
| **Objectif** | Rendre tout le contenu éditable depuis `/admin` : Resources Filament (Témoignages, Clients/Logos, Services, Formations, Formateurs, Actualités), page **Paramètres**, et authentification/rôles admin. Focus ⭐ : éditeur **WYSIWYG riche** pour les Actualités (News). |
| **Livrable attendu** | « Tout le contenu éditable depuis `/admin` » (PLAN.md §Phase 3). |
| **Livrable atteint** | ✅ **Oui** — 7 Resources (21 routes) + 1 page Paramètres enregistrées et accessibles ; News avec TipTap ; 57 tests verts. |
| **Décisions appliquées** | Toutes arrêtées dans PLAN.md (Filament v3, Spatie Media Library pour les logos, éditeur TipTap pour News, purification HTML au rendu). Aucune décision structurante restée ouverte. |
| **Ajout d'implémentation** | Installation du plugin officiel **`filament/spatie-laravel-media-library-plugin`** (composant d'upload Filament branché sur la collection média `logo` du modèle `Client`, déjà déclarée). Cohérent avec la décision « Spatie Media Library » de PLAN.md. |
| **Écarts** | La **normalisation/recadrage automatique** des logos (conversion à dimension uniforme) et le **carrousel marquee** restent en **Phase 4** (conformément à PLAN.md). L'upload de logo et la collection média sont en place ici. |

## 2. Environnement & stack

- **PHP** 8.3.30 (Laragon) · **MySQL** 8.4.3 · **Node** 22.
- **Laravel 12.61** · **Filament v3.3** · **Livewire 3** · **awcodes/filament-tiptap-editor 3.5** (WYSIWYG) · **spatie/laravel-medialibrary 11** + **filament/spatie-laravel-media-library-plugin 3.3** · **mews/purifier** (anti-XSS).
- Tests : **PHPUnit 11** sur **SQLite `:memory:`**.

## 3. Travaux réalisés

### 3.1 Resources Filament (CRUD)
Toutes documentées (PHPDoc sur chaque méthode), labels et aides en français, organisées en **groupes de navigation** :

| Groupe | Resource | Points clés |
|---|---|---|
| **Contenu** | `NewsResource` ⭐ | Éditeur **TipTap** (profil riche : titres, listes, citations, liens, **images/médias**, tableaux, code, embeds), slug auto, catégorie (datalist), statut brouillon/publié, **date de publication planifiable**, **SEO** (meta title/description), auteur. Upload des images de l'éditeur vers le disque `public/news/content` (**jamais en base64**). Eager-loading de l'auteur (anti N+1). |
| **Contenu** | `ServiceResource` | Slug auto, icône Heroicon, accroche/description, image, ordre, activation. |
| **Contenu** | `FormationResource` | Slug auto, prix (optionnel = « sur devis »), image, **compteur de commandes** (`withCount`), ordre, activation. |
| **Contenu** | `InstructorResource` | Photo (avatar + éditeur d'image), bio, fonction, ordre, activation. |
| **Page d'accueil** | `TestimonialResource` | Auteur/fonction/entreprise, contenu, **note en étoiles** (1–5), avatar, publication, **réordonnancement** par glisser-déposer. |
| **Page d'accueil** | `ClientResource` | Upload **logo via Spatie Media Library** (collection `logo`, fichier unique), site web, ordre, activation. |
| **Réglages** | `UserResource` | Gestion des comptes admin : rôle `is_admin`, mot de passe haché (politique 10 car. lettres+chiffres), garde anti-verrouillage. |

Conventions communes : tables avec `defaultSort`, `reorderable('sort_order')` (contenus ordonnés), filtres ternaires actif/publié, colonnes `searchable/sortable/toggleable`.

### 3.2 Page Paramètres (`ManageSettings`)
Page Filament dédiée (groupe **Réglages**) chargeant/persistant la table `settings` via le modèle `Setting` (typage + cache gérés par le modèle). Formulaire en **onglets** :
- **Identité** : nom du site, slogan, copyright, présentation (footer).
- **Coordonnées** : adresse, e-mail, téléphones (`TagsInput`).
- **Destinataires e-mail** : contact, dépôt de CV, newsletter, formation.
- **Réseaux sociaux** : Twitter/X, YouTube, Instagram, LinkedIn.
- **Accueil** : chiffres clés et cibles d'audience (`Repeater` réordonnables).

### 3.3 Authentification & rôles
- Accès `/admin` réservé aux `is_admin` (gate `User::canAccessPanel`, déjà en place Phase 0), couvert par tests.
- `UserResource` permet de créer/gérer d'autres administrateurs (mot de passe haché, jamais réécrit si vide à l'édition).

### 3.4 Sécurité du rendu WYSIWYG (config/purifier.php)
La whitelist `mews/purifier` par défaut était **trop restrictive** (pas de `h1-h6`, `blockquote`, `table`, `code`…) : le contenu riche TipTap aurait été **supprimé au rendu**. Whitelist **enrichie** pour autoriser le contenu riche **tout en bloquant tout vecteur XSS** : titres, listes, citations, code, tableaux, images, alignement, et **iframes restreintes à YouTube/Vimeo en HTTPS** (`HTML.SafeIframe` + `URI.SafeIframeRegexp` ancré). `HTML.TargetBlank` force `rel="noopener noreferrer"`.

## 4. Tests fonctionnels

Vérification de l'enregistrement réel : `php artisan route:list --path=admin` → **21 routes de resources** (7 × index/create/edit) + **route page `manage-settings`**.

Suite automatisée `tests/Feature/AdminPanelResourcesTest.php` (Livewire + HTTP) :
- Contrôle d'accès : invité redirigé vers `/admin/login`, non-admin → 403, admin → 200 sur les 7 resources.
- CRUD : création témoignage, service, client, **actualité publiée (contenu riche)** ; validations (champs requis témoignage/actualité).
- Sécurité comptes : **mot de passe haché** (`Hash::check`), **mot de passe faible refusé**, mot de passe **requis à la création**, **auto-suppression masquée** sur son propre compte.
- Settings : chargement des valeurs existantes + persistance (e-mail destinataire, téléphones).
- News : **publication sans date → auto-datée** et éligible au scope public (corrige un échec silencieux).

## 5. Couverture de tests

| | |
|---|---|
| **Suite complète** | **57 tests · 196 assertions · 100 % verts** (0 échec, 0 risqué). |
| Nouveaux tests Phase 3 | **16** (`AdminPanelResourcesTest`). |
| Base (Phases 0–2) | 41 tests, toujours verts (aucune régression). |

> Couverture en % par module non mesurée : ni **Xdebug** ni **PCOV** ne sont activés dans l'environnement Laragon courant (`--coverage` indisponible). La couverture fonctionnelle est assurée par les scénarios ci-dessus (accès, CRUD, validation, sécurité). À activer en Phase 9 (CI).

## 6. Tests de sécurité (OWASP)

Approche : `composer audit` + **deux revues adversariales** (revue de code + audit sécurité OWASP) + checklist manuelle. *(Le serveur MCP Aikido n'était pas connecté à cette session — scan automatisé reporté ; couverture assurée par les revues adversariales et la checklist.)*

**Correctifs ÉLEVÉS appliqués avant commit :**
- **A03 / XSS persistant via SVG** : `image/svg+xml` **retiré** des types acceptés du logo client (un SVG servi en même origine peut embarquer du JS). Seuls JPG/PNG/WEBP sont admis.
- **A01 / verrouillage du backoffice** : garde anti-auto-suppression ajoutée sur la page d'édition utilisateur (pas seulement la table) ; **suppression de masse retirée** de la resource Users ; toggle `is_admin` **désactivé sur sa propre fiche** (anti auto-rétrogradation, non réhydraté donc inaltérable).

**Correctifs MOYENS/FAIBLES appliqués :**
- A04 — Reverse-tabnabbing : `HTML.TargetBlank => true` (rel noopener/noreferrer auto).
- A05 — `URI.SafeIframeRegexp` durci (HTTPS only, chemin ancré) ; profils purifier morts (`test`, `youtube`) supprimés.
- A07 — Politique de mot de passe : `Password::min(10)->letters()->numbers()`.
- Robustesse — N+1 auteur News corrigé ; échec silencieux « publié sans date » corrigé ; PHPDoc complétés sur toutes les pages (convention CLAUDE.md).

**Checklist OWASP — points vérifiés conformes :**
- **A01** : `/admin` réservé `is_admin` (testé) ; middlewares Filament complets (`EncryptCookies`, `AuthenticateSession`, **CSRF**, `Authenticate`).
- **A02** : hachage via le hasher Laravel ; pas de crypto maison ; secrets hors code.
- **A03** : rendu News purifié (`{!! clean() !!}`), whitelist sans `script`/`on*`/`style` dangereux ; sorties Blade échappées ailleurs ; ORM Eloquent paramétré (pas d'injection SQL).
- **A05/A06** : aucun secret en dur ; `composer audit` → **0 vulnérabilité**.
- **A07** : mot de passe haché, `$hidden`, non réécrit si vide à l'édition.
- **A08** : tous les `FileUpload` images contraints (`->image()` + `maxSize`) ; logo Spatie avec allowlist MIME ; SVG exclu.

**Risques résiduels (faible, à traiter ultérieurement) :** dé-promotion/suppression du **dernier** admin par un autre admin (garde « dernier admin » non implémentée — protections d'auto-action en place) ; pas de 2FA ni `uncompromised()` (HaveIBeenPwned) sur les mots de passe ; durcissement du service des médias (en-têtes/origine distincte) prévu avec R2.

## 7. Performance / scalabilité

- **Anti N+1** : `NewsResource::getEloquentQuery()` précharge `author` ; `FormationResource` utilise `withCount('orders')`.
- **Cache** : la page Paramètres s'appuie sur le cache des `settings` (invalidé à l'écriture par le modèle `Setting`).
- **Uploads** : images de l'éditeur stockées sur disque (pas de base64 en base) ; tailles maximales imposées.

## 8. Dette technique / TODO reportés

- **Phase 4** : normalisation/recadrage auto des logos (conversion Spatie à dimension uniforme, webp) + carrousel marquee alimenté.
- **Phase 9** : activer PCOV/Xdebug pour la mesure de couverture en CI ; envisager 2FA Filament + `Password::uncompromised()` ; garde « dernier admin » ; durcissement du service des médias (en-têtes `Content-Disposition`/CSP, origine R2).

## 9. Branche & PR

- **Branche** : `phase-3` (basée sur `phase-2`).
- **PR** : vers `master` — lien ajouté après ouverture.

> Note : des modifications **non liées à la Phase 3** (configuration **Mailpit** dans `.env.example`, `CLAUDE.md`, `PLAN.md`) étaient présentes dans l'arbre de travail au moment du commit ; elles ont été **volontairement exclues** de ce commit (travail tiers en cours).
