Séance 3 sur 38 livrablesQuiz corrigé
BrickFinder Le front & la sécurité
Ton back voit (M1) et réfléchit (M2). Aujourd'hui on lui donne un visage : une interface React qui prend une photo et affiche « tu peux construire ça » — et on verrouille la sécurité.
Par Josselyn C., prof de BTS SIO. Publié le , mis à jour le .
- On construit
- un front React (upload photo → inventaire → faisabilité) + la sécurisation du back
- On installe
- React via Vite — pas à pas
- Pré-requis
- le back des Milestones 1 & 2 (il tourne sur http://localhost:3000)
Milestone 3 sur 3 — le dernier. À la fin : une appli complète, utilisable, et sécurisée. Une vraie réalisation portfolio E6.
Où on en est — le grand final
Récap de ton projet BrickFinder :
| Milestone | Ce que ton back sait faire |
|---|---|
| 1 · Il voit | POST /api/inventaire : une photo → un inventaire JSON vérifié |
| 2 · Il réfléchit | POST /api/faisabilite : un inventaire → « sets constructibles + manquantes » |
| 3 · Il a un visage | aujourd'hui : une interface React + la sécurité |
Jusqu'ici, tu testais avec Thunder Client. Un vrai utilisateur, lui, veut cliquer. On construit donc le front : une page où on dépose une photo, et où s'affichent les sets qu'on peut construire.
1. C'est quoi React ?
React est une librairie pour construire des interfaces web à base de composants. Un composant est un bout d'interface réutilisable (un bouton, une carte, toute une page) décrit par une fonction qui renvoie du JSX — du HTML écrit dans du JavaScript.
Créer l'app (avec Vite)
Vite est l'outil qui monte un projet React prêt à l'emploi. Dans un nouveau dossier (à côté de ton back) :
npm create vite@latest brickfinder-front -- --template react
cd brickfinder-front
npm install
npm run dev
VITE ready
➜ Local: http://localhost:5173/
Ouvre http://localhost:5173 : la page React d'exemple s'affiche. Ton front tourne !
Le composant de départ
Ouvre src/App.jsx. Remplace tout son contenu par le minimum :
function App() {
return (
<div>
<h1>BrickFinder</h1>
<p>Dépose une photo de tes briques.</p>
</div>
)
}
export default App
Flag A1
Checkpoint
Change le titre et le texte, ajoute un deuxième paragraphe. Rends : une capture de ta page modifiée dans le navigateur.
2. L'état : useState & l'upload
Une interface réagit. Pour se « souvenir » de ce que l'utilisateur fait (le fichier choisi, les résultats…), React utilise l'état : des variables spéciales créées avec useState. Quand l'état change, l'affichage se met à jour tout seul.
Choisir un fichier
import { useState } from "react"
function App() {
const [fichier, setFichier] = useState(null)
return (
<div>
<h1>BrickFinder</h1>
<input type="file" accept="image/*"
onChange={e => setFichier(e.target.files[0])} />
{fichier && <p>Photo prête : {fichier.name}</p>}
</div>
)
}
export default App
Choisis une photo : son nom s'affiche. L'état fonctionne.
Flag A2
Checkpoint
Ajoute un état message (useState("")) et un bouton qui, au clic, met message à « Analyse en cours… ». Affiche message. Rends : ton code + une capture après le clic.
3. Envoyer la photo au back
On relie le front au back du Milestone 1. Au clic sur un bouton, on envoie la photo à POST /api/inventaire et on récupère l'inventaire.
const [inventaire, setInventaire] = useState(null)
async function analyser() {
const fd = new FormData()
fd.append("photo", fichier)
const rep = await fetch("http://localhost:3000/api/inventaire", {
method: "POST",
body: fd
})
const data = await rep.json()
setInventaire(data.inventaire)
}
Ajoute le bouton dans le JSX : <button onClick={analyser}>Analyser</button>.
Flag B1
Production
Fais marcher la chaîne : choisir une photo → cliquer « Analyser » → l'inventaire revient. Affiche temporairement JSON.stringify(inventaire) dans la page. Rends : une capture montrant l'inventaire reçu dans le navigateur.
4. useEffect : enchaîner vers la faisabilité
Dès qu'on a l'inventaire, on veut automatiquement demander la faisabilité (Milestone 2). C'est le rôle de useEffect : exécuter du code quand une donnée change. Ici : « quand inventaire change, va chercher la faisabilité ».
import { useState, useEffect } from "react"
const [faisabilite, setFaisabilite] = useState([])
useEffect(() => {
if (!inventaire) return
fetch("http://localhost:3000/api/faisabilite", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ inventaire })
})
.then(rep => rep.json())
.then(data => setFaisabilite(data.resultats))
}, [inventaire])
Flag B2
Production
Affiche temporairement le nombre de résultats : <p>{faisabilite.length} sets évalués</p>. Analyse une photo et vérifie que ça se remplit. Rends : une capture.
5. Le moment waouh : les sets en image
C'est ici que ton appli devient bluffante. Pour chaque set, on affiche sa vraie photo (récupérée de Rebrickable en M2), une jauge de faisabilité animée, et une célébration quand tu peux le construire. On affiche la liste avec .map :
<div className="cartes">
{faisabilite.map(set => (
<div className={set.constructible ? "carte ok" : "carte"} key={set.setNum}>
<img src={set.image} alt={set.nom} />
<h3>{set.nom}</h3>
<div className="barre">
<div className="jauge" style={{ width: set.pourcentage + "%" }} />
</div>
<p className="pct">{set.pourcentage}%</p>
{set.constructible
? <p className="badge">✅ Tu peux le construire !</p>
: <p>Il te manque {set.manquantes.length} type(s) de pièce</p>}
</div>
))}
</div>
Le style qui fait tout (src/App.css)
Remplace le contenu de src/App.css par :
.cartes { display: grid; gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.carte { border: 1px solid #ddd; border-radius: 12px; padding: 12px;
text-align: center; transition: transform .2s; }
.carte:hover { transform: translateY(-4px); }
.carte img { width: 100%; height: 120px; object-fit: contain; }
.barre { background: #eee; border-radius: 99px; height: 10px; overflow: hidden; }
.jauge { background: #6a2d9c; height: 100%; transition: width .8s ease; }
.carte.ok { border-color: #10916f; box-shadow: 0 0 0 2px #10916f; }
.badge { color: #10916f; font-weight: bold; }
Gérer le chargement et les erreurs
Un appel réseau peut être lent ou échouer. On ajoute deux états :
const [chargement, setChargement] = useState(false)
const [erreur, setErreur] = useState("")
Flag B3
Production (l'appli marche !)
Affiche les cartes de faisabilité, avec un message « Analyse… » pendant le chargement. Rends : une capture de ton appli affichant au moins un set avec son pourcentage.
6. Sécuriser l'appli
Une appli qui marche, c'est bien. Une appli sécurisée, c'est ce qu'on attend d'un pro (et au cœur de l'E7 / OWASP). Trois chantiers concrets.
1 · La clé reste côté serveur
Vérifie-le : ta clé Claude est lue dans index.js (back) via process.env, et jamais écrite dans le front React. Le navigateur envoie une photo au back ; c'est le back qui parle à Claude avec la clé. Si la clé était dans le front, n'importe qui pourrait l'extraire (le code front est public).
2 · Valider ce qui entre (ne jamais faire confiance au client)
Un utilisateur pourrait envoyer un énorme fichier, ou pas une image. On limite multer, côté back :
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024 }, // 5 Mo max
fileFilter: (req, file, cb) => {
cb(null, file.mimetype.startsWith("image/")) // images seulement
}
})
3 · Protéger les actions sensibles (OWASP A01)
Souviens-toi : l'endpoint POST /api/catalogue/:setNum (M2) modifie la base. Laissé ouvert, n'importe qui pourrait polluer ton catalogue : c'est une faille de contrôle d'accès (OWASP A01). On exige un jeton admin :
function adminSeulement(req, res, next) {
if (req.headers["x-admin-token"] !== process.env.ADMIN_TOKEN) {
return res.status(403).json({ erreur: "Accès refusé" })
}
next()
}
app.post("/api/catalogue/:setNum", adminSeulement, async (req, res) => { /* ... */ })
Flag D1
Investigation / sécurité
Essaie d'appeler POST /api/catalogue/31147-1 sans l'en-tête x-admin-token (doit être refusé), puis avec le bon jeton (doit passer). Rends : les deux réponses + explique en 1 phrase quelle faille OWASP tu viens de corriger.
7. Finitions
Ton appli est complète et sécurisée. Quelques touches pour la rendre présentable (et défendable en E6).
Une touche finale
Tu as déjà le style waouh (Ch5). Ajoute un en-tête sympa au-dessus des cartes (« Avec tes pièces, tu peux construire… ») et, si tu veux, un petit confetti ou une animation quand au moins un set est constructible. L'œil doit comprendre en 2 secondes.
Afficher les pièces manquantes
Dans chaque carte non constructible, liste le détail : {set.manquantes.map(m => <li key={m.type+m.couleur}>{m.manque} × {m.type} {m.couleur}</li>)}.
Flag E1
Extension
Ajoute dans chaque carte la liste des pièces manquantes (type, couleur, quantité). Rends : une capture d'une carte « presque constructible » avec sa liste de manquantes.
Flag E2
Extension (bonus)
Ajoute un bouton « Trier par faisabilité » et un filtre « ne montrer que les constructibles ». Rends : une capture avant/après filtre, et dis quel état React contrôle le filtre.
Bravo — BrickFinder est complet
Tu as construit, de zéro, une application web full-stack, utile et sécurisée :
| Brique | Ce que tu sais faire |
|---|---|
| React / Vite | créer une app, des composants, du JSX |
| useState | gérer l'état (fichier, résultats, chargement, erreur) |
| fetch / FormData | envoyer une photo et appeler une API depuis le front |
| useEffect | déclencher un appel quand une donnée change (effets & API) |
| Affichage (.map) | transformer des données en interface (cartes, listes) |
| Sécurité | clé côté serveur, validation des entrées, contrôle d'accès (OWASP A01) |
Mini-quiz (vérifie-toi)
Exercice Réussi À revoir
- Qu'est-ce qu'un composant React ? Qu'est-ce que le JSX ?
- À quoi sert
useState? Que se passe-t-il quand on change un état ? - Quand s'exécute le code d'un
useEffect(() => {...}, [inventaire])? - C'est quoi l'erreur CORS et comment on l'a réglée ?
- Pourquoi la clé d'API ne doit JAMAIS être dans le front ?
Voir le corrigéMasquer le corrigé de l'exercice
1. Un composant = une fonction qui renvoie un bout d'interface ; le JSX = du HTML écrit dans du JavaScript.
2. À mémoriser une donnée qui peut changer ; quand on la change (via son set…), l'affichage se re-dessine tout seul.
3. À chaque fois que inventaire change (et au premier affichage).
4. Le navigateur bloque les appels entre origines différentes (:5173 → :3000) ; on l'a autorisé côté back avec le middleware cors().
5. Parce que le code front est public (visible par tous) : une clé dedans serait volée et utilisée à tes frais. Elle reste côté serveur.
Tu avais trouvé ?
Tu es bloqué ? La checklist
| Symptôme | Essaie ça |
|---|---|
| Le front ne démarre pas | npm install fait ? lancer depuis le dossier brickfinder-front avec npm run dev |
| « blocked by CORS policy » | le back a-t-il app.use(cors()) et a-t-il été relancé ? |
| « Failed to fetch » | le back tourne-t-il sur :3000 ? bonne URL dans le fetch ? |
req.file undefined côté back | le champ FormData doit s'appeler photo (comme upload.single("photo")) |
| « Each child should have a unique key » | ajoute key={...} unique sur les éléments d'un .map |
| La faisabilité reste vide | as-tu importé des sets au catalogue (M2) ? l'inventaire est-il bien envoyé ? |
Glossaire
| Mot | Définition |
|---|---|
| React | librairie d'interfaces web à base de composants |
| Vite | outil qui crée et lance un projet React |
| Composant | une fonction qui renvoie un morceau d'interface |
| JSX | du HTML écrit dans du JavaScript |
| État (useState) | une donnée mémorisée ; la changer re-dessine l'affichage |
| useEffect | exécuter du code quand une donnée change |
| FormData | l'emballage pour envoyer un fichier par fetch |
| CORS | règle de sécurité du navigateur entre origines différentes |
| Middleware | du code qui s'exécute avant une route (ex. contrôle d'accès) |
| OWASP A01 | Broken Access Control : actions sensibles mal protégées |