Appearance
BDD-125 — Revue des accès API (mise à jour de connexion)
1. User Story
En tant qu'administrateur, je veux pouvoir renommer une connexion API et remplacer sa clé API sans avoir à la supprimer puis recréer, afin de gérer le cycle de vie d'une connexion (rotation de clé, ajustement d'intitulé) tout en conservant les sources déjà rattachées.
Ce ticket complète BDD-117 (création/listing/suppression de connexion) et BDD-118 (sources
API_FORM).
2. Critères d'acceptation
| # | Critère | Statut |
|---|---|---|
| 1 | L'administrateur peut renommer une connexion existante sans rompre les sources rattachées | ✅ |
| 2 | L'administrateur peut remplacer la clé API d'une connexion existante (chiffrement + reset statut ACTIVE) | ✅ |
| 3 | Les deux modifications passent par un seul endpoint PATCH /connections/:uuid (champs combinables ou indépendants) | ✅ |
| 4 | En cas d'erreur de validation ou connexion introuvable, aucune modification n'est persistée et un message explicite est affiché | ✅ |
| 5 | Le adminGuard du front fonctionne avec la signature Vue Router 4 (retourne true / route) et tolère une session non encore hydratée | ✅ |
Détail fonctionnel des critères
Renommage
- Action « Renommer » dans le menu de chaque ligne de la liste
/connections(édition inlineq-inputavec ✓ / ✕). Entervalide,Échapannule, bouton ✓ désactivé si le nom est vide ou > 255 caractères.- Pas de modification de la clé ni d'appel API distant (seul le libellé change).
- Action « Renommer » dans le menu de chaque ligne de la liste
Modification de clé API
- Action « Modifier la clé API » dans le menu (icône
vpn_key). - Ouvre une dialog dédiée : champ password avec toggle visibilité (
visibility/visibility_off), bouton Annuler / Enregistrer. - Côté serveur : chiffrement immédiat (
encrypt), persistance, statut remis àACTIVE,error_messageeffacé,last_validated_atmis à jour. Pas d'appel à/connection-checkauPATCH(aligné surPOST /connections). - Le
api_key_prefixest recalculé (16 premiers caractères) — la liste affiche immédiatement le nouveau préfixe. - Validité opérationnelle : détectée lors des appels distants (parcours
/connections/:uuid/browse, sources API) ; un401distant déclenchemarkConnectionAsError(statutERROR).
- Action « Modifier la clé API » dans le menu (icône
Endpoint unifié
PATCH /connections/:uuidaccepte{ name?, apiKey? }(au moins l'un des deux est requis).- Validation
class-validator: champs optionnels, mais chacun, s'il est fourni, doit être une chaîne non vide. 400 BadRequestsi le body est vide ou si les deux champs sont blancs après trim.
Erreurs explicites
PATCH:400si body vide / champs blancs après trim ;404si connexion introuvable ou hors périmètre entreprise.- Clé réellement invalide : pas rejetée au
PATCH; la connexion passe enERRORlors du prochain appel API distant (markConnectionAsError, message « Clé API invalide ou révoquée. »). - Côté front :
notify.error(extractErrorMessage(error, …))sur échecPATCH; la dialog clé API reste ouverte (renommage : fermeture immédiate).
Garde
adminGuard- Migration vers la signature promise-based (Vue Router 4) : retourne
true,'/upload', ou uneRouteLocation. - Si
userStore.userestnull(rechargement direct sur/connections), appel àgetMe()avant décision. - Si
getMe()échoue (réseau, 401) → redirection vers'/upload'plutôt qu'erreur non gérée.
- Migration vers la signature promise-based (Vue Router 4) : retourne
3. Périmètre fonctionnel
Objectif : permettre la création d'une source depuis ConnectionBrowsePage pour une ressource de type fiche (submissions), en plus du formulaire (responses).
Backend
| Fichier | Rôle |
|---|---|
back/src/api/connections/dto/connection.dto.ts | UpdateConnectionDto : name? optionnel, ajout apiKey? (avec @IsOptional @IsString @IsNotEmpty) |
back/src/api/connections/connections.service.ts | updateConnection étendue : construit updateData selon les champs fournis ; si apiKey présent → encrypt() puis mise à jour api_key_encrypted / api_key_iv / api_key_prefix / status=ACTIVE / error_message=null / last_validated_at. markConnectionAsError pour le suivi ERROR à l'usage. BadRequestException si aucun champ exploitable |
back/src/api/connections/connections.controller.ts | Mise à jour du @ApiOperation (« Mettre à jour une connexion API (nom et/ou clé) ») |
back/src/api/sources/dto/source.dto.ts | Extension du contrat source API : cardUuid?, cardTitle?, apiResourceType?: 'form' | 'card', en conservant formUuid/formTitle |
back/src/api/sources/sources.controller.ts | GET /sources/api-preview accepte désormais formUuid ou cardUuid (XOR), avec labels optionnels (formTitle, cardTitle) |
back/src/api/sources/sources.service.ts | Unification du flux de création/preview API : résolution de ressource (form/card) puis appel à loadFormResponses ou loadCardSubmissions |
Frontend
| Fichier | Rôle |
|---|---|
front/src/stores/connections-store.js | updateConnection({ connectionUuid, name?, apiKey? }) : payload construit conditionnellement (typeof === 'string'), mise à jour optimiste de la liste locale via connections[i] = { ...connections[i], ...data } |
front/src/pages/connections/ConnectionsPage.vue | Nouvelle action « Modifier la clé API » dans le menu (icône vpn_key, séparateur), nouvelle dialog showEditApiKeyDialog avec toggle visibilité et bouton Enregistrer désactivé tant que la clé est vide, helpers startEditApiKey / cancelEditApiKey / confirmEditApiKey |
front/src/pages/connections/ConnectionCreatePage.vue | Wording : « générée dans Console Admin » + sous-titre d'aide ; placeholder clé heriade_… (générique) |
front/src/router/adminGuard.js | Migration en async : retourne true ou '/upload', charge getMe() si user absent, fallback '/upload' en cas d'échec |
front/src/pages/connections/ConnectionBrowsePage.vue | Le bouton Créer une source est disponible pour form et card; redirection vers /sources/create avec formUuid/formTitle ou cardUuid/cardTitle |
front/src/pages/admin/sources/AdminSourceCreatePage.vue | Pré-remplissage API enrichi (cardUuid/cardTitle), preview et submit pilotés par apiResourceType |
front/src/stores/source-store.js | getApiSourcePreview et createSource enrichis pour supporter cardUuid/cardTitle/apiResourceType |
Aucune migration de base nécessaire — la table
api_connectionscontient déjà tous les champs (api_key_encrypted,api_key_iv,api_key_prefix,status,error_message,last_validated_at) introduits par BDD-117.
4. Endpoints API
| Méthode | Chemin | Guards | Description |
|---|---|---|---|
PATCH | /connections/:uuid | AuthGuard, AdminGuard | Met à jour une connexion : name, apiKey, ou les deux |
GET | /sources/api-preview | AuthGuard, AdminGuard | Prévisualise une ressource API (formUuid ou cardUuid) |
POST | /sources | AuthGuard, AdminGuard | Crée une source API depuis formulaire ou fiche |
Corps PATCH /connections/:uuid — renommage seul
json
{ "name": "API Formulaires prod" }Corps PATCH /connections/:uuid — rotation de clé seule
json
{ "apiKey": "heriade_frm_nouvelle_secret_xyz" }Corps PATCH /connections/:uuid — mise à jour combinée
json
{
"name": "API Formulaires staging",
"apiKey": "heriade_frm_staging_secret"
}Réponse 200 (extrait)
json
{
"uuid": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"name": "API Formulaires prod",
"baseUrl": "https://api.formulaires.test",
"apiKeyPrefix": "heriade_frm_xxxx",
"status": "ACTIVE",
"errorMessage": null,
"lastValidatedAt": "2026-05-27T10:00:00.000Z",
"createdAt": "2026-05-18T10:00:00.000Z",
"updatedAt": "2026-05-27T10:00:00.000Z"
}Erreurs courantes
| Code | Cas |
|---|---|
400 | Body vide / name et apiKey tous deux absents ou blancs après trim (PATCH /connections/:uuid) |
400 | GET /sources/api-preview : formUuid et cardUuid absents ou fournis simultanément |
401 | Jeton absent / invalide |
403 | Rôle non admin |
404 | Connexion introuvable ou hors périmètre entreprise (ApiConnectionNotFoundException) |
Complément payload POST /sources (mode API)
Cas formulaire :
json
{
"source": "api",
"connectionUuid": "uuid-connexion",
"formUuid": "uuid-formulaire",
"formTitle": "Inscriptions 2026",
"apiResourceType": "form",
"name": "Source inscriptions",
"tableName": "inscriptions_2026"
}Cas fiche :
json
{
"source": "api",
"connectionUuid": "uuid-connexion",
"cardUuid": "uuid-fiche",
"cardTitle": "Fiche Clients",
"apiResourceType": "card",
"name": "Source soumissions clients",
"tableName": "soumissions_clients"
}5. Règles métier implémentées
- Périmètre entreprise : un admin met à jour uniquement les connexions de son
enterprise_id; un super-admin n'est pas filtré (buildEnterpriseWhere). - Trim systématique côté serveur (
dto.name?.trim(),dto.apiKey?.trim()) avant validation/persistance. - Pas de pré-vérification au
PATCHni auPOST /connections: la clé est chiffrée et stockée ; la validité est constatée à l'usage (parcours, preview source, etc.). - Reset du statut : une rotation de clé via
PATCHrepose les compteurs (status = ACTIVE,error_message = null,last_validated_at = now) même si l'ancienne clé était enERROR— sans garantir que la nouvelle clé soit valide tant qu'aucun appel distant n'a réussi. - Marquage erreur :
markConnectionAsError(appelé depuisconnection-browse.servicesur401distant) repasse la connexion enERRORsans modifier la clé stockée. - Prefix recalculé systématiquement sur la nouvelle clé (
buildApiKeyPrefix, 16 premiers caractères). - Mise à jour optimiste côté store : remplacement de l'élément dans
connections[]par fusion ({ ...existing, ...data }) — pas de refetch global. - Sources rattachées : aucun impact. Une source
API_FORM(cf. BDD-118) reste exploitable ; ses données déjà chargées ne sont pas resynchronisées (hors périmètre, prévu en BDD-119). - Ressource API (création/preview) : en mode
source='api', exactement une ressource doit être fournie (formUuidoucardUuid). - Compatibilité ascendante : le flux historique
formUuid/formTitlereste inchangé. - Persistance inchangée : pas de migration DB ; les sources issues de fiches restent stockées avec
source_type='API_FORM'.
6. Points de vérification
- Sur
/connections→ menu d'actions d'une ligne : présence de Consulter, Renommer, Modifier la clé API, Supprimer dans cet ordre, avec séparateurs. - Renommage : édition inline, validation
Enter, annulationÉchap/ bouton ✕, toast « Connexion renommée », pas de modification si entrée vide ; un échec backend reset l'édition et affiche le message d'erreur. - Rotation de clé : dialog « Mettre à jour la clé API » affiche le nom de la connexion ; Enregistrer désactivé tant que le champ est vide ; toggle œil pour afficher/masquer la clé ; après succès → toast « Clé API mise à jour », dialog fermée, préfixe rafraîchi dans la liste, badge Active (même si la connexion était auparavant en Erreur).
- Erreur
PATCH(validation, 404) : la dialog clé API reste ouverte, toast d'erreur, aucun changement côté store si la requête échoue. - Clé invalide en conditions réelles : après
PATCHréussi, parcourir la connexion avec une mauvaise clé → badge Erreur,markConnectionAsErroren base (sans rollback duPATCH). - Renommage + rotation simultanés via Swagger /
curl: un seulPATCHmet à jour les deux champs. - Garde
adminGuard: rechargement direct (F5) sur/connectionsavec session expirée → redirection vers/uploadsans erreur Vue Router non gérée ; rechargement avecgetMequi timeout → même redirection. - Routes ajoutées au passage (
/workflows,sources/workflows) : sidebar admin affiche Dépôt / Sources / Workflows / Connexions ; highlight actif sur/workflows. - Connection Browse (fiches) : sur
/connections/:uuid/browse, ressourceFiches, après sélection d'une fiche et chargement des soumissions, le bouton Créer une source est visible. - Redirection fiches : clic sur Créer une source depuis
card→/sources/create?connectionUuid=...&cardUuid=...&cardTitle=.... - Wizard source (card) : avec query
cardUuid, arrivée directe sur l'étape Nommer (parité avecformUuid). - Preview + submit card :
GET /sources/api-previewaveccardUuid, puisPOST /sourcesaveccardUuidetapiResourceType='card'.
7. Tests associés
Backend (unitaires)
back/src/api/connections/connections.service.spec.ts — bloc describe('updateConnection') :
- Renommage seul (trim, pas d'
encrypt). - Scope admin →
findFirstinclutenterprise_id. - Scope super-admin → pas de filtre entreprise.
- Rotation de clé seule :
encryptappelé, payload Prisma complet (api_key_encrypted,api_key_iv,api_key_prefix,status=ACTIVE,error_message=null,last_validated_atinstance deDaterécente), pas d'appelfetch//connection-check. - Renommage + rotation combinés : un seul
update, payload Prisma contenant les deux blocs. BadRequestExceptionsi DTO vide.BadRequestExceptionsinameetapiKeyblancs après trim.- Connexion introuvable →
ApiConnectionNotFoundException, pas d'effet de bord. markConnectionAsError: mise à jourstatus=ERRORsi connexion trouvée ; no-op si introuvable.back/src/api/sources/sources.service.spec.ts:createSourceAPI depuis formulaire (historique).createSourceAPI depuis fiche : brancheloadCardSubmissionscouverte.getApiFormPreviewavecapiResourceType='card': brancheloadCardSubmissionscouverte.
Frontend (unitaires)
front/src/__tests__/stores/connections-store.spec.js— blocupdateConnection:- Sans
connectionUuid→ retournenull, n'appelle pasapi.patch. apiKeyseul → payload{ apiKey }, mise à jour locale duapiKeyPrefix.name+apiKey→ payload combiné.- Valeurs non-string (number, null) → filtrées (payload
{}). - Connexion absente du state local → pas d'erreur, retour de la donnée serveur.
renamingConnectionUuidpositionné pendant la requête, reset ànullaprès.- Erreur API → flag remis à
nullviafinally, liste locale non mutée.
- Sans
front/src/__tests__/pages/connections/ConnectionsPage.spec.js:- Renommage : ouverture, validation (trim →
Nouveau nom), annulation, échec → toast erreur. - Vide après trim → pas d'appel
updateConnection. - Dialog clé API : ouverture, mise à jour réussie (payload
{ apiKey }, dialog fermée, toast succès), vide → pas d'appel, échec API simulé → dialog reste ouverte, annulation → fermeture sans appel. - Suppression : confirmation, refetch, toast succès.
- Renommage : ouverture, validation (trim →
front/src/__tests__/router/adminGuard.spec.js:- Autorise si
isAdmin. - Redirige
/uploadsi non-admin. - Charge le profil si
usernull. - Redirige
/uploadsigetMeéchoue.
- Autorise si
front/src/__tests__/router/routes.spec.js: 18 routes enfants, ajoutworkflows(avecsourcesGuard) et redirectsources/workflows → /workflows.front/src/__tests__/layouts/MainLayout.spec.js: sidebar admin = 4 liens (Dépôt / Sources / Workflows / Connexions), highlight/workflows.front/src/__tests__/pages/connections/ConnectionBrowsePage.spec.js:- redirection Créer une source avec
formUuid/formTitle(historique), - redirection Créer une source avec
cardUuid/cardTitle(nouveau).
- redirection Créer une source avec
front/src/__tests__/pages/admin/sources/AdminSourceCreatePage.spec.js:- preview + submit en mode API formulaire (historique),
- preview + submit en mode API fiche (
cardUuid,cardTitle,apiResourceType='card').
front/src/__tests__/stores/source-store.spec.js:getApiSourcePreviewsupporteformUuidetcardUuid,createSourcesupporte payload API fiche.
Frontend (e2e Playwright)
front/e2e/pages/connections/connections.spec.js :
- Bloc menu actions : présence de Consulter, Renommer, Modifier la clé API, Supprimer ; clic sur Consulter → redirection vers
/connections/:uuid/browse. - Bloc modification clé API :
- Succès : ouverture dialog, payload
{ apiKey: trimé }, toast « Clé API mise à jour », dialog fermée, nouveau préfixe affiché. - Bouton Enregistrer désactivé tant que la clé est vide.
- Échec
PATCHsimulé (ex. message d'erreur API) : dialog reste ouverte, toast erreur. - Annuler → dialog fermée, aucun
PATCH.
- Succès : ouverture dialog, payload
8. Notes d'implémentation
- Endpoint unifié plutôt que
PATCH /connections/:uuid/api-keyséparé : un seul handler, un seul DTO, une seule validation. Les UI front (édition inline / dialog) restent indépendantes côté UX. - La dialog clé API est délibérément différente de l'édition inline du nom : action sensible (change secrets chiffrés et statut affiché), un dialog modal réduit le risque de modification accidentelle. La validité réelle de la clé n'est pas testée à l'enregistrement : premier signal lors d'un parcours ou d'une source API.
- Le statut
ACTIVEreset sur rotation réussie permet de réparer une connexion auto-marquéeERROR(parmarkConnectionAsErrorlors d'un appel API qui aurait reçu un401) sans devoir recréer la connexion ni perdre les sourcesAPI_FORMrattachées. - La migration de
adminGuardenasync(ancienne signature(to, from, next) => next()) a été nécessaire pour résoudre un warning Vue Router 4 («nextis deprecated, return a value instead ») et pour pouvoir appelerawait getMe()avant la décision de routage. TestsadminGuard.spec.jsmis à jour en conséquence. - L'ajout des routes
workflows/sources/workflowsn'appartient pas au scope BDD-125 stricto sensu, mais a été synchronisé avecdevelopau passage ; les specsroutes.spec.jsetMainLayout.spec.jsont été ajustés pour refléter ces 2 routes supplémentaires. - Extension
cardimplémentée sans migration : les données restent dans le modèleAPI_FORMexistant. GET /sources/api-previewimpose un XOR strict entreformUuidetcardUuidpour éviter les ambiguïtés.- UX harmonisée : un seul bouton Créer une source dans
ConnectionBrowsePagepour formulaires et fiches.
9. Hors périmètre
- Rafraîchissement automatique d'une source
API_FORMaprès rotation de clé (re-sync des réponses) → BDD-119. - Audit log des modifications de connexion (qui a changé la clé, quand) → ticket dédié si besoin produit.
- Modification de l'URL de base d'une connexion (
baseUrl) : volontairement non exposée — une changement d'URL est considéré comme une nouvelle connexion (création + migration manuelle des sources). - Modification du type (
FORMULAIRES→ autre) : non applicable tant qu'il n'existe qu'un seulApiConnectionType.
