@hgdc/ui 0.73.0 → 0.74.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/README.md +1 -1
- package/dist/csp.d.ts +38 -0
- package/dist/csp.js +83 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Le menu « Produits » et la palette (`badge` d'une commande) montrent la pastil
|
|
|
66
66
|
|
|
67
67
|
### D'un produit à l'autre sous `app.hgdc.ch` (ADR 0020)
|
|
68
68
|
|
|
69
|
-
Entre deux produits servis sous la même origine, la coque fait comme une seule app : transition de vue entre documents, lien relatif marqué `data-hgdc-prerender` et prérendu au survol (`PRODUCT_SPECULATION_RULES` de `@hgdc/ui/product-switch`). Le H couvre le contenu 100 ms après le clic (`PRODUCT_LEAVE_DELAY_MS`) : jamais pour une page prérendue. Chrome et Edge : tout ; Safari 18.2+ : la transition, sans prérendu ; Firefox : navigation ordinaire.
|
|
69
|
+
Entre deux produits servis sous la même origine, la coque fait comme une seule app : transition de vue entre documents, lien relatif marqué `data-hgdc-prerender` et prérendu au survol (`PRODUCT_SPECULATION_RULES` de `@hgdc/ui/product-switch`). Le H couvre le contenu 100 ms après le clic (`PRODUCT_LEAVE_DELAY_MS`) : jamais pour une page prérendue. Chrome et Edge : tout ; Safari 18.2+ : la transition, sans prérendu ; Firefox : navigation ordinaire. La barre ouvre aussi en arrière-plan la session des autres modules affichés (ADR 0026, `startSessionOpening`) : un cadre caché à la fois, au plus une fois par jour et par module, jamais après une déconnexion ni sans `localStorage`. Le premier clic vers un module trouve sa session.
|
|
70
70
|
|
|
71
71
|
Pendant le passage (0.39) :
|
|
72
72
|
|
package/dist/csp.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Socle : jamais encadrable (clickjacking), ni `<base>`, ni plugin, formulaires vers soi. */
|
|
2
|
+
export declare const BASE_POLICY = "frame-ancestors 'none'; base-uri 'self'; object-src 'none'; form-action 'self'";
|
|
3
|
+
/**
|
|
4
|
+
* Empreintes des scripts en ligne que ce paquet rend sans nonce. Aujourd'hui : les règles de
|
|
5
|
+
* préchargement de la barre (`PRODUCT_SPECULATION_RULES`), que Chrome bloque malgré
|
|
6
|
+
* `'inline-speculation-rules'`. `csp.test.ts` les recalcule : changer un script sans son empreinte échoue.
|
|
7
|
+
*/
|
|
8
|
+
export declare const KIT_INLINE_SCRIPT_HASHES: readonly string[];
|
|
9
|
+
/** Scripts en ligne couverts par `KIT_INLINE_SCRIPT_HASHES`, pour le test qui recalcule les empreintes. */
|
|
10
|
+
export declare const KIT_INLINE_SCRIPTS: readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* Origines que les composants de ce paquet joignent depuis le navigateur : bandeau d'état
|
|
13
|
+
* (`ServiceStatus`), formulaire « Demander un accès » (`AccessRequest`), réglage de la barre.
|
|
14
|
+
*/
|
|
15
|
+
export declare const FAMILY_CONNECT_SOURCES: readonly string[];
|
|
16
|
+
/**
|
|
17
|
+
* Origines joignables par `fetch` ou WebSocket, tirées d'adresses complètes (backend, collecteur
|
|
18
|
+
* d'erreurs) : l'origine seule, plus sa version WebSocket. Une adresse vide ou illisible est ignorée :
|
|
19
|
+
* elle ne doit jamais ouvrir la politique.
|
|
20
|
+
*/
|
|
21
|
+
export declare function connectSources(urls: ReadonlyArray<string | undefined>): string[];
|
|
22
|
+
/** Nonce d'une requête : 16 octets aléatoires en base64. */
|
|
23
|
+
export declare function newNonce(): string;
|
|
24
|
+
export type DocumentPolicyOptions = {
|
|
25
|
+
/** Nonce de la requête (`newNonce`), posé aussi sur les scripts en ligne du rendu serveur. */
|
|
26
|
+
nonce: string;
|
|
27
|
+
/** Origines que le navigateur joint, en plus de la sienne et de `FAMILY_CONNECT_SOURCES`. */
|
|
28
|
+
connect: ReadonlyArray<string>;
|
|
29
|
+
/** Empreintes de scripts en ligne propres à l'application, en plus de celles du paquet. */
|
|
30
|
+
scriptHashes?: ReadonlyArray<string>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Politique des documents : le socle, `script-src` (fichiers de l'application, scripts en ligne portant
|
|
34
|
+
* le nonce, ceux du paquet par leur empreinte) et `connect-src`. Ni `'unsafe-inline'` ni `'unsafe-eval'`.
|
|
35
|
+
* Images, styles et polices ne sont pas restreints. Une valeur mal formée lève : elle ne doit jamais
|
|
36
|
+
* pouvoir ajouter une directive.
|
|
37
|
+
*/
|
|
38
|
+
export declare function documentPolicy({ nonce, connect, scriptHashes }: DocumentPolicyOptions): string;
|
package/dist/csp.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Politique de sécurité du contenu (CSP) des documents d'une console, commune à tous les modules.
|
|
3
|
+
*
|
|
4
|
+
* Les modules partagent une même origine : un script injecté dans l'un agit dans tous les autres. La
|
|
5
|
+
* politique doit donc être la même partout, d'où ce module. Aucune dépendance : le middleware qui
|
|
6
|
+
* tire le nonce et pose l'en-tête reste dans chaque application (il dépend de son serveur).
|
|
7
|
+
*/
|
|
8
|
+
import { PRODUCT_SPECULATION_RULES } from "./product-switch.js";
|
|
9
|
+
/** Socle : jamais encadrable (clickjacking), ni `<base>`, ni plugin, formulaires vers soi. */
|
|
10
|
+
export const BASE_POLICY = "frame-ancestors 'none'; base-uri 'self'; object-src 'none'; form-action 'self'";
|
|
11
|
+
/**
|
|
12
|
+
* Empreintes des scripts en ligne que ce paquet rend sans nonce. Aujourd'hui : les règles de
|
|
13
|
+
* préchargement de la barre (`PRODUCT_SPECULATION_RULES`), que Chrome bloque malgré
|
|
14
|
+
* `'inline-speculation-rules'`. `csp.test.ts` les recalcule : changer un script sans son empreinte échoue.
|
|
15
|
+
*/
|
|
16
|
+
export const KIT_INLINE_SCRIPT_HASHES = ["sha256-a67/vPhviRex9BGXSAteyrRDrGJEKbTE/4tBKxtGgjQ="];
|
|
17
|
+
/** Scripts en ligne couverts par `KIT_INLINE_SCRIPT_HASHES`, pour le test qui recalcule les empreintes. */
|
|
18
|
+
export const KIT_INLINE_SCRIPTS = [PRODUCT_SPECULATION_RULES];
|
|
19
|
+
/**
|
|
20
|
+
* Origines que les composants de ce paquet joignent depuis le navigateur : bandeau d'état
|
|
21
|
+
* (`ServiceStatus`), formulaire « Demander un accès » (`AccessRequest`), réglage de la barre.
|
|
22
|
+
*/
|
|
23
|
+
export const FAMILY_CONNECT_SOURCES = [
|
|
24
|
+
"https://status.hgdc.ch",
|
|
25
|
+
"https://api.mail.hgdc.ch",
|
|
26
|
+
"https://account.hgdc.ch",
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Origines joignables par `fetch` ou WebSocket, tirées d'adresses complètes (backend, collecteur
|
|
30
|
+
* d'erreurs) : l'origine seule, plus sa version WebSocket. Une adresse vide ou illisible est ignorée :
|
|
31
|
+
* elle ne doit jamais ouvrir la politique.
|
|
32
|
+
*/
|
|
33
|
+
export function connectSources(urls) {
|
|
34
|
+
const sources = new Set();
|
|
35
|
+
for (const raw of urls) {
|
|
36
|
+
if (!raw)
|
|
37
|
+
continue;
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(raw);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
46
|
+
continue;
|
|
47
|
+
sources.add(url.origin);
|
|
48
|
+
sources.add(`${url.protocol === "https:" ? "wss:" : "ws:"}//${url.host}`);
|
|
49
|
+
}
|
|
50
|
+
return [...sources];
|
|
51
|
+
}
|
|
52
|
+
/** Nonce d'une requête : 16 octets aléatoires en base64. */
|
|
53
|
+
export function newNonce() {
|
|
54
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
55
|
+
return btoa(String.fromCharCode(...bytes));
|
|
56
|
+
}
|
|
57
|
+
const NONCE = /^[A-Za-z0-9+/_-]{16,}={0,2}$/;
|
|
58
|
+
const HASH = /^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
|
|
59
|
+
/** Une source de `connect-src` : une origine `https:`, `wss:`, `http:` ou `ws:`, sans chemin ni séparateur. */
|
|
60
|
+
const SOURCE = /^(https|wss|http|ws):\/\/[A-Za-z0-9.*:[\]-]+$/;
|
|
61
|
+
/**
|
|
62
|
+
* Politique des documents : le socle, `script-src` (fichiers de l'application, scripts en ligne portant
|
|
63
|
+
* le nonce, ceux du paquet par leur empreinte) et `connect-src`. Ni `'unsafe-inline'` ni `'unsafe-eval'`.
|
|
64
|
+
* Images, styles et polices ne sont pas restreints. Une valeur mal formée lève : elle ne doit jamais
|
|
65
|
+
* pouvoir ajouter une directive.
|
|
66
|
+
*/
|
|
67
|
+
export function documentPolicy({ nonce, connect, scriptHashes = [] }) {
|
|
68
|
+
if (!NONCE.test(nonce))
|
|
69
|
+
throw new Error("nonce CSP invalide");
|
|
70
|
+
const hashes = [...KIT_INLINE_SCRIPT_HASHES, ...scriptHashes];
|
|
71
|
+
for (const hash of hashes)
|
|
72
|
+
if (!HASH.test(hash))
|
|
73
|
+
throw new Error(`empreinte CSP invalide : ${hash}`);
|
|
74
|
+
const sources = [...new Set([...connect, ...FAMILY_CONNECT_SOURCES])];
|
|
75
|
+
for (const source of sources)
|
|
76
|
+
if (!SOURCE.test(source))
|
|
77
|
+
throw new Error(`source CSP invalide : ${source}`);
|
|
78
|
+
return [
|
|
79
|
+
BASE_POLICY,
|
|
80
|
+
`script-src ${["'self'", `'nonce-${nonce}'`, ...hashes.map((hash) => `'${hash}'`), "'inline-speculation-rules'"].join(" ")}`,
|
|
81
|
+
`connect-src ${["'self'", ...sources].join(" ")}`,
|
|
82
|
+
].join("; ");
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"description": "Interface de la H Collection : jetons de la DA, thèmes, polices et composants.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",
|