Skip to content

Commit 4091599

Browse files
authored
docs(suit): add API reference documentation (#3284)
1 parent 19fcd32 commit 4091599

9 files changed

Lines changed: 404 additions & 182 deletions

File tree

.claude/rules/styling-dsfr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Never write raw `@media (max-width: 768px)`.
6161

6262
## DSFR runtime
6363

64-
- **Assets**: copied to `public/dsfr/` by `scripts/copy-dsfr.js` (git-ignored, regenerated on `dev`/`build`). Never import DSFR CSS via webpack.
64+
- **Assets**: copied to `public/dsfr/` by `scripts/copy-dsfr.mjs` (git-ignored, regenerated on `dev`/`build`). Never import DSFR CSS via webpack.
6565
- **JS**: loaded via `<Script type="module" strategy="beforeInteractive">`. Handles modals, dropdowns, theme toggle, keyboard navigation. Never duplicate this logic in React — use `data-fr-*` attributes.
6666
- **Dark mode**: `data-fr-scheme="system"` on `<html>`, cookie `fr-theme` read by inline script to avoid flash, `ThemeModal` for user toggle.
6767
- **Icons**: `fr-icon-{name}-{fill|line}` classes. Always `aria-hidden="true"` on decorative icons.

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,12 @@ Implémenté dans `packages/app/src/server/services/suitApiAuth.ts`.
193193

194194
```bash
195195
# Première génération
196-
./packages/app/scripts/generate-suit-signing-keys.sh generate dev # → ./suit-signing-keys/dev/
197-
./packages/app/scripts/generate-suit-signing-keys.sh generate prod # → ./suit-signing-keys/prod/
198-
./packages/app/scripts/generate-suit-signing-keys.sh generate all # → les deux
196+
node packages/app/scripts/generate-suit-signing-keys.mjs generate dev # → ./suit-signing-keys/dev/
197+
node packages/app/scripts/generate-suit-signing-keys.mjs generate prod # → ./suit-signing-keys/prod/
198+
node packages/app/scripts/generate-suit-signing-keys.mjs generate all # → les deux
199199

200200
# Rotation (sauvegarde les anciennes clés, génère de nouvelles)
201-
./packages/app/scripts/generate-suit-signing-keys.sh renew prod
201+
node packages/app/scripts/generate-suit-signing-keys.mjs renew prod
202202
```
203203

204204
`generate` refuse d'écraser des clés existantes. `renew` les sauvegarde dans un dossier `backup-{date}` avant de regénérer.
@@ -229,15 +229,15 @@ Implémenté dans `packages/app/src/server/services/suitApiAuth.ts`.
229229

230230
**Option B (script Node fourni)** — signe **toutes les routes SUIT protégées** en une seule exécution et affiche les `curl` prêts à copier :
231231
```bash
232-
node packages/app/scripts/generate-suit-signature.js \
232+
node packages/app/scripts/generate-suit-signature.mjs \
233233
--key-file ./suit-signing-keys/dev/suit-signing.key
234234
```
235235

236236
Le script itère automatiquement sur les routes SUIT (actuellement `GET /api/v1/export/declarations` et `GET /api/v1/files`). Pour signer `GET /api/v1/files/<id>`, passer `--file-id <uuid>`. Pour surcharger l'URL cible, passer `--url https://egapro-preprod.fabrique.social.gouv.fr`. Voir `--help` pour toutes les options. Il suffit ensuite d'exporter `EGAPRO_SUIT_API_KEY=<api-key>` et d'exécuter un des `curl` affichés.
237237

238238
### Procédure de rotation
239239

240-
1. `./packages/app/scripts/generate-suit-signing-keys.sh renew <env>` — génère une nouvelle paire, sauvegarde l'ancienne
240+
1. `node packages/app/scripts/generate-suit-signing-keys.mjs renew <env>` — génère une nouvelle paire, sauvegarde l'ancienne
241241
2. Mettre à jour le sealed-secret K8s avec la nouvelle clé publique
242242
3. Déployer EgaPro
243243
4. Transmettre la nouvelle clé privée à SUIT — ils doivent basculer juste après le déploiement

docs/SUIT-API.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# API EGAPRO — Équipe SUIT
2+
3+
API REST sécurisée pour récupérer les déclarations soumises et les fichiers (avis CSE, évaluations conjointes).
4+
5+
## Base URL
6+
7+
- Alpha : `https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1`
8+
9+
## Authentification
10+
11+
Chaque requête doit porter **trois en-têtes** :
12+
13+
| Header | Valeur |
14+
| --- | --- |
15+
| `Authorization` | `Bearer <EGAPRO_SUIT_API_KEY>` |
16+
| `X-Timestamp` | Epoch en secondes (UTC) |
17+
| `X-Signature` | Signature RSA-SHA256 (base64) du payload `{timestamp}\|{METHOD}\|{pathname}` avec la clé privée SUIT |
18+
19+
Fenêtre anti-replay : **30 jours** en dev/alpha, **30 secondes** en preprod/prod.
20+
21+
## Générer la paire de clés RSA
22+
23+
À faire **une seule fois** côté SUIT. Garder la clé privée en lieu sûr (coffre, secret manager).
24+
25+
```sh
26+
openssl genrsa -out suit_private_key.pem 4096
27+
openssl rsa -in suit_private_key.pem -pubout -out suit_public_key.pem
28+
```
29+
30+
Encoder la clé publique en base64 et la transmettre à l'équipe EGAPRO (qui l'injectera dans `EGAPRO_SUIT_PUBLIC_KEY_PEM`) :
31+
32+
```sh
33+
base64 -w0 suit_public_key.pem # Linux
34+
base64 -i suit_public_key.pem | tr -d '\n' # macOS
35+
```
36+
37+
En retour, EGAPRO fournit la clé d'API (Bearer) à exporter :
38+
39+
```sh
40+
export EGAPRO_SUIT_API_KEY="<clé fournie par EGAPRO>"
41+
```
42+
43+
## Générer la signature pour toutes les routes (en une commande)
44+
45+
Signe toutes les routes SUIT avec le même `TS` et affiche les 3 headers + un `curl` prêt à copier pour chacune.
46+
47+
Prérequis : `suit_private_key.pem` dans le dossier courant, `EGAPRO_SUIT_API_KEY` exporté, `BASE_URL` défini.
48+
49+
```sh
50+
BASE_URL="https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1"
51+
API_PREFIX="/api/v1"
52+
TS=$(date +%s)
53+
54+
# Remplacer <fileId> par l'identifiant du fichier à télécharger.
55+
for ROUTE in \
56+
"GET /export/declarations" \
57+
"GET /files" \
58+
"GET /files/<fileId>"
59+
do
60+
METHOD="${ROUTE%% *}"
61+
SUBPATH="${ROUTE#* }"
62+
PATHNAME="$API_PREFIX$SUBPATH"
63+
SIG=$(printf '%s|%s|%s' "$TS" "$METHOD" "$PATHNAME" \
64+
| openssl dgst -sha256 -sign suit_private_key.pem \
65+
| openssl base64 -A)
66+
echo "=== $METHOD $PATHNAME ==="
67+
echo "X-Timestamp: $TS"
68+
echo "X-Signature: $SIG"
69+
echo
70+
echo "curl -X $METHOD \\"
71+
echo " -H 'Authorization: Bearer '\"\$EGAPRO_SUIT_API_KEY\" \\"
72+
echo " -H 'X-Timestamp: $TS' \\"
73+
echo " -H 'X-Signature: $SIG' \\"
74+
echo " '$BASE_URL$SUBPATH'"
75+
echo
76+
done
77+
```
78+
79+
> La signature porte sur le **pathname complet** reçu par le serveur (`/api/v1/...`), pas sur le sous-chemin relatif à `BASE_URL`.
80+
> Au-delà de la fenêtre anti-replay (cf. section Authentification), la requête renverra 403 — regénérer `TS` + `SIG`.
81+
82+
## Générer pour une seule route
83+
84+
```sh
85+
TS=$(date +%s)
86+
METHOD=GET
87+
PATHNAME=/api/v1/export/declarations
88+
SIG=$(printf '%s|%s|%s' "$TS" "$METHOD" "$PATHNAME" \
89+
| openssl dgst -sha256 -sign suit_private_key.pem \
90+
| openssl base64 -A)
91+
echo "X-Timestamp: $TS"
92+
echo "X-Signature: $SIG"
93+
```
94+
95+
## Endpoints
96+
97+
### 1. Exporter les déclarations
98+
99+
```sh
100+
curl "$BASE_URL/export/declarations?date_begin=2026-01-01&date_end=2026-01-31" \
101+
-H "Authorization: Bearer $EGAPRO_SUIT_API_KEY" \
102+
-H "X-Timestamp: $TS" \
103+
-H "X-Signature: $SIG"
104+
```
105+
106+
- `date_begin` (obligatoire, `YYYY-MM-DD`) : date de début incluse
107+
- `date_end` (optionnel, `YYYY-MM-DD`) : date de fin exclue. Par défaut : `date_begin + 1 jour`
108+
109+
### 2. Lister les fichiers d'une déclaration
110+
111+
```sh
112+
curl "$BASE_URL/files?siren=123456789&year=2026" \
113+
-H "Authorization: Bearer $EGAPRO_SUIT_API_KEY" \
114+
-H "X-Timestamp: $TS" \
115+
-H "X-Signature: $SIG"
116+
```
117+
118+
- `siren` (9 chiffres) et `year` (`YYYY`) obligatoires
119+
120+
### 3. Télécharger un fichier
121+
122+
```sh
123+
curl -OJ "$BASE_URL/files/<fileId>" \
124+
-H "Authorization: Bearer $EGAPRO_SUIT_API_KEY" \
125+
-H "X-Timestamp: $TS" \
126+
-H "X-Signature: $SIG"
127+
```
128+
129+
Le `fileId` est renvoyé par l'endpoint `/files`.
130+
131+
## Réponses d'erreur
132+
133+
| Code | Cause |
134+
| --- | --- |
135+
| `400` | Paramètres invalides |
136+
| `401` | Clé API manquante ou invalide |
137+
| `403` | Signature manquante, invalide ou timestamp hors fenêtre |
138+
| `404` | Fichier introuvable |
139+
| `500` | Erreur serveur |
140+
141+
## Documentation OpenAPI
142+
143+
Disponible hors production (désactivée en prod) :
144+
145+
- Swagger UI : `https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1/docs`
146+
- Spec JSON : `https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1/openapi.json`
147+
148+
## Tester via Swagger UI
149+
150+
Swagger UI ne signe pas les requêtes. Générer `X-Timestamp` + `X-Signature` en shell (bloc **Générer pour une seule route** ci-dessus, en adaptant `PATHNAME`), puis ouvrir Swagger :
151+
152+
```sh
153+
open "https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1/docs"
154+
```
155+
156+
1. Coller `X-Timestamp`, `X-Signature` et `Authorization: Bearer $EGAPRO_SUIT_API_KEY` dans **Authorize**
157+
2. Lancer la requête via **Try it out**
158+
3. 403 → regénérer `TS` + `SIG` (fenêtre 30 s en preprod/prod, 30 j en dev/alpha)

