Skip to content

Commit 8ea53ff

Browse files
committed
fix: migrate suit signing keys script from shell to JavaScript
1 parent 2b11407 commit 8ea53ff

9 files changed

Lines changed: 262 additions & 196 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

SUIT-API.md renamed to docs/SUIT-API.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ API REST sécurisée pour récupérer les déclarations soumises et les fichiers
44

55
## Base URL
66

7-
- Alpha : `https://egapro-alpha.ovh.fabrique.social.gouv.fr`
7+
- Alpha : `https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1`
88

99
## Authentification
1010

@@ -42,23 +42,24 @@ export EGAPRO_SUIT_API_KEY="<clé fournie par EGAPRO>"
4242

4343
## Générer la signature pour toutes les routes (en une commande)
4444

45-
Équivalent shell du script `generate-suit-signature.js`. Signe toutes les routes SUIT avec le même `TS` et affiche les 3 headers + un `curl` prêt à copier pour chacune.
45+
Signe toutes les routes SUIT avec le même `TS` et affiche les 3 headers + un `curl` prêt à copier pour chacune.
4646

4747
Prérequis : `suit_private_key.pem` dans le dossier courant, `EGAPRO_SUIT_API_KEY` exporté, `BASE_URL` défini.
4848

4949
```sh
50-
BASE_URL="https://egapro-alpha.ovh.fabrique.social.gouv.fr"
50+
BASE_URL="https://egapro-alpha.ovh.fabrique.social.gouv.fr/api/v1"
51+
API_PREFIX="/api/v1"
5152
TS=$(date +%s)
5253

5354
# Remplacer <fileId> par l'identifiant du fichier à télécharger.
5455
for ROUTE in \
55-
"GET /api/v1/export/declarations" \
56-
"GET /api/v1/files" \
57-
"GET /api/v1/files/<fileId>"
56+
"GET /export/declarations" \
57+
"GET /files" \
58+
"GET /files/<fileId>"
5859
do
5960
METHOD="${ROUTE%% *}"
60-
FULL_PATH="${ROUTE#* }"
61-
PATHNAME="${FULL_PATH%%\?*}"
61+
SUBPATH="${ROUTE#* }"
62+
PATHNAME="$API_PREFIX$SUBPATH"
6263
SIG=$(printf '%s|%s|%s' "$TS" "$METHOD" "$PATHNAME" \
6364
| openssl dgst -sha256 -sign suit_private_key.pem \
6465
| openssl base64 -A)
@@ -70,12 +71,13 @@ do
7071
echo " -H 'Authorization: Bearer '\"\$EGAPRO_SUIT_API_KEY\" \\"
7172
echo " -H 'X-Timestamp: $TS' \\"
7273
echo " -H 'X-Signature: $SIG' \\"
73-
echo " '$BASE_URL$FULL_PATH'"
74+
echo " '$BASE_URL$SUBPATH'"
7475
echo
7576
done
7677
```
7778

78-
Fenêtre de validité : **30 jours** sur alpha/dev, **30 secondes** sur preprod/prod. Au-delà → 403, il faut regénérer `TS` + `SIG`.
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`.
7981
8082
## Générer pour une seule route
8183

@@ -95,7 +97,7 @@ echo "X-Signature: $SIG"
9597
### 1. Exporter les déclarations
9698

9799
```sh
98-
curl "$BASE_URL/api/v1/export/declarations?date_begin=2026-01-01&date_end=2026-01-31" \
100+
curl "$BASE_URL/export/declarations?date_begin=2026-01-01&date_end=2026-01-31" \
99101
-H "Authorization: Bearer $EGAPRO_SUIT_API_KEY" \
100102
-H "X-Timestamp: $TS" \
101103
-H "X-Signature: $SIG"
@@ -107,7 +109,7 @@ curl "$BASE_URL/api/v1/export/declarations?date_begin=2026-01-01&date_end=2026-0
107109
### 2. Lister les fichiers d'une déclaration
108110

109111
```sh
110-
curl "$BASE_URL/api/v1/files?siren=123456789&year=2026" \
112+
curl "$BASE_URL/files?siren=123456789&year=2026" \
111113
-H "Authorization: Bearer $EGAPRO_SUIT_API_KEY" \
112114
-H "X-Timestamp: $TS" \
113115
-H "X-Signature: $SIG"
@@ -118,13 +120,13 @@ curl "$BASE_URL/api/v1/files?siren=123456789&year=2026" \
118120
### 3. Télécharger un fichier
119121

120122
```sh
121-
curl -OJ "$BASE_URL/api/v1/files/<fileId>" \
123+
curl -OJ "$BASE_URL/files/<fileId>" \
122124
-H "Authorization: Bearer $EGAPRO_SUIT_API_KEY" \
123125
-H "X-Timestamp: $TS" \
124126
-H "X-Signature: $SIG"
125127
```
126128

127-
Le `fileId` est renvoyé par l'endpoint `/api/v1/files`.
129+
Le `fileId` est renvoyé par l'endpoint `/files`.
128130

129131
## Réponses d'erreur
130132

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)