SKUSeer
Une boutique avec quatre cents références n'a pas besoin d'une équipe data, elle a besoin de savoir quoi réapprovisionner mardi. SKUSeer part d'un historique de ventes, prévoit la demande produit par produit, et place le tout derrière un modèle de permissions assez fin pour que le remplaçant du week-end voie les stocks sans pouvoir modifier les accès de qui que ce soit.
Moteur de prévision par Bilal Eddinaoui. Le modèle multi-tenant, le système de permissions, l'API et le travail de sécurité décrits ici sont les miens.
- 20
- permissions distinctes sur 7 domaines
- 13
- surfaces d'API, des imports aux prévisions
- 3
- services derrière un seul fournisseur d'identité
Ce que c'est
Trois services derrière une seule authentification : une API qui porte le modèle multi-tenant et les règles métier, un moteur de prévision qui transforme l'historique des ventes en demande prévue par produit, et une interface React. Keycloak se place devant comme fournisseur d'identité : l'authentification est déléguée plutôt que réimplémentée, et le rôle de l'API se réduit à faire confiance à un jeton vérifié.
Un tenant est un magasin. Tout ce qui se trouve en dessous — produits, catégories, historique de ventes, imports, exécutions de prévision, promotions, rapports, intégrations — appartient à ce magasin et reste invisible pour tous les autres.
Des rôles définis par le gérant, pas par le développeur
La plupart des logiciels pour petites structures livrent trois rôles figés en espérant que l'un convienne. Les commerces ne se découpent pas ainsi : le comptable a besoin des rapports mais pas de modifier le stock, le remplaçant du week-end du tableau de bord et de rien d'autre, l'associé du gérant de tout sauf du pouvoir d'exclure le gérant.
La primitive est donc la permission, pas le rôle. Vingt permissions, nommées d'après ce qu'elles autorisent — voir les produits, importer des produits, relancer un entraînement, planifier un rapport, gérer les membres, gérer les rôles — et un rôle est un ensemble nommé de permissions appartenant à un seul tenant. Deux magasins peuvent chacun avoir un rôle « Manager » signifiant autre chose, car la contrainte d'unicité porte sur le couple, pas sur le nom.
- Les permissions sont vérifiées à chaque requête contre l'appartenance de l'appelant au tenant visé : un jeton valide pour un magasin ne donne rien dans un autre.
- Le rôle d'administrateur de la plateforme vit dans le realm du fournisseur d'identité, pas dans un tenant — un gérant ne peut pas se l'attribuer.
Ce qu'une revue de sécurité a changé
C'est la partie du projet qui mérite lecture. L'examen du chemin d'authentification a révélé une véritable voie de prise de contrôle de compte : l'API créait une ligne utilisateur à la première connexion et rattachait une identité entrante à une ligne préexistante trouvée par adresse e-mail. Un attaquant enregistrant un compte non vérifié avec l'adresse d'un employé connu héritait de cette ligne et de ses appartenances aux tenants.
Le correctif : ne revendiquer une ligne préexistante que si le fournisseur atteste que l'e-mail est vérifié, ce qui déplace la décision de confiance vers le seul composant capable de la prendre. Deux failles voisines ont été fermées au passage : la revendication de partie autorisée du jeton n'était pas vérifiée, donc un jeton émis pour un autre client du même realm était accepté ; et l'export CSV écrivait sans échappement des valeurs contrôlées par l'utilisateur, permettant à un nom de produit forgé de s'exécuter comme formule à l'ouverture du fichier dans un tableur.
- Vérifier la partie autorisée et l'audience, pas seulement l'émetteur et la signature.
- Neutraliser les caractères de formule en tête à l'export — un tableur est un environnement d'exécution (CWE-1236).
- Plafonner le corps des requêtes avant l'analyse, pour refuser un envoi volumineux au lieu de le mettre en mémoire.
- Le compte de démonstration partagé est provisionné un cran sous administrateur : une démo publique ne peut ni supprimer le magasin ni modifier les rôles.
Pourquoi ce n'est pas une démo publique
Keycloak, Postgres, l'API et le moteur de prévision font quatre processus longue durée avec état persistant — largement au-delà d'une offre gratuite. Le déploiement est écrit et prêt : un reverse proxy qui termine le TLS, chaque service lié à localhost, Keycloak en mode production derrière des en-têtes transmis, et le realm généré au déploiement depuis le realm de développement versionné, afin que le client public ne fasse confiance qu'à une seule origine et que le compte de démonstration perde son rôle d'administrateur.
Générer le realm plutôt que maintenir deux fichiers était un choix délibéré. Deux copies d'une configuration d'identité divergent, et la divergence reste invisible jusqu'à devenir un incident de sécurité.
Réalisé avec
- FastAPI
- PostgreSQL
- Keycloak
- OIDC
- React
- Vite
- TanStack Query
- Docker Compose
Non hébergé : quatre services avec état derrière un fournisseur d'identité. Le déploiement de production est écrit mais nécessite un serveur.