Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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 :

MilestoneCe que ton back sait faire
1 · Il voitPOST /api/inventaire : une photo → un inventaire JSON vérifié
2 · Il réfléchitPOST /api/faisabilite : un inventaire → « sets constructibles + manquantes »
3 · Il a un visageaujourd'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.

Schéma : FRONT React · :5173 · on le construit · BACK Express · :3000 · M1 + M2 · photo / inventaire (fetch) · inventaire / faisabilité (JSON)

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) :

powershell
npm create vite@latest brickfinder-front -- --template react
cd brickfinder-front
npm install
npm run dev
Ce que tu dois voir
  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.

0 sur 8 dans cette étape

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

python
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.

0 sur 8 dans cette étape

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.

0 sur 8 dans cette étape

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é ».

python
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.

0 sur 8 dans cette étape

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 :

html
<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.

0 sur 8 dans cette étape

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.

0 sur 8 dans cette étape

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.

0 sur 8 dans cette étape

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.

0 sur 8 dans cette étape

Bravo — BrickFinder est complet

Tu as construit, de zéro, une application web full-stack, utile et sécurisée :

BriqueCe que tu sais faire
React / Vitecréer une app, des composants, du JSX
useStategérer l'état (fichier, résultats, chargement, erreur)
fetch / FormDataenvoyer une photo et appeler une API depuis le front
useEffectdé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

  1. Qu'est-ce qu'un composant React ? Qu'est-ce que le JSX ?
  2. À quoi sert useState ? Que se passe-t-il quand on change un état ?
  3. Quand s'exécute le code d'un useEffect(() => {...}, [inventaire]) ?
  4. C'est quoi l'erreur CORS et comment on l'a réglée ?
  5. 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ômeEssaie ça
Le front ne démarre pasnpm 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é backle 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 videas-tu importé des sets au catalogue (M2) ? l'inventaire est-il bien envoyé ?

Glossaire

MotDéfinition
Reactlibrairie d'interfaces web à base de composants
Viteoutil qui crée et lance un projet React
Composantune fonction qui renvoie un morceau d'interface
JSXdu HTML écrit dans du JavaScript
État (useState)une donnée mémorisée ; la changer re-dessine l'affichage
useEffectexécuter du code quand une donnée change
FormDatal'emballage pour envoyer un fichier par fetch
CORSrègle de sécurité du navigateur entre origines différentes
Middlewaredu code qui s'exécute avant une route (ex. contrôle d'accès)
OWASP A01Broken Access Control : actions sensibles mal protégées

Compétences mobilisées — référentiel BTS SIO SLAM

Séance 3 terminé ?

Coche chaque livrable quand tu l'as : c'est la preuve que l'étape est acquise.

livrables
–
quiz réussi
–
de série
–
Voir toutes les étapes du TP