packages/app/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ Cascade: 1) DSFR classes → 2) DSFR utilities + CSS custom properties → 3) Sc
201201

202202
### DSFR runtime
203203

204-
- **Assets**: copied to `public/dsfr/` by `scripts/copy-dsfr.js` (git-ignored, regenerated on `dev`/`build`). Never import DSFR CSS via webpack.
204+
- **Assets**: copied to `public/dsfr/` by `scripts/copy-dsfr.mjs` (git-ignored, regenerated on `dev`/`build`). Never import DSFR CSS via webpack.
205205
- **JS**: loaded via `<Script type="module" strategy="beforeInteractive">`. Handles modals, dropdowns, theme toggle, keyboard navigation. Never duplicate this logic in React — use `data-fr-*` attributes.
206206
- **Dark mode**: `data-fr-scheme="system"` on `<html>`, cookie `fr-theme` read by inline script to avoid flash, `ThemeModal` for user toggle.
207207
- **Icons**: `fr-icon-{name}-{fill|line}` classes. Always `aria-hidden="true"` on decorative icons.

packages/app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"build": "node scripts/copy-dsfr.js && node scripts/copy-swagger-ui.mjs && next build",
7+
"build": "node scripts/copy-dsfr.mjs && node scripts/copy-swagger-ui.mjs && next build",
88
"check": "biome check .",
99
"check:unsafe": "biome check --write --unsafe .",
1010
"check:write": "biome check --write .",
@@ -16,7 +16,7 @@
1616
"db:migrate": "drizzle-kit migrate",
1717
"db:push": "drizzle-kit push",
1818
"db:studio": "drizzle-kit studio",
19-
"dev": "node scripts/copy-dsfr.js && node scripts/copy-swagger-ui.mjs && next dev --turbo",
19+
"dev": "node scripts/copy-dsfr.mjs && node scripts/copy-swagger-ui.mjs && next dev --turbo",
2020
"preview": "next build && next start",
2121
"start": "next start",
2222
"test": "vitest run",

packages/app/scripts/generate-suit-signature.js renamed to packages/app/scripts/generate-suit-signature.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Par défaut, signe l'ensemble des routes SUIT connues en une seule exécution.
119119
Passer --path pour signer une route unique (custom).
120120
121121
Usage :
122-
node scripts/generate-suit-signature.js [options]
122+
node scripts/generate-suit-signature.mjs [options]
123123
124124
Options :
125125
--key-file <path> Chemin vers la clé privée PEM

0 commit comments

Comments
 (0)