Aller au contenu

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

Danger

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

GET/api/cles
200
json
{
  "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"
    }
  ]
}
401 — pas de session, ou une clé présentée
json
{ "error": "unauthorized" }
500 — lecture impossible
json
{ "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.

iduuid

L’identifiant de la ligne. C’est lui qu’on passe à la révocation.

nomchaîne

Ce 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îne

Les 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 | lecture

Voir Authentification pour ce que chacune ouvre.

cree_lehorodatage

Date de création, en ISO 8601 UTC.

expire_lehorodatage ou null

null 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 null

Réé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 null

Non nul = la clé est morte. Elle n’est plus acceptée nulle part, et la ligne reste pour l’histoire.

#Créer

POST/api/cles

Corps de la requête

json
{ "nom": "CI — déploiement", "portee": "themes", "jours": 90 }
201
json
{
  "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"
}
400 — corps invalide
json
{ "error": "invalid_input", "message": "String must contain at least 1 character(s)" }
409 — plafond atteint
json
{
  "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înerequis

Entre 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 : themes

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

joursentier

Duré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.

Danger

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

DELETE/api/cles?id=<uuid>
200
json
{ "ok": true }
400 — identifiant absent ou mal formé
json
{ "error": "invalid_input" }
404 — clé inconnue, ou d’un autre compte
json
{ "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.

Note

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

  1. 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, credentials par défaut suffit.

  2. L’enregistrer hors du dépôt

    Le CLI la met dans ~/.webcosa/config.json, en 0600. Un CI la met dans ses secrets. Ce qu’on ne fait pas : un .webcosa.json de projet, qui est versionné et finira poussé sur une forge le jour même.

  3. 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"
  4. 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.