Contrat API — Mode Technique du portail SSH (recherche d’un client par e-mail)
Statut : IMPLÉMENTÉ (2026-10-07) côté API. Le front (apps/ssh-portal) peut se brancher sur les routes ci-dessous.
Besoin
Les revendeurs officiels et techniciens aident les clients à obtenir leur lien de paiement (TiketMOMO) ou à configurer leur routeur. Pour cela, ils doivent connaître l’identifiant du client, ses NAS et les noms de ses profils. Le mode Admin du portail SSH montre déjà tous les clients et leurs comptes VPN, mais il est réservé au propriétaire. On ajoute un mode Technique : le technicien saisit l’e-mail exact d’un client et obtient uniquement ces informations.
Qui y a accès
- Utilisateur connecté au portail SSH (jeton
Authorization: Bearer …, même authentification que le reste du portail). - ET partenaire officiel : candidature
APPROVEDdanspartner_applications(même règle que le middleware existantrequireOfficialPartnerAuth). - Le propriétaire (rôle
admin) y a aussi accès. - Pour la capacité
GET /api/v1/technique/access, un client ordinaire ou un revendeur non approuvé reçoit 200 avecallowed: false, afin que le front masque l’onglet « Mode Technique » sans traiter une erreur. - Pour la recherche
GET /api/v1/technique/clients/lookup, un client ordinaire ou un revendeur non approuvé reçoit 403 (TECHNIQUE_ACCESS_DENIED).
Capacité (pour afficher ou masquer l’onglet)
GET /api/v1/technique/access
200
{
"success": true,
"data": { "allowed": true, "role": "partner", "daily_quota": 200, "used_today": 3 }
}
role vaut partner ou admin. Si l’accès est refusé : { "success": true, "data": { "allowed": false } } (200, pour
que le front décide sans traiter une erreur).
Recherche d’un client
GET /api/v1/technique/clients/lookup?email=client@example.invalid
email: obligatoire, correspondance EXACTE (insensible à la casse, espaces retirés). Pas de recherche partielle, pas de liste, pas de joker : on ne doit pas pouvoir parcourir la base clients.
200 — client trouvé
{
"success": true,
"data": {
"client_id": "client_0000000000000000000000000000abcd",
"email": "client@example.invalid",
"display_name": "Client Exemple",
"store": {
"active": true,
"slug": "boutique-exemple",
"payment_collector": "PLATFORM"
},
"nas": [
{
"nas_id": "exemple-TM12",
"router_serial": "HX0000EXEMPLE",
"router_model": "hAP ax2",
"tunnel_status": "CONNECTED",
"tunnel_last_seen_at": "2026-10-06T08:00:00.000Z",
"allocation_id": "00000000-0000-0000-0000-000000000001"
}
],
"sales_profiles": [{ "name": "100F-4H", "price": 100, "currency": "XOF", "validity": "4h" }],
"hotspot_profiles": [
{ "router_serial": "HX0000EXEMPLE", "name": "100F-4H", "price": 100, "active": true }
],
"payment_link_template": "https://live.jmoai.net/api/v1/store/buy-ticketmomo?nasid={nas_id}&amount={price}¤cy=cfa&profile_name={profile_name}"
}
}
Sources des champs (pour l’implémentation)
| Champ | Source |
|---|---|
client_id, email, display_name |
managers (id, email, nom) — recherche sur l’e-mail exact |
store |
client_store_profiles (store_slug, suspended_at → active) ; payment_collector = mode d’encaissement (PLATFORM = notre compte, MERCHANT = compte du vendeur) |
nas |
remote_access_allocations du client avec status = ACTIVE (nas_id, locked_router_serial, locked_router_model, tunnel_status, tunnel_last_seen_at, id) |
sales_profiles |
client_store_profiles.store_settings.sales_profiles (même lecture que GET /api/v1/clients/store/profiles) |
hotspot_profiles |
mikrotik_hotspot_profiles filtrés sur les router_serial des NAS du client |
payment_link_template |
base publique de l’API du silo (PUBLIC_API_BASE_URL), jamais live.jmoai.net en dur |
Ce qui n’est JAMAIS renvoyé : téléphone, mots de passe, identifiants routeur, clés VPN, soldes, ventes, retraits, adresses IP publiques, licences et leurs clés, autres e-mails.
Erreurs
| Code HTTP | error.code |
Quand |
|---|---|---|
| 400 | TECHNIQUE_EMAIL_REQUIRED |
email absent ou invalide |
| 401 | — | non connecté |
| 403 | TECHNIQUE_ACCESS_DENIED |
pas partenaire officiel approuvé |
| 404 | TECHNIQUE_CLIENT_NOT_FOUND |
aucun client avec cet e-mail exact (même message dans tous les cas, sans indice) |
| 429 | TECHNIQUE_RATE_LIMITED |
quota dépassé (voir ci-dessous), en-tête Retry-After |
Format d’erreur : { "success": false, "error": { "code": "…", "message": "…" } } (message en français).
Garde-fous (obligatoires côté API)
- Quota : 30 recherches par heure et 200 par jour et par technicien (réglables par l’environnement :
TECHNIQUE_LOOKUP_HOURLY_QUOTA, défaut30, etTECHNIQUE_LOOKUP_DAILY_QUOTA, défaut200). Les recherches « introuvable » comptent aussi, pour empêcher de deviner des e-mails. - Journal d’audit : chaque recherche est enregistrée (qui, quel e-mail recherché, trouvé ou non, date, IP). Le propriétaire peut le consulter dans le mode Admin.
- Limiteur de débit dédié sur ces routes (cliquet
tests/rateLimitDrift.test.js). - Recherche par e-mail exact uniquement, et réponse minimale (champs ci-dessus).
Décisions propriétaire (2026-10-07)
- Périmètre : un technicien agréé peut chercher n’importe quel client, par e-mail exact, avec quota et audit.
- Prévenir le client : pas de notification client pour l’instant.
Ce que le front doit faire (portail SSH)
- Appeler
GET /api/v1/technique/accessau chargement ; n’afficher l’onglet Mode Technique que siallowed = true. - Un seul champ : l’e-mail du client, et un bouton « Rechercher ».
- Afficher : identifiant client (avec bouton Copier), NAS (identifiant, routeur, état du tunnel), profils de vente et
profils hotspot, puis un générateur de lien de paiement : choisir un NAS et un profil, remplir le gabarit
payment_link_template, et proposer Copier et Partager sur WhatsApp. - Afficher le quota restant (
used_today/daily_quota) et les messages d’erreur ci-dessus en français. - Ne rien mettre en cache dans le navigateur au-delà de l’écran courant.
Mode Admin (« Gold ») — afficher l’identifiant sous l’e-mail (front seulement)
Demande du propriétaire (2026-10-06) : dans le mode Admin, voir l’identifiant de chaque client sous son e-mail.
Aucune modification d’API : la liste du mode Admin reçoit déjà client_id pour chaque compte, et la recherche
l’accepte déjà (apps/ssh-portal/src/features/admin/components/AdminDashboard.tsx, filtre l.108-111).
À faire côté front :
- dans chaque ligne de la liste (vers la l.259, avatar +
managers.display_name || managers.email) : afficherclient_idsur une seconde ligne, en petit et en police à chasse fixe, avec un bouton Copier ; - dans l’en-tête du client sélectionné (l.131) : même affichage sous le nom ou l’e-mail ;
- la recherche « par email, nom… » accepte déjà l’identifiant : mettre à jour le texte d’aide en « Rechercher par e-mail, nom ou identifiant ».