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 ?

Elle couvre cinq 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)
Audit Lire les journaux d’audit, globalement ou filtrés par marchand
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. Une seule variable d’environnement compte (console-admin/.env.example) :

Fenêtre de terminal
# URL de l'API E-Pay (appels server-side uniquement)
TITREO_API_BASE_URL=http://localhost:3000
NODE_ENV=development

Elle est lue à un seul endroit, console-admin/lib/config.ts :

export const config = {
apiBaseUrl: process.env.TITREO_API_BASE_URL ?? 'http://localhost:3000',
}

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 d’un groupe (authed) protégé par le middleware :

app/
page.tsx -> redirige vers /merchants
login/page.tsx -> formulaire de connexion
(authed)/
layout.tsx -> requireAdmin() + Sidebar
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
api-keys/page.tsx -> clés API du marchand (+ create-form)
psp-configs/page.tsx -> configs PSP du marchand (lecture + suppression)
audit-logs/page.tsx -> journaux d'audit du 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

La navigation latérale (console-admin/components/Sidebar.tsx) n’expose que trois entrées : Marchands, Admins Titreo, Paramètres. Les écrans par marchand (PSP configs, API keys, audit) sont atteints via des onglets (console-admin/components/MerchantTabs.tsx), pas depuis la sidebar.

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.

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