@mostajs/pdf-sign-ui 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@
4
4
 
5
5
  Le format suit [Keep a Changelog](https://keepachangelog.com/fr/) ; versionnage [SemVer](https://semver.org/lang/fr/).
6
6
 
7
+ ## [0.3.0] — 2026-09-26
8
+
9
+ ### Ajouté
10
+ - `buildSignRequest` transmet **`timestampUrl`** (horodatage RFC 3161, `@mostajs/pdf-sign` 0.3.0).
11
+ L'option s'y perdait **en silence** : le document sortait sans horodatage alors que l'appelant
12
+ croyait la date attestée par un tiers — donc opposable. Découvert en production.
13
+ - **Avertissement sur les clés perdues.** C'est la DEUXIÈME option évanouie de la même façon après
14
+ `openPassword` en 0.2.0, et le commentaire du code mettait pourtant en garde contre ce piège
15
+ exact. Un jeu de clés fermé est défendable ; le fermer **en silence** ne l'est pas.
16
+ `buildSignRequest` nomme désormais les options qu'il ignore.
17
+
18
+ On avertit sans lever : lever casserait un appelant passant une clé supplémentaire inoffensive,
19
+ alors que le but est seulement que la perte cesse d'être invisible.
20
+
21
+ ### Modifié
22
+ - `peerDependencies["@mostajs/pdf-sign"]` : `^0.2.0` → `^0.3.0`.
23
+
24
+
25
+ ## 0.2.0 — 2026-09-21
26
+
27
+ ### Ajouté
28
+ - `buildSignRequest` transmet **`openPassword`** (mot de passe à l'ouverture, `@mostajs/pdf-sign`
29
+ 0.2.0). Le jeu de clés recopié est **fermé** : l'option y disparaissait sans erreur, et le
30
+ document sortait déverrouillé alors que l'appelant le croyait fermé.
31
+ - `validateSignForm` refuse `openPassword` quand le moteur ne peut pas chiffrer, avec un message
32
+ **qui le nomme** — renvoyer celui des permissions envoyait chercher la panne du côté des cases
33
+ à cocher, qui n'y sont pour rien.
34
+ - `buildEngineViewModel` expose `canLock`.
35
+
36
+ ### Modifié (RUPTURE d'installation)
37
+ - `peerDependencies["@mostajs/pdf-sign"]` : `^0.1.0` → `^0.2.0`. La 0.1.0 refusait le moteur 0.2.0
38
+ et faisait **échouer `npm install`** (ERESOLVE) chez tout consommateur des deux modules.
39
+
7
40
  ## [0.1.0] — 2026-07-01
8
41
 
9
42
  ### Ajouté
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # @mostajs/pdf-sign-ui — fiche LLM
2
- > Couche de PRÉSENTATION de @mostajs/pdf-sign : logique HEADLESS (validation du formulaire de signature, préréglages de documents officiels/SIH, view-models moteur & vérification, contrôleur orchestrant un `service` injecté). Framework-agnostique, zéro dépendance, testable sans navigateur. IMPLÉMENTÉ 0.1.0.
2
+ > Couche de PRÉSENTATION de @mostajs/pdf-sign : logique HEADLESS (validation du formulaire de signature, préréglages de documents officiels/SIH, view-models moteur & vérification, contrôleur orchestrant un `service` injecté). Framework-agnostique, zéro dépendance, testable sans navigateur. IMPLÉMENTÉ 0.2.0.
3
3
 
4
- - Version: 0.1.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
4
+ - Version: 0.2.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
5
5
  - Chemin: mostajs/mosta-pdf-sign-stack/mosta-pdf-sign-ui · Moteur: ../mosta-pdf-sign
6
6
 
7
7
  ## RÔLE
@@ -41,3 +41,11 @@ Composants en React.createElement (pas de JSX à transpiler ; bundler pour les .
41
41
  - box = [x1,y1,x2,y2] nombres finis ; sinon rejet. t (i18n) optionnel → identité.
42
42
  - Composants React sans build JSX (React.createElement) : Node natif ne charge pas .jsx → test via loader (test:react) ; en prod un bundler résout .jsx.
43
43
  - field.stampImage propagé par buildSignRequest (cachet image, moteur pyhanko).
44
+
45
+ ## 0.2.0 — MOT DE PASSE D'OUVERTURE
46
+ - buildSignRequest transmet `openPassword` (jeu de clés FERMÉ : avant 0.2.0 il le PERDAIT en
47
+ silence — document réputé verrouillé, et ouvrable).
48
+ - validateSignForm refuse `openPassword` sans pyhanko / avec engine node, avec un message QUI LE
49
+ NOMME (et non celui des permissions).
50
+ - buildEngineViewModel expose `canLock`.
51
+ - peerDependency @mostajs/pdf-sign passe à ^0.2.0 (l'option n'existe pas avant).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/pdf-sign-ui",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Couche de présentation de @mostajs/pdf-sign — logique headless (validation du formulaire de signature, préréglages de documents officiels/SIH, view-models de vérification, contrôleur). Framework-agnostique, zéro dépendance.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
@@ -10,19 +10,44 @@
10
10
  ".": "./src/index.js",
11
11
  "./react": "./src/react/index.js"
12
12
  },
13
- "files": ["src", "README.md", "llms.txt", "CHANGELOG.md"],
13
+ "files": [
14
+ "src",
15
+ "README.md",
16
+ "llms.txt",
17
+ "CHANGELOG.md"
18
+ ],
14
19
  "scripts": {
15
20
  "test": "node test-scripts/headless.test.mjs",
16
21
  "test:react": "node --experimental-loader ./test-scripts/jsx-loader.mjs test-scripts/react.test.mjs",
17
22
  "example": "node examples/verify-viewmodel.mjs"
18
23
  },
19
24
  "peerDependencies": {
20
- "@mostajs/pdf-sign": "^0.1.0",
25
+ "@mostajs/pdf-sign": "^0.3.0",
21
26
  "react": ">=18"
22
27
  },
23
28
  "peerDependenciesMeta": {
24
- "@mostajs/pdf-sign": { "optional": true },
25
- "react": { "optional": true }
29
+ "@mostajs/pdf-sign": {
30
+ "optional": true
31
+ },
32
+ "react": {
33
+ "optional": true
34
+ }
26
35
  },
27
- "keywords": ["mostajs", "pdf", "signature", "ui", "headless", "sih", "pades"]
36
+ "keywords": [
37
+ "mostajs",
38
+ "pdf",
39
+ "signature",
40
+ "ui",
41
+ "headless",
42
+ "sih",
43
+ "pades"
44
+ ],
45
+ "mostajs": {
46
+ "niveau": "N2",
47
+ "nature": "compose",
48
+ "reutilisabilite": "multi-domaine",
49
+ "depend_de": [
50
+ "@mostajs/pdf-sign"
51
+ ]
52
+ }
28
53
  }
