#Clés
Trois opérations sur /api/cles : lister, créer, révoquer. Elles se distinguent
de toutes les autres routes de l’API par un point, et c’est le plus important de
la page — une clé d’API ne peut pas les appeler.
#Pourquoi une clé ne fabrique pas de clés
Le scénario qu’on refuse est précis. Une clé fuite : dans un dépôt public, dans
les journaux d’un CI, sur le poste d’un prestataire. Si cette clé pouvait
appeler POST /api/cles, son porteur s’en fabriquerait dix autres en une
seconde, chacune avec sa propre empreinte, sans laisser trace de l’original. Le
jour où l’on repère la fuite et où l’on révoque la clé compromise, les dix
autres continuent de fonctionner : la révocation ne révoque plus rien, et le
vol devient permanent.
En exigeant une session — donc un mot de passe, donc un accès au compte lui-même — on garantit qu’une clé ne peut jamais qu'agir, jamais se reproduire. C’est ce qui rend le bouton « Révoquer » de l’écran des réglages honnête.
Le refus est explicite et vient avant la lecture des cookies : une requête
munie d’une clé ET d’une session valable reçoit 401 unauthorized. Sans ce
refus d’entrée, la garde se contournerait par simple juxtaposition des deux
modes d’authentification.
Conséquence pratique : ces trois routes s’appellent depuis un navigateur
connecté au CMS, ou pas du tout. Le CLI ne fabrique pas de clés — sa commande
connexion en enregistre une, elle ne la crée pas.
#Lister
/api/cles{
"cles": [
{
"id": "3f7b1c2e-8a41-4d5f-9c33-6b0e2a7d1f84",
"nom": "Poste de travail",
"prefixe": "wc_kJ8pQx3nR",
"portee": "themes",
"cree_le": "2026-06-02T08:41:19.220Z",
"expire_le": null,
"derniere_utilisation": "2026-08-05T07:55:03.981Z",
"revoquee_le": null
},
{
"id": "a1d9e004-77bc-4f18-b2aa-51c8d3e6f902",
"nom": "CI — déploiement",
"prefixe": "wc_Zq4mT1vX8",
"portee": "themes",
"cree_le": "2026-01-14T10:02:55.001Z",
"expire_le": "2026-07-14T10:02:55.001Z",
"derniere_utilisation": "2026-07-13T23:11:40.512Z",
"revoquee_le": "2026-07-02T16:20:08.117Z"
}
]
}{ "error": "unauthorized" }{ "error": "load_failed" }Les clés révoquées restent dans la liste, grisées dans l’interface. Une ligne
qui disparaît laisse un doute — « l’ai-je vraiment révoquée ? » — qu’on ne peut
plus lever. Filtre sur revoquee_le si tu n’en veux pas.
empreinte n’est jamais rendue. Non pas qu’elle révélerait le secret — un
SHA-256 ne se renverse pas — mais parce qu’une empreinte qui circule finit
copiée dans un journal, et qu’un champ qu’on ne renvoie jamais est un champ
qu’on ne fuite jamais.
iduuidL’identifiant de la ligne. C’est lui qu’on passe à la révocation.
nomchaîneCe que tu as écrit à la création. Il n’a aucun rôle technique et tout un rôle opérationnel : après une fuite, c’est lui qui dit sur quelle machine aller chercher.
prefixechaîneLes douze premiers caractères du secret, wc_ compris. Aucun rôle de
sécurité : il existe pour que tu reconnaisses ta clé dans une liste de
cinq. Sans lui, révoquer la bonne relève du tirage au sort — et on finit par tout
révoquer « au cas où », c’est-à-dire par casser les intégrations qui marchaient.
porteethemes | lectureVoir Authentification pour ce que chacune ouvre.
cree_lehorodatageDate de création, en ISO 8601 UTC.
expire_lehorodatage ou nullnull signifie « ne périme pas ». C’est le cas d’usage « poste de travail » ;
une clé de CI, elle, a tout intérêt à porter une date.
derniere_utilisationhorodatage ou nullRéécrite à chaque requête authentifiée par cette clé, dans la même opération
que la vérification. C’est ce qui permet de répondre à « cette clé sert-elle
encore ? » avant de la révoquer, puis à « depuis quand ? » après une fuite.
null = jamais servie.
revoquee_lehorodatage ou nullNon nul = la clé est morte. Elle n’est plus acceptée nulle part, et la ligne reste pour l’histoire.
#Créer
/api/clesCorps de la requête
{ "nom": "CI — déploiement", "portee": "themes", "jours": 90 }{
"cle": {
"id": "a1d9e004-77bc-4f18-b2aa-51c8d3e6f902",
"nom": "CI — déploiement",
"prefixe": "wc_Zq4mT1vX8",
"portee": "themes",
"cree_le": "2026-08-05T09:30:00.000Z",
"expire_le": "2026-11-03T09:30:00.000Z",
"derniere_utilisation": null,
"revoquee_le": null
},
"secret": "wc_Zq4mT1vX8pR2nK5hL9wB4cF6hN1sD0aG8eU3iO7pK"
}{ "error": "invalid_input", "message": "String must contain at least 1 character(s)" }{
"error": "trop_de_cles",
"message": "Tu as déjà 20 clés en service. Révoque celles dont tu ne te sers plus avant d'en créer une nouvelle."
}nomchaînerequisEntre 1 et 60 caractères, espaces de bordure retirés. Nommez la machine, pas l’intention : six mois plus tard, deux clés appelées « CI » ne se distinguent plus, alors que « GitHub Actions — vitrine Durand » dit où aller la retirer.
porteethemes | lecturedéfaut : themesToute autre valeur vaut 400 invalid_input. Le défaut est themes parce que
l’usage écrasant est le CLI : une clé en lecture seule qui refuse un pousser
produit un message d’erreur que personne ne relie à une portée choisie trois
semaines plus tôt.
joursentierDurée de vie, de 1 à 365 jours. Absent, la clé ne périme pas.
Une durée plutôt qu’une date : l’appelant n’a pas à fabriquer un ISO 8601 correct, et on évite de recevoir une date déjà passée.
secret n’apparaît que dans cette réponse, et il n’y a aucun autre endroit
d’où le tirer — ni GET /api/cles, ni la base, ni un journal. Écris-le au
moment où tu le reçois. Si tu perds cette réponse, la seule issue est de
révoquer la clé et d’en créer une autre. C’est la bonne issue : toute autre
supposerait qu’on en a gardé une copie quelque part.
#Le plafond
Vingt clés vivantes par compte — ni révoquées, ni périmées. Révoquer ou laisser périmer libère la place.
Le plafond n’est pas là pour économiser des lignes en base : vingt lignes ne coûtent rien. Il est là parce qu’une liste de clés qu’on ne relit plus est une liste qu’on ne révoque plus. Passé une trentaine d’entrées, plus personne ne sait à quoi sert « clé-2 » ni sur quelle machine elle traîne, et la seule décision sûre — tout révoquer — devient trop coûteuse pour être prise. Vingt tient à l’écran.
#Révoquer
/api/cles?id=<uuid>{ "ok": true }{ "error": "invalid_input" }{ "error": "introuvable" }Malgré la méthode, rien n’est supprimé : la révocation écrit revoquee_le et
laisse la ligne. Une clé compromise est justement celle dont on veut continuer à
lire l’histoire — sa derniere_utilisation dit si elle a servi après la fuite,
son nom dit où aller chercher. Un vrai delete effacerait les deux au moment
précis où ils deviennent utiles.
Révoquer deux fois rend le même résultat que révoquer une fois. Une clé déjà
révoquée répond 200 ok, et sa date de révocation initiale n’est pas réécrite —
c’est la seule information qui dise quand on a réagi. Un client qui rejoue sa
requête après un délai réseau ne va donc pas chercher un problème qui n’existe
pas.
En revanche, une clé inconnue répond 404. C’est le seul mensonge que cette
route ne peut pas se permettre : répondre ok pour une révocation qui n’a jamais
eu lieu ferait croire qu’une clé est morte alors qu’elle vit.
L’identifiant est validé comme uuid avant d’atteindre la base. Sans cela,
?id=nimporte-quoi ferait échouer la comparaison côté Postgres et l’appelant
recevrait un 500 pour ce qui est une faute de sa part.
#Une clé du début à la fin
La créer, depuis le navigateur
Réglages → Clés d’API dans le CMS, ou l’appel ci-dessus depuis la console du navigateur — les cookies de session partent avec,
credentialspar défaut suffit.L’enregistrer hors du dépôt
Le CLI la met dans
~/.webcosa/config.json, en0600. Un CI la met dans ses secrets. Ce qu’on ne fait pas : un.webcosa.jsonde projet, qui est versionné et finira poussé sur une forge le jour même.Vérifier qu’elle répond
Le plus court des appels authentifiés, qui ne modifie rien :
curl -s -o /dev/null -w "%{http_code}\n" \https://cms.webcosa.com/api/themes \-H "Authorization: Bearer $WEBCOSA_CLE"La révoquer quand la machine part
Un poste rendu, un prestataire qui s’en va, un CI démonté : la clé se révoque même si personne ne pense qu’elle a fuité. Le coût est nul, et c’est ce qui maintient la liste sous vingt.

