Aller au contenu

console-admin

console-admin est le back-office interne de Titreo E-Pay — l’outil d’administration utilisé par les équipes Titreo (pas par les marchands). Elle répond à une question simple : comment crée-t-on et administre-t-on les marchands de la plateforme sans toucher à la base de données à la main ?

Ses responsabilités, toutes de scope admin :

Responsabilité Détail
Marchands Créer, lister, éditer (nom, webhook, TTL de session), activer/désactiver un marchand
Clés API Émettre et révoquer les clés API d’un marchand (scopes restreints, jamais admin)
Configs PSP Consulter et supprimer les configs PSP d’un marchand (en lecture surtout : ces clés sont saisies par le marchand depuis sa propre console)
Cartes cadeaux Consulter et retirer les accès Titréo d’un marchand. Jamais de saisie à sa place
Audit Lire les journaux d’audit, globalement ou filtrés par marchand
Transactions Lister les sessions de tous les marchands, voir le détail, exporter en CSV
Journal Lire le journal métier, tous marchands
Logs techniques Explorer les logs et reconstituer la trace d’une requête ou d’un paiement
Téléchargements Retrouver les paquets à installer chez un marchand, avec leur empreinte
Admins Titreo Lister et créer les comptes admin qui ont accès à cette console

À ne pas confondre avec console-merchant : celle-ci est destinée au marchand pour gérer ses clés et ses PSP ; console-admin est l’outil interne qui voit tous les marchands.

flowchart LR
  A["Équipe Titreo (navigateur)"] --> B["console-admin (Next.js, port 3400)"]
  B -->|"server actions / RSC, cookie tep_session"| C["API E-Pay (port 3000)"]
  C --> D["Firebird"]
  E["Marchand"] -.->|"sa propre console"| F["console-merchant"]
  F --> C

D’après console-admin/package.json :

  • Next.js 15 (App Router) + React 19
  • Zod pour valider les entrées de formulaires côté serveur
  • Tailwind CSS (config console-admin/tailwind.config.ts, tokens dans console-admin/app/tokens.css)
  • Biome pour le lint/format
  • Pas de base de données ni d’ORM ici : la console est sans état, tout passe par l’API.

Scripts npm réels (package.json) :

Script Effet
dev next dev -p 3400
build next build
start next start -p 3400
typecheck tsc --noEmit
lint / lint:fix biome check . / biome check --write .

La console tourne sur le port 3400. Variables d’environnement :