package/src/headless.js CHANGED
@@ -12,7 +12,7 @@ const IDENTITY = (s) => s;
12
12
 
13
13
  /**
14
14
  * Valide le formulaire de signature.
15
- * @param {object} data { input, p12, passphrase, passphraseFile, engine, box, page, permissions, preset }
15
+ * @param {object} data { input, p12, passphrase, passphraseFile, engine, box, page, permissions, openPassword, preset }
16
16
  * @param {object} [opts] { engines?: {node,pyhanko}, t?: (k)=>string }
17
17
  * @returns {{ valid: boolean, errors: Record<string,string> }}
18
18
  */
@@ -37,6 +37,13 @@ export function validateSignForm(data = {}, opts = {}) {
37
37
  if (engine === "node") errors.permissions = t("Le cœur Node ne gère pas les permissions — choisir pyhanko.");
38
38
  else if (!engines.pyhanko) errors.permissions = t("Permissions restrictives indisponibles : moteur pyhanko requis.");
39
39
  }
40
+
41
+ // Le mot de passe d'ouverture est un CHIFFREMENT, et il a son propre message : renvoyer celui
42
+ // des permissions à qui n'a saisi qu'une clé l'enverrait chercher du côté des cases à cocher.
43
+ if (data.openPassword) {
44
+ if (engine === "node") errors.openPassword = t("Le cœur Node ne chiffre pas — choisir pyhanko pour un mot de passe d'ouverture.");
45
+ else if (!engines.pyhanko) errors.openPassword = t("Mot de passe d'ouverture indisponible : moteur pyhanko requis.");
46
+ }
40
47
  if (engine === "pyhanko" && !engines.pyhanko) {
41
48
  errors.engine = t("Moteur pyhanko indisponible sur ce serveur.");
42
49
  }
