Aller au contenu

API — Administration

Cette page documente AdminController et son helper AdminAuth, montés sous /api/admin. Tous les endpoints de cette page requièrent une authentification admin — soit par cookie de session (après POST /api/admin/login), soit par header X-Admin-Token.

Contrôleurs couverts

  • AdminController → login/logout admin, gestion produits, utilisateurs, commandes, comptes admin, upload image
  • AdminAuth (helper statique) → centralise la vérification d'auth admin (isAdmin(session, token))

Le portail admin caché (/api/admin/portal/*) est documenté à part dans admin-portal.md.


Mécanisme d'authentification admin

Tous les endpoints de AdminController (hors login et status) sont protégés par la même vérification :

// Vérifie : session cookie OU header X-Admin-Token
if (!isAdmin(session, token)) return unauthorized();

Session cookie :

  1. POST /api/admin/login → stocke admin_logged_in = true dans la HttpSession
  2. Requêtes suivantes → Spring transmet automatiquement JSESSIONID
  3. AdminAuth.isAdmin() lit la clé de session

Header X-Admin-Token :

  1. POST /api/admin/login → retourne aussi un adminToken UUID dans le body
  2. Le token est stocké en mémoire dans AdminController.VALID_ADMIN_TOKENS (ConcurrentHashMap)
  3. Requêtes suivantes → le client envoie X-Admin-Token: <uuid>
  4. AdminAuth.isAdmin() vérifie la présence du token dans la map

Tokens non persistants

La map VALID_ADMIN_TOKENS est réinitialisée à chaque redémarrage du backend. C'est intentionnel — comportement pédagogique pour les démonstrations de chaos mémoire (heap dump). En environnement de formation, une coupure du backend force tous les admins à se reconnecter.


SuperAdmin vs Admin ordinaire

PerfShop distingue deux niveaux d'administrateur :

  • Superadmin (flag isSuperAdmin = true dans admin_users) : accès complet, y compris la gestion des comptes admin. Un seul superadmin existe par défaut, créé par le seed initial.
  • Admin ordinaire : accès limité selon ses droits granulaires (canAccessChaos, canAccessMonitoring, canAccessAdmin, canAccessJmeter, canAccessScripts).

Les endpoints marqués 👑 dans les tableaux ci-dessous sont réservés au superadmin — ils retournent 403 Forbidden avec le message Réservé au superadmin. pour un admin ordinaire.


Vue d'ensemble

Authentification

Méthode Endpoint Auth Description
POST /api/admin/login Aucune Login admin (BCrypt sur admin_users)
POST /api/admin/logout Session ou token Détruit la session et invalide le token
GET /api/admin/status Aucune (lecture) Statut d'authentification

Profil admin

Méthode Endpoint Auth Description
GET /api/admin/accounts/me Admin Profil du compte connecté

Gestion des comptes admin

Méthode Endpoint Auth Description
GET /api/admin/accounts Superadmin 👑 Liste de tous les comptes admin
POST /api/admin/accounts Superadmin 👑 Créer un compte admin
DELETE /api/admin/accounts/{id} Superadmin 👑 Supprimer un compte admin
PUT /api/admin/accounts/{id}/password Admin (soi-même) ou Superadmin 👑 Changer un mot de passe
PUT /api/admin/accounts/{id}/rights Superadmin 👑 Modifier les droits d'accès

Produits

Méthode Endpoint Auth Description
GET /api/admin/products Admin Liste des produits non pédagogiques
POST /api/admin/products Admin Créer un produit
PUT /api/admin/products/{id} Admin Modifier un produit
DELETE /api/admin/products/{id} Admin Supprimer un produit
POST /api/admin/products/{id}/image Admin Upload d'image produit (multipart)

Utilisateurs

Méthode Endpoint Auth Description
GET /api/admin/users Admin Liste des utilisateurs avec compteur de commandes
POST /api/admin/users Admin Créer un utilisateur
DELETE /api/admin/users/{id} Admin Supprimer un utilisateur (et ses commandes)

Commandes

Méthode Endpoint Auth Description
GET /api/admin/orders Admin Liste de toutes les commandes
DELETE /api/admin/orders/{id} Admin Supprimer une commande

Authentification admin

POST /api/admin/login

Auth : aucune (c'est l'entrée) Service : AdminUserService.authenticate() (BCrypt contre table admin_users)

Requête

{
  "email": "admin@perfshop.fr",
  "password": "admin"
}

Réponse — 200 OK

{
  "success": true,
  "adminToken": "550e8400-e29b-41d4-a716-446655440000",
  "email": "admin@perfshop.fr",
  "isSuperAdmin": true,
  "canAccessChaos": true,
  "canAccessMonitoring": true,
  "canAccessAdmin": true,
  "canAccessJmeter": true,
  "canAccessScripts": true
}

Les flags canAccess* sont utilisés par le frontend pour adapter l'affichage (masquer les onglets interdits). Le adminToken est le même UUID stocké dans VALID_ADMIN_TOKENS — il peut être utilisé à la place du cookie.

Codes d'erreur

Code Corps Cause
401 {"error": "Identifiants incorrects"} Clé auth.error.credentials

POST /api/admin/logout

Détruit la session et retire le token de VALID_ADMIN_TOKENS.

Auth : session ou token

Réponse — 200 OK

{ "success": true }

Pas d'erreur — l'endpoint est idempotent (un double logout retourne toujours 200).


GET /api/admin/status

Retourne l'état d'authentification admin, sans générer d'erreur.

Auth : optionnelle

Réponse — 200 OK

{ "authenticated": true }

Profil admin

GET /api/admin/accounts/me

Retourne le profil du compte admin connecté. Accessible à tout admin (pas seulement superadmin).

Auth : admin

Réponse — 200 OK

{
  "id": 1,
  "email": "admin@perfshop.fr",
  "canAccessChaos": true,
  "canAccessMonitoring": true,
  "canAccessAdmin": true,
  "canAccessJmeter": true,
  "canAccessScripts": true,
  "isSuperAdmin": true,
  "createdAt": "2024-01-01T00:00:00Z"
}

Le champ passwordHash n'est jamais exposé dans ce DTO. Même en chaos sécurité, seul le portail caché admin-portal peut exposer ce champ.


Gestion des comptes admin

GET /api/admin/accounts

Liste tous les comptes admin.

Auth : superadmin uniquement 👑

Réponse — 200 OK

Tableau d'objets identiques au DTO de /accounts/me :

[
  { "id": 1, "email": "admin@perfshop.fr", "isSuperAdmin": true, "canAccessChaos": true, "..." },
  { "id": 2, "email": "trainer@perfshop.fr", "isSuperAdmin": false, "canAccessChaos": true, "..." }
]

Codes d'erreur

Code Cause
401 Non authentifié
403 Admin ordinaire (clé admin.error.reserved_superadmin)

POST /api/admin/accounts

Crée un nouveau compte admin.

Auth : superadmin 👑

Requête

{
  "email": "trainer@perfshop.fr",
  "password": "Minimum6",
  "canAccessChaos": true,
  "canAccessMonitoring": true,
  "canAccessAdmin": false,
  "canAccessJmeter": false,
  "canAccessScripts": false
}
Champ Type Requis Contrainte
email string oui Non vide, unique
password string oui ≥ 6 caractères
canAccess* boolean non (défaut false) Booléen

Réponse — 201 Created

Le compte créé sérialisé (même structure que GET /accounts/me).

Codes d'erreur

Code Cause
400 Email ou mot de passe manquant (admin.error.email_password_required_dot)
400 Mot de passe trop court (admin.service.password_min_length)
400 Email déjà utilisé (admin.service.email_exists)
401 Non authentifié
403 Non superadmin

DELETE /api/admin/accounts/{id}

Supprime un compte admin.

Auth : superadmin 👑 Protection : le superadmin lui-même ne peut pas être supprimé (admin.service.superadmin_no_delete).

Réponse — 200 OK

{ "success": true }

Codes d'erreur

Code Cause
401 Non authentifié
403 Non superadmin, ou tentative de suppression du superadmin
404 Compte introuvable

PUT /api/admin/accounts/{id}/password

Change le mot de passe d'un compte admin.

Auth : - Superadmin : peut changer n'importe quel compte - Admin ordinaire : peut uniquement changer son propre mot de passe (vérification via id == me.id)

Requête

{ "password": "NouveauMdpSolide" }

Réponse — 200 OK

{ "success": true }

Codes d'erreur

Code Cause
400 Mot de passe vide ou trop court
401 Non authentifié
403 Tentative de changer un autre compte en tant qu'admin ordinaire (admin.error.own_password_only)
404 Compte introuvable

Détection 400 vs 404

Le service AdminUserService.updatePassword() lance IllegalArgumentException pour deux raisons distinctes : mot de passe trop court (→ 400) et compte introuvable (→ 404). Le contrôleur distingue les deux en interrogeant findById() séparément : si le compte existe, c'est le mot de passe qui est en cause.


PUT /api/admin/accounts/{id}/rights

Modifie les droits d'accès d'un compte admin ordinaire.

Auth : superadmin 👑 Protection : les droits du superadmin sont fixes et non modifiables (admin.service.superadmin_rights_fixed).

Requête

{
  "canAccessChaos": true,
  "canAccessMonitoring": true,
  "canAccessAdmin": false,
  "canAccessJmeter": false,
  "canAccessScripts": false
}

Réponse — 200 OK

Le compte mis à jour sérialisé.

Codes d'erreur

Code Cause
401 Non authentifié
403 Non superadmin, ou tentative de modifier le superadmin
404 Compte introuvable

Gestion des produits

GET /api/admin/products

Liste tous les produits non pédagogiques (les produits avec is_pedagogique = true sont masqués).

Auth : admin Tri : par catégorie puis par nom

Réponse — 200 OK

[
  {
    "id": 1,
    "name": "Casque audio Bluetooth",
    "description": "...",
    "price": 149.99,
    "stock": 42,
    "category": "Audio",
    "imageUrl": "/images/products/1.jpg",
    "createdAt": "2025-01-15T10:00:00Z",
    "updatedAt": "2025-03-20T14:30:00Z"
  }
]

Produits pédagogiques masqués

Certains produits sont créés exclusivement pour le parcours pédagogique (agents, objets narratifs). Ils portent le flag is_pedagogique = true et sont toujours filtrés des listes admin — ils ne peuvent être ni modifiés ni supprimés via /api/admin/products/*. Une tentative retourne 403 Forbidden avec le message admin.error.pedagogique_protected.


POST /api/admin/products

Crée un produit.

Auth : admin

Requête

{
  "name": "Nouveau produit",
  "description": "Description détaillée",
  "price": 29.99,
  "stock": 100,
  "category": "Informatique",
  "imageUrl": "/images/products/default.jpg"
}

Tous les champs sont obligatoires sauf imageUrl.

Réponse — 201 Created

Le produit créé sérialisé.

Codes d'erreur

Code Cause
400 Champ obligatoire manquant ou format invalide (admin.error.data_invalid)
401 Non authentifié

PUT /api/admin/products/{id}

Met à jour un produit existant. Tous les champs du body sont optionnels — seuls ceux transmis sont mis à jour.

Auth : admin

Requête

{
  "price": 24.99,
  "stock": 75
}

Réponse — 200 OK

Le produit mis à jour.

Codes d'erreur

Code Cause
400 Données invalides
401 Non authentifié
403 Produit pédagogique protégé (admin.error.pedagogique_protected)
404 Produit introuvable

DELETE /api/admin/products/{id}

Supprime un produit.

Auth : admin

Réponse — 200 OK

{ "success": true }

Codes d'erreur

Code Cause
401 Non authentifié
403 Produit pédagogique protégé
404 Produit introuvable

POST /api/admin/products/{id}/image

Upload une image pour un produit. Le fichier est écrit sur le volume Docker monté et l'URL est mise à jour en base.

Auth : admin Content-Type : multipart/form-data Champ attendu : file Formats acceptés : image/jpeg, image/png, image/webp, image/gif

Requête

curl -H "X-Admin-Token: $TOKEN" \
     -F "file=@/path/to/image.png" \
     http://localhost:9080/api/admin/products/42/image

Réponse — 200 OK

{
  "success": true,
  "imageUrl": "/images/products/custom_42.png",
  "filename": "custom_42.png"
}

Le nom de fichier est normalisé côté serveur : custom_<productId>.<ext>. L'extension est choisie selon le Content-Type reçu.

Codes d'erreur

Code Cause
400 Content-Type ne commence pas par image/ (admin.error.image_required)
401 Non authentifié
404 Produit introuvable
500 Échec d'écriture disque (admin.error.file_write_failed)

Où sont stockées les images

Le répertoire cible est contrôlé par la propriété Spring perfshop.images.upload-path (défaut : /images-data/products). Dans Docker Compose, ce chemin est un volume monté qui permet au conteneur nginx de servir les images via le préfixe d'URL /images/products/.


Gestion des utilisateurs

GET /api/admin/users

Liste les utilisateurs finaux non pédagogiques avec leur nombre de commandes.

Auth : admin

Réponse — 200 OK

[
  {
    "id": 42,
    "email": "alice@example.com",
    "firstName": "Alice",
    "lastName": "Durand",
    "createdAt": "2025-02-01T10:00:00Z",
    "orderCount": 7
  }
]

Les comptes avec is_pedagogique = true (les agents narratifs du parcours BAC5) sont filtrés — ils n'apparaissent pas dans cette liste et ne peuvent pas être supprimés via l'admin.


POST /api/admin/users

Crée un utilisateur final. Utilisé typiquement pour les démonstrations ou pour dépanner un étudiant.

Auth : admin

Requête

{
  "email": "new-user@example.com",
  "password": "UnMotDePasseSolide"
}

Réponse — 201 Created

{ "success": true, "id": 42 }

Le mot de passe est haché avec BCrypt via AuthService.hashPassword() avant insertion.

Codes d'erreur

Code Cause
400 Email ou mot de passe manquant (admin.error.email_password_required)
400 Email déjà utilisé (admin.error.email_exists)
401 Non authentifié

DELETE /api/admin/users/{id}

Supprime un utilisateur et toutes ses commandes (via orderRepository.deleteByUserId() en amont).

Auth : admin Protection : les comptes avec is_pedagogique = true ne peuvent pas être supprimés (admin.error.user_pedagogique_protected).

Réponse — 200 OK

{ "success": true }

Codes d'erreur

Code Cause
401 Non authentifié
403 Compte pédagogique protégé
404 Utilisateur introuvable

Gestion des commandes

GET /api/admin/orders

Liste toutes les commandes tous utilisateurs confondus.

Auth : admin

Paramètres

Paramètre Type Défaut Description
includeTestData (query) boolean false Si true, inclut les commandes is_test_data = true des agents pédagogiques

Réponse — 200 OK

[
  {
    "id": 789,
    "orderNumber": "PS-2026-000123",
    "totalAmount": 299.98,
    "status": "CONFIRMED",
    "createdAt": "2026-04-08T10:30:00Z",
    "shippingMethod": "express",
    "shippingAddress": "12 rue de la Paix, 75001 Paris",
    "userId": 42,
    "userEmail": "alice@example.com",
    "itemCount": 1,
    "isTestData": false
  }
]

Tri : les plus récentes en premier (createdAt DESC).


DELETE /api/admin/orders/{id}

Supprime une commande.

Auth : admin

Réponse — 200 OK

{ "success": true }

Codes d'erreur

Code Cause
401 Non authentifié
404 Commande introuvable

Suppression destructive

Cette opération est définitive — elle supprime la commande et ses order_items par cascade JPA. Elle ne recrédite pas le stock. C'est un outil de nettoyage administratif (ex. purge des commandes de test), pas un remplaçant de POST /api/orders/{id}/cancel.


Exemple curl — workflow admin complet

# 1. Login admin
TOKEN=$(curl -s -H "Content-Type: application/json" \
  -d '{"email":"admin@perfshop.fr","password":"admin"}' \
  http://localhost:9080/api/admin/login | jq -r '.adminToken')

# 2. Lister les produits
curl -H "X-Admin-Token: $TOKEN" http://localhost:9080/api/admin/products

# 3. Créer un produit
curl -H "X-Admin-Token: $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"name":"Test","price":9.99,"stock":10,"category":"Divers"}' \
     http://localhost:9080/api/admin/products

# 4. Upload image
curl -H "X-Admin-Token: $TOKEN" \
     -F "file=@/tmp/logo.png" \
     http://localhost:9080/api/admin/products/42/image

# 5. Créer un compte admin ordinaire (superadmin requis)
curl -H "X-Admin-Token: $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"email":"trainer@perfshop.fr","password":"trainer123","canAccessChaos":true}' \
     http://localhost:9080/api/admin/accounts

Points d'attention

Ce contrôleur n'est PAS vulnérable

Contrairement au portail admin caché (admin-portal.md), AdminController est sécurisé par construction :

  • BCrypt pour la comparaison de mot de passe
  • Pas de SQLi (requêtes paramétrées via JPA)
  • Pas d'IDOR (vérifications isSuperAdmin systématiques)
  • Pas de mass assignment (DTOs explicites)

Les failles pédagogiques sont isolées dans AdminPortalController qui n'est activé qu'en Chaos Sécurité niveau 4. Cette séparation garantit qu'un chaos mal configuré ne compromet jamais le chemin d'administration légitime.

Pas de pagination

Les listes admin (/products, /users, /orders, /accounts) ne sont pas paginées. C'est acceptable car PerfShop est une plateforme pédagogique avec des volumes de données limités (quelques centaines de lignes max). En production avec des volumes élevés, il faudrait ajouter une pagination Spring Data comme dans ProductController.


Liens associés