Variable Rôle Lue dans
TITREO_API_BASE_URL URL de l’API E-Pay, appels côté serveur uniquement. Défaut http://localhost:3000 console-admin/lib/config.ts
GRAFANA_BASE_URL Grafana du client. Vide : aucun lien « Ouvrir dans Grafana » console-admin/lib/config.ts
TITREO_CDN_INTERNAL_URL Adresse par laquelle le serveur lit downloads/index.json (http://cdn en production). Absente : celle de TITREO_CDN_PUBLIC_URL console-admin/lib/downloads.ts
TITREO_CDN_PUBLIC_URL Adresse portée par les liens de téléchargement (https://cdn.<domaine> en production) console-admin/lib/downloads.ts

Les deux variables du CDN sont posées par deploy/docker-compose.prod.yml.

Un Dockerfile multi-étapes (deps → builder → runner) existe à la racine de la brique et expose le port 3400. Voir aussi l’environnement de dev local.

L’arborescence App Router (console-admin/app/) est organisée autour de deux groupes protégés, (authed) et (wide) :

app/
page.tsx -> redirige vers /merchants
login/page.tsx -> formulaire de connexion
healthz/route.ts -> sonde de vivacité
(authed)/
layout.tsx -> requireAdmin() + Sidebar
transactions/
page.tsx -> sessions de tous les marchands
[sessionId]/page.tsx -> détail d'une session, lien vers sa trace
export/route.ts -> relais vers /v1/admin/sessions/export.csv
journal/page.tsx -> journal métier
merchants/
page.tsx -> liste des marchands (+ filtre actif/en attente)
new/page.tsx -> création d'un marchand
[id]/
page.tsx -> aperçu + édition du marchand
psp-configs/page.tsx -> configs PSP du marchand (lecture + suppression)
gift-configs/page.tsx -> accès cartes cadeaux du marchand (lecture + suppression)
api-keys/page.tsx -> clés API du marchand (+ create-form)
audit-logs/page.tsx -> journaux d'audit du marchand
downloads/page.tsx -> paquets à installer chez un marchand
admin-users/
page.tsx -> liste des admins Titreo
new/page.tsx -> création d'un admin (+ form)
settings/page.tsx -> compte admin courant + déconnexion
(wide)/
layout.tsx -> requireAdmin() + Sidebar, sans colonne de lecture bornée
logs/page.tsx -> logs techniques : explorateur et trace

La navigation latérale (console-admin/components/Sidebar.tsx) expose sept entrées : Transactions, Journal, Logs techniques, Marchands, Téléchargements, Admins Titreo, Paramètres. Les écrans par marchand sont atteints via cinq onglets (console-admin/components/MerchantTabs.tsx) : Aperçu, PSP configs, Cartes cadeaux, API keys, Audit logs.

app/(authed)/merchants/[id]/gift-configs/page.tsx liste les accès Titréo du marchand : fournisseur, enseigne, clé masquée, statut, date de vérification, date de mise à jour. Un accès est « Actif » s’il est actif et vérifié.

Sans accès, la page le dit : la page de paiement de ce marchand ne propose pas les cartes cadeaux. Le bouton Supprimer appelle DELETE /v1/admin/merchants/:id/gift-configs/:provider. Il n’y a pas de formulaire de saisie ; pour poser des accès à la place d’un marchand, passer par tep gift set sur le serveur.

app/(wide)/logs/page.tsx monte components/logs/LogsApp.tsx. Deux vues : Explorateur (barre de requête à pastilles, histogramme empilé par niveau avec sélection à la souris, facettes, suivi en direct toutes les 3 s, 500 lignes par page) et Trace (couloirs par acteur, silences repliés).

Tout est décrit sur Logs techniques : routes de l’API, syntaxe de la barre de requête, activation de la collecte, limites.

La fiche d’une transaction et le journal métier mènent à la trace (/logs?view=trace&session=…, /logs?view=trace&corr=…).

app/(authed)/downloads/page.tsx lit downloads/index.json sur le CDN (lib/downloads.ts) et liste, par paquet, toutes les versions publiées : fichier, taille, empreinte SHA-256, date, lien. La plus récente porte le badge « Dernière ».

Paquet Installation
Module PrestaShop Zip à déposer dans le back-office. Il porte les adresses de la plateforme
SDK JavaScript / TypeScript Par l’adresse du fichier, sans registre npm
SDK PHP Par Composer, en dépôt de type « package »

L’app Shopify n’y figure pas : c’est un service hébergé, installé par son lien d’installation.

Si l’index est illisible — aucun paquet publié, ou CDN qui ne répond pas — la page le dit et renvoie à tep plugin publish. Voir Plugin PrestaShop.

Toute la logique réseau est centralisée dans console-admin/lib/api.ts. Les pages sont des Server Components et les mutations des server actions ('use server') ; le navigateur ne contacte jamais l’API directement.

sequenceDiagram
  participant U as "Navigateur"
  participant C as "console-admin (server)"
  participant A as "API E-Pay (3000)"
  U->>C: "POST login (server action)"
  C->>A: "POST /v1/auth/admin/login"
  A-->>C: "Set-Cookie: tep_session=..."
  C-->>U: "cookie httpOnly tep_session + redirect /merchants"
  U->>C: "GET /merchants"
  C->>A: "GET /v1/admin/merchants (Cookie: tep_session)"
  A-->>C: "{ data, nextCursor, hasMore }"
  C-->>U: "HTML rendu (RSC)"

Le flux complet de session (console-admin/lib/actions/auth.ts + console-admin/lib/auth.ts) :

  1. loginAction poste les identifiants sur POST /v1/auth/admin/login.
  2. L’API renvoie un cookie tep_session ; la console le ré-émet côté serveur en cookie httpOnly, sameSite: 'strict', secure: true.
  3. À chaque rendu, getCurrentUser() appelle GET /v1/auth/me et exige kind === 'admin' (requireAdmin() redirige sinon vers /login).
  4. logoutAction appelle POST /v1/auth/logout et supprime le cookie.

Le cookie est le credential bearer de l’API : console-admin/lib/api.ts le réinjecte dans l’en-tête Cookie de chaque appel sortant.

Le même fichier pose un X-Correlation-Id sur chaque appel, un identifiant par appel. En cas d’erreur, ApiHttpError.correlationId reprend celui que l’API a renvoyé ; la page de logs l’affiche sous « Référence ». Voir Traçabilité.

Tous les appels sont concentrés dans console-admin/lib/actions/*.ts. Les routes consommées (vérifiées présentes côté api/src/http/routes/) :

Server action (fichier) Méthode + endpoint API
auth.ts POST /v1/auth/admin/login, POST /v1/auth/logout, GET /v1/auth/me
merchants.ts GET/POST /v1/admin/merchants, GET/PATCH /v1/admin/merchants/{id}
api-keys.ts GET/POST /v1/admin/merchants/{id}/api-keys, DELETE …/api-keys/{keyId}
psp-configs.ts GET /v1/admin/merchants/{id}/psp-configs, DELETE …/psp-configs/{provider}
gift-configs.ts GET /v1/admin/merchants/{id}/gift-configs, DELETE …/gift-configs/{provider}
events.ts GET /v1/admin/events, GET /v1/admin/sessions, GET /v1/sessions/{id}
logs.ts GET /v1/admin/logs, GET /v1/admin/logs/histogram, GET /v1/admin/logs/labels, GET /v1/admin/traces/{correlationId}, GET /v1/admin/sessions/{id}/trace
audit-logs.ts GET /v1/audit-logs (filtres merchantId, action, limit, cursor)
admin-users.ts GET/POST /v1/admin/users

Le préfixe /v1/admin/ côté API porte les opérations réservées à l’administration. Le contrat de réponse en cas d’erreur ({ code, message }) est mappé sur ApiHttpError dans lib/api.ts puis affiché dans les formulaires. Voir les contrats inter-briques pour la forme exacte des enveloppes.

Deux protections explicites valent d’être notées (détails dans Sécurité) :

  • CSP par nonce (console-admin/middleware.ts) : Content-Security-Policy construite par requête avec un nonce sur script-src et 'strict-dynamic'. Le middleware redirige aussi vers /login toute route non publique sans cookie tep_session. La politique part en Report-Only (basculer CSP_REPORT_ONLY=false pour l’appliquer).
  • Scopes de clés API verrouillés (console-admin/lib/actions/api-keys.ts) : la liste autorisée énumère explicitement six scopes (sessions:read, sessions:write, psp:read, psp:write, webhooks:read, webhooks:write) et exclut volontairement tout scope admin, pour qu’une clé liée à un marchand ne puisse jamais être tout-accès.