@@ -61,6 +68,33 @@ function isBox(b) {
61
68
  * @param {object} data
62
69
  * @returns {import("@mostajs/pdf-sign").SignOptions}
63
70
  */
71
+ /**
72
+ * Les clés que `buildSignRequest` sait recopier. Tout le reste est PERDU.
73
+ *
74
+ * ⚠️ Deux options s'y sont déjà évanouies sans bruit : `openPassword` — le document sortait
75
+ * déverrouillé alors que l'appelant le croyait fermé — puis `timestampUrl` — le document sortait
76
+ * sans horodatage alors que l'appelant croyait la date attestée. Deux fois, le même mécanisme, et
77
+ * deux fois découvert en production.
78
+ *
79
+ * Fermer le jeu est un choix défendable ; le fermer EN SILENCE ne l'est pas. On avertit donc, sans
80
+ * lever : lever casserait un appelant qui passe une clé supplémentaire inoffensive, alors que le
81
+ * but est seulement que la perte cesse d'être invisible.
82
+ */
83
+ const CLES_CONNUES = new Set([
84
+ 'input', 'output', 'p12', 'passphrase', 'passphraseFile', 'engine',
85
+ 'reason', 'name', 'location', 'contact', 'preset',
86
+ 'page', 'box', 'stampImage', 'permissions', 'restrict',
87
+ 'openPassword', 'timestampUrl',
88
+ ]);
89
+
90
+ function signalerClesPerdues(data) {
91
+ const perdues = Object.keys(data || {}).filter((k) => !CLES_CONNUES.has(k) && data[k] !== undefined);
92
+ if (perdues.length) {
93
+ console.warn('[pdf-sign-ui] option(s) ignorée(s) par buildSignRequest, donc PERDUE(S) : '
94
+ + perdues.join(', ') + '. Ajoutez-les au jeu de clés ou retirez-les de l’appel.');
95
+ }
96
+ }
97
+
64
98
  export function buildSignRequest(data = {}) {
65
99
  const preset = data.preset ? getPreset(data.preset) : undefined;
66
100
  const reason = data.reason || preset?.reason;
@@ -88,7 +122,16 @@ export function buildSignRequest(data = {}) {
88
122
  }),
89
123
  field: prune({ page: numOrUndef(data.page), box: isBox(data.box) ? data.box : undefined, stampImage: data.stampImage }),
90
124
  permissions,
125
+ // ⚠️ Ce jeu de clés est FERMÉ : une option que `buildSignRequest` ignore disparaît en silence,
126
+ // et le document sort sans elle — sans erreur. `openPassword` a été perdu ainsi jusqu'en
127
+ // 0.2.0 : le document s'ouvrait alors que l'appelant le croyait verrouillé.
128
+ openPassword: data.openPassword || undefined,
129
+ // Horodatage RFC 3161 (`@mostajs/pdf-sign` 0.3.0) : fait passer la signature de PAdES-B à
130
+ // PAdES-T. Perdu ici en silence lors de sa première intégration — exactement comme
131
+ // `openPassword` avant lui. D'où l'avertissement ci-dessous.
132
+ timestampUrl: data.timestampUrl || undefined,
91
133
  };
134
+ signalerClesPerdues(data);
92
135
  return prune(req);
93
136
  }
94
137
 
@@ -114,6 +157,7 @@ export function buildEngineViewModel(availability = { node: true, pyhanko: false
114
157
  pyhanko,
115
158
  canRestrict: pyhanko,
116
159
  canPades: pyhanko,
160
+ canLock: pyhanko, // mot de passe à l'ouverture (chiffrement) — même moteur
117
161
  label: pyhanko ? t("PAdES + permissions (pyhanko)") : t("PKCS#7 (Node)"),
118
162
  hint: pyhanko
119
163
  ? t("Signature avancée et permissions restrictives disponibles.")