@nodefony/security 10.0.0-beta.2 → 10.0.0-beta.3

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.
Files changed (160) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
  3. package/dist/index.js +44 -10
  4. package/dist/nodefony/command/security-oauth-doctor.js +66 -0
  5. package/dist/nodefony/command/security-secrets.js +42 -25
  6. package/dist/nodefony/command/security-token.js +46 -15
  7. package/dist/nodefony/command/security-user-delete.js +5 -4
  8. package/dist/nodefony/command/security-user-password.js +4 -11
  9. package/dist/nodefony/config/config.js +80 -5
  10. package/dist/nodefony/contracts/index.js +1 -2
  11. package/dist/nodefony/errors/InsecureTransportError.js +24 -0
  12. package/dist/nodefony/errors/index.js +2 -1
  13. package/dist/nodefony/keycloak/keycloakAdmin.js +255 -0
  14. package/dist/nodefony/keycloak/keycloakRealm.js +260 -0
  15. package/dist/nodefony/keycloak/keycloakRealmInput.js +124 -0
  16. package/dist/nodefony/keycloak/scaffold.js +231 -0
  17. package/dist/nodefony/keycloak/security-keycloak-realm.js +128 -0
  18. package/dist/nodefony/scaffold/contribute.js +20 -0
  19. package/dist/nodefony/service/authFlow.js +111 -11
  20. package/dist/nodefony/service/firewall.js +111 -21
  21. package/dist/nodefony/service/keycloak.js +209 -0
  22. package/dist/nodefony/service/oauth2.js +379 -26
  23. package/dist/nodefony/service/tokenService.js +57 -6
  24. package/dist/nodefony/service/totp.js +51 -6
  25. package/dist/nodefony/service/webAuthn.js +20 -2
  26. package/dist/nodefony/src/SecuredArea.js +14 -3
  27. package/dist/nodefony/src/admin/SecurityAdminApi.js +70 -40
  28. package/dist/nodefony/src/admin/adminAudit.js +0 -1
  29. package/dist/nodefony/src/admin/userRevocationCascade.js +79 -15
  30. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +66 -6
  31. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +38 -15
  32. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +1 -0
  33. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +13 -3
  34. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +9 -2
  35. package/dist/nodefony/src/authenticator/bearer.js +1 -2
  36. package/dist/nodefony/src/boot/publicRoutesNotice.js +27 -0
  37. package/dist/nodefony/src/entryPoint.js +39 -0
  38. package/dist/nodefony/src/oauth/clientCredentials.js +40 -0
  39. package/dist/nodefony/src/oauth/metadata.js +36 -9
  40. package/dist/nodefony/src/oauth/oauth2Client.js +60 -33
  41. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +39 -6
  42. package/dist/nodefony/src/oauth/providerDiagnosis.js +283 -0
  43. package/dist/nodefony/src/oauth/providerRoles.js +79 -0
  44. package/dist/nodefony/src/oauth/providers/github.js +32 -6
  45. package/dist/nodefony/src/oauth/providers/oidc.js +82 -3
  46. package/dist/nodefony/src/openToSignup.js +47 -0
  47. package/dist/nodefony/src/sessionAuthentication.js +26 -0
  48. package/dist/nodefony/src/token/JwtKeystore.js +78 -46
  49. package/dist/nodefony/src/token/RemoteJwtVerifier.js +40 -13
  50. package/dist/nodefony/src/token/secretFile.js +59 -2
  51. package/dist/nodefony/src/transport/CredentialTransportPolicy.js +51 -0
  52. package/dist/types/index.d.ts +26 -6
  53. package/dist/types/nodefony/command/security-oauth-doctor.d.ts +23 -0
  54. package/dist/types/nodefony/command/security-secrets.d.ts +2 -0
  55. package/dist/types/nodefony/command/security-token.d.ts +9 -0
  56. package/dist/types/nodefony/command/security-user-delete.d.ts +4 -3
  57. package/dist/types/nodefony/command/security-user-password.d.ts +4 -4
  58. package/dist/types/nodefony/config/config.d.ts +63 -1
  59. package/dist/types/nodefony/contracts/IAuthenticator.d.ts +1 -1
  60. package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +42 -1
  61. package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +48 -0
  62. package/dist/types/nodefony/contracts/ISecuredArea.d.ts +22 -0
  63. package/dist/types/nodefony/contracts/ITotpSecret.d.ts +4 -1
  64. package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +7 -1
  65. package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +4 -1
  66. package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +6 -0
  67. package/dist/types/nodefony/errors/InsecureTransportError.d.ts +20 -0
  68. package/dist/types/nodefony/errors/index.d.ts +1 -0
  69. package/dist/types/nodefony/keycloak/keycloakAdmin.d.ts +154 -0
  70. package/dist/types/nodefony/keycloak/keycloakRealm.d.ts +138 -0
  71. package/dist/types/nodefony/keycloak/keycloakRealmInput.d.ts +92 -0
  72. package/dist/types/nodefony/keycloak/scaffold.d.ts +98 -0
  73. package/dist/types/nodefony/keycloak/security-keycloak-realm.d.ts +43 -0
  74. package/dist/types/nodefony/scaffold/contribute.d.ts +15 -0
  75. package/dist/types/nodefony/service/authFlow.d.ts +35 -3
  76. package/dist/types/nodefony/service/firewall.d.ts +17 -3
  77. package/dist/types/nodefony/service/keycloak.d.ts +115 -0
  78. package/dist/types/nodefony/service/oauth2.d.ts +125 -9
  79. package/dist/types/nodefony/service/tokenService.d.ts +7 -1
  80. package/dist/types/nodefony/service/totp.d.ts +28 -4
  81. package/dist/types/nodefony/service/webAuthn.d.ts +23 -1
  82. package/dist/types/nodefony/src/SecuredArea.d.ts +4 -0
  83. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +54 -12
  84. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +18 -0
  85. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +13 -0
  86. package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +11 -3
  87. package/dist/types/nodefony/src/boot/publicRoutesNotice.d.ts +16 -0
  88. package/dist/types/nodefony/src/entryPoint.d.ts +37 -0
  89. package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
  90. package/dist/types/nodefony/src/oauth/metadata.d.ts +24 -1
  91. package/dist/types/nodefony/src/oauth/oauth2Client.d.ts +50 -1
  92. package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +46 -2
  93. package/dist/types/nodefony/src/oauth/providerDiagnosis.d.ts +117 -0
  94. package/dist/types/nodefony/src/oauth/providerRoles.d.ts +58 -0
  95. package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +34 -1
  96. package/dist/types/nodefony/src/openToSignup.d.ts +36 -0
  97. package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
  98. package/dist/types/nodefony/src/token/JwtKeystore.d.ts +47 -5
  99. package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +29 -2
  100. package/dist/types/nodefony/src/token/secretFile.d.ts +22 -0
  101. package/dist/types/nodefony/src/transport/CredentialTransportPolicy.d.ts +47 -0
  102. package/docs/api-keys.md +17 -17
  103. package/docs/audit.md +16 -16
  104. package/docs/authenticators.md +60 -27
  105. package/docs/authorization.md +16 -16
  106. package/docs/cors.md +29 -29
  107. package/docs/csrf.md +35 -33
  108. package/docs/external-jwt.md +66 -4
  109. package/docs/firewall.md +57 -24
  110. package/docs/headers.md +30 -30
  111. package/docs/index.md +12 -4
  112. package/docs/keycloak.md +818 -0
  113. package/docs/login-page.md +361 -0
  114. package/docs/oauth2.md +236 -122
  115. package/docs/obtenir-un-jeton.md +18 -11
  116. package/docs/tokens.md +136 -85
  117. package/docs/totp.md +105 -64
  118. package/docs/webauthn.md +90 -87
  119. package/docs/webhooks.md +3 -3
  120. package/keycloak/themes/nodefony/account/resources/css/nodefony.css +66 -0
  121. package/keycloak/themes/nodefony/account/resources/img/favicon.ico +0 -0
  122. package/keycloak/themes/nodefony/account/resources/img/logo.png +0 -0
  123. package/keycloak/themes/nodefony/account/theme.properties +14 -0
  124. package/keycloak/themes/nodefony/admin/resources/css/nodefony.css +66 -0
  125. package/keycloak/themes/nodefony/admin/resources/img/favicon.ico +0 -0
  126. package/keycloak/themes/nodefony/admin/resources/img/logo.png +0 -0
  127. package/keycloak/themes/nodefony/admin/theme.properties +12 -0
  128. package/keycloak/themes/nodefony/email/html/template.ftl +73 -0
  129. package/keycloak/themes/nodefony/email/messages/messages_en.properties +2 -0
  130. package/keycloak/themes/nodefony/email/messages/messages_fr.properties +3 -0
  131. package/keycloak/themes/nodefony/email/resources/img/logo.png +0 -0
  132. package/keycloak/themes/nodefony/email/theme.properties +8 -0
  133. package/keycloak/themes/nodefony/login/messages/messages_en.properties +22 -0
  134. package/keycloak/themes/nodefony/login/messages/messages_fr.properties +25 -0
  135. package/keycloak/themes/nodefony/login/resources/css/login.css +1141 -0
  136. package/keycloak/themes/nodefony/login/resources/css/nodefony.css +688 -0
  137. package/keycloak/themes/nodefony/login/resources/css/skins/blueprint.css +70 -0
  138. package/keycloak/themes/nodefony/login/resources/css/skins/console.css +57 -0
  139. package/keycloak/themes/nodefony/login/resources/css/skins/dots.css +67 -0
  140. package/keycloak/themes/nodefony/login/resources/css/skins/enterprise.css +84 -0
  141. package/keycloak/themes/nodefony/login/resources/css/skins/horizon.css +158 -0
  142. package/keycloak/themes/nodefony/login/resources/css/skins/ledger.css +224 -0
  143. package/keycloak/themes/nodefony/login/resources/css/skins/minimal.css +63 -0
  144. package/keycloak/themes/nodefony/login/resources/css/skins/photo-card.css +72 -0
  145. package/keycloak/themes/nodefony/login/resources/img/favicon.ico +0 -0
  146. package/keycloak/themes/nodefony/login/resources/img/logo.png +0 -0
  147. package/keycloak/themes/nodefony/login/template.ftl +423 -0
  148. package/keycloak/themes/nodefony/login/theme.properties +17 -0
  149. package/keycloak/themes/nodefony/welcome/resources/css/nodefony.css +32 -0
  150. package/keycloak/themes/nodefony/welcome/resources/img/logo.png +0 -0
  151. package/keycloak/themes/nodefony/welcome/theme.properties +11 -0
  152. package/keycloak/themes/nodefony-blueprint/login/theme.properties +12 -0
  153. package/keycloak/themes/nodefony-console/login/theme.properties +12 -0
  154. package/keycloak/themes/nodefony-dots/login/theme.properties +12 -0
  155. package/keycloak/themes/nodefony-enterprise/login/theme.properties +12 -0
  156. package/keycloak/themes/nodefony-horizon/login/theme.properties +12 -0
  157. package/keycloak/themes/nodefony-ledger/login/theme.properties +12 -0
  158. package/keycloak/themes/nodefony-minimal/login/theme.properties +12 -0
  159. package/keycloak/themes/nodefony-photo-card/login/theme.properties +12 -0
  160. package/package.json +17 -15
package/docs/headers.md CHANGED
@@ -39,7 +39,7 @@ source: "src/packages/@nodefony/security/docs/headers.md"
39
39
  > (`@nodefony/http`, dès l'entrée brute — couvre aussi les fichiers statiques et les erreurs) et la
40
40
  > couche **applicative** (`@nodefony/security`, dans le pipeline — CSP, Referrer-Policy, isolation
41
41
  > cross-origin). Ancré sur `SecurityHeaders` (`securityHeaders.ts:42`) et
42
- > `Firewall.applySecurityHeaders()` (`firewall.ts:1045`).
42
+ > `Firewall.applySecurityHeaders()` (`firewall.ts:1152`).
43
43
 
44
44
  📍 [Documentation](../../../../../docs/index.md) › [Sécurité](index.md) › **En-têtes de sécurité**
45
45
 
@@ -122,8 +122,8 @@ fait l'inverse : **tout ce qui est constant est calculé une fois au démarrage*
122
122
  statique) et la **gèle** avec `Object.freeze` (`securityHeaders.ts:77`). Par requête, le firewall
123
123
  se contente de la parcourir et de la poser : zéro concaténation, zéro objet créé.
124
124
  - Côté transport, même principe : `HttpKernel.computeSecurityHeaderCaches()`
125
- (`http-kernel.ts:358`) précalcule la chaîne HSTS (`max-age`, `includeSubDomains`, `preload`) au
126
- boot ; `onHttpRequest` (`http-kernel.ts:1009`) ne fait plus que trois `setHeader`.
125
+ (`http-kernel.ts:411`) précalcule la chaîne HSTS (`max-age`, `includeSubDomains`, `preload`) au
126
+ boot ; `onHttpRequest` (`http-kernel.ts:1093`) ne fait plus que trois `setHeader`.
127
127
  - Le seul coût variable est le **nonce CSP**, et il est **paresseux** : `Context.cspNonce`
128
128
  (`Context.ts:253`) ne tire ses 128 bits (16 octets CSPRNG, en base64) d'un pool amorti (`randomFillSync`, 4 Ko pour 256 nonces) qu'à la première lecture,
129
129
  puis mémoïse. Une réponse qui n'a aucun script inline à signer ne paie aucun appel crypto.
@@ -173,7 +173,7 @@ export default defineConfig(() => ({
173
173
  ```
174
174
 
175
175
  Les clés sont **typées et auto-complétées** : le module augmente le registre `NodefonyModuleConfig`
176
- du core (`index.ts:33`), donc `use("@nodefony/security", …)` propose les clés **et** les valeurs
176
+ du core (`index.ts:37`), donc `use("@nodefony/security", …)` propose les clés **et** les valeurs
177
177
  d'enum (`referrerPolicy`, `coop`, `corp`…). Une valeur hors enum casse le boot, pas la production.
178
178
 
179
179
  ### 2. Ce qu'on observe
@@ -255,7 +255,7 @@ Choisir en cinq secondes — puis le détail dans les cartes.
255
255
  **La menace** : n'importe quelle entrée non échappée (commentaire, nom d'utilisateur, paramètre
256
256
  réfléchi) devient un vecteur d'exécution de code. Le CSP est le filet quand l'échappement a raté.
257
257
 
258
- **Le défaut Nodefony** est délibérément « secure-but-usable » (`config.ts:238`) : seul `script-src`
258
+ **Le défaut Nodefony** est délibérément « secure-but-usable » (`config.ts:276`) : seul `script-src`
259
259
  est **strict** — `'self'` plus le nonce de la requête, ce qui est la vraie défense XSS. Le reste
260
260
  couvre les besoins réels d'une app moderne (CSS-in-JS via `style-src 'unsafe-inline'`, images
261
261
  `data:`/`blob:`, workers, fetch/WS same-origin) et ajoute les durcissements gratuits :
@@ -275,7 +275,7 @@ privilégient `frame-ancestors`, `X-Frame-Options` reste le filet pour les ancie
275
275
  victime est capté par ton interface.
276
276
 
277
277
  Posé par le **transport** depuis un cache calculé au boot — `secFrameOptions`
278
- (`http-kernel.ts:1049`) — et configuré côté `@nodefony/http` avec `frameOptions`
278
+ (`http-kernel.ts:1144`) — et configuré côté `@nodefony/http` avec `frameOptions`
279
279
  (`http/nodefony/config/config.ts:136`), qui vaut `DENY` par défaut. `SAMEORIGIN` si ton propre site
280
280
  s'auto-encadre. C'est un des trois en-têtes que security **ne ré-émet pas** : il doit valoir aussi
281
281
  pour un HTML statique servi directement depuis `public/`.
@@ -287,7 +287,7 @@ pour un HTML statique servi directement depuis `public/`.
287
287
  avec tes cookies.
288
288
 
289
289
  Valeur unique reconnue : `nosniff`, posée depuis le cache `secContentTypeOptions`
290
- (`http-kernel.ts:1046`). C'est **l'en-tête qui justifie le mieux la couche transport** : le danger
290
+ (`http-kernel.ts:1141`). C'est **l'en-tête qui justifie le mieux la couche transport** : le danger
291
291
  vient précisément des fichiers servis hors pipeline applicatif — un banc live le prouve sur une 404
292
292
  (`security-headers.test.ts:38`).
293
293
 
@@ -297,10 +297,10 @@ vient précisément des fichiers servis hors pipeline applicatif — un banc liv
297
297
  installer un intercepteur.
298
298
 
299
299
  La chaîne est assemblée au boot par `HttpKernel.computeSecurityHeaderCaches()`
300
- (`http-kernel.ts:358`) : `max-age`, puis `includeSubDomains` et `preload` selon la config.
300
+ (`http-kernel.ts:411`) : `max-age`, puis `includeSubDomains` et `preload` selon la config.
301
301
 
302
302
  Elle n'est posée que **sur une réponse HTTPS ou HTTP/2** — le cache `secHsts` est conditionné au type
303
- de serveur (`http-kernel.ts:1052`). C'est conforme à la RFC 6797, qui veut qu'un HSTS reçu en clair
303
+ de serveur (`http-kernel.ts:1147`). C'est conforme à la RFC 6797, qui veut qu'un HSTS reçu en clair
304
304
  soit ignoré : l'émettre sur du HTTP simple ne ferait que polluer. Défaut : un an, sous-domaines
305
305
  inclus.
306
306
 
@@ -315,7 +315,7 @@ inclus.
315
315
  **La menace** : la fuite d'URL. Chemins parlants, identifiants de session dans une query, jetons de
316
316
  réinitialisation — tout part chez le site suivant via le `Referer`.
317
317
 
318
- Défaut Nodefony : `no-referrer` (`security/nodefony/config/config.ts:278`), la valeur la plus stricte. La valeur est un
318
+ Défaut Nodefony : `no-referrer` (`security/nodefony/config/config.ts:316`), la valeur la plus stricte. La valeur est un
319
319
  **enum W3C fermé** — huit valeurs validées au boot, donc pas de faute de frappe qui passerait en
320
320
  silence (l'écriture libre `no-refferer` casserait la protection sans prévenir).
321
321
 
@@ -359,7 +359,7 @@ propres sous-domaines ; `cross-origin` ouvre — c'est ce qu'il faut sur une CDN
359
359
  **La menace** : plusieurs origines partageant heap et processus, donc des canaux auxiliaires
360
360
  mesurables.
361
361
 
362
- Nodefony l'émet comme un **booléen de champ structuré** RFC 8941 : la valeur est littéralement `?1`
362
+ Nodefony l'émet comme un **booléen de champ structuré** RFC 9651 : la valeur est littéralement `?1`
363
363
  (`securityHeaders.ts:75`). C'est une **demande**, pas une garantie — le navigateur décide.
364
364
 
365
365
  ### `Permissions-Policy` — coupe le micro par défaut
@@ -379,7 +379,7 @@ config security est la première source de confusion sur ce sujet.
379
379
 
380
380
  ### Couche applicative — `use("@nodefony/security", { headers })`
381
381
 
382
- Dérivé du schéma Zod `headersSchema` (`config.ts:206`).
382
+ Dérivé du schéma Zod `headersSchema` (`config.ts:256`).
383
383
 
384
384
  <!-- prettier-ignore -->
385
385
  | Option | Type | Défaut | Effet |
@@ -397,7 +397,7 @@ Dérivé du schéma Zod `headersSchema` (`config.ts:206`).
397
397
  ### Socle transport — `use("@nodefony/http", { securityHeaders })`
398
398
 
399
399
  Dérivé de `securityHeadersSchema` (`http/nodefony/config/config.ts:123`). Ces trois réglages sont
400
- **éditables à chaud** (`runtimeMutable`) : `HttpKernel.onConfigChanged()` (`http-kernel.ts:408`)
400
+ **éditables à chaud** (`runtimeMutable`) : `HttpKernel.onConfigChanged()` (`http-kernel.ts:461`)
401
401
  recalcule les caches, donc la valeur suivante s'applique sans redémarrage.
402
402
 
403
403
  | Option | Type | Défaut | Effet |
@@ -411,10 +411,10 @@ recalcule les caches, donc la valeur suivante s'applique sans redémarrage.
411
411
 
412
412
  > [!WARNING]
413
413
  > Les clés `hsts`, `hstsMaxAgeS`, `frameguard` et `noSniff` **existent** dans la config security
414
- > (`config.ts:257`, `config.ts:257`, `config.ts:257`, `config.ts:257`) mais **ne pilotent rien** : la couche
414
+ > (`config.ts:263`, `config.ts:263`, `config.ts:263`, `config.ts:263`) mais **ne pilotent rien** : la couche
415
415
  > applicative ne les lit pas (`securityHeaders.ts:6`), elles ne servent qu'à l'introspection
416
416
  > affichée dans Studio. Pour changer réellement `X-Frame-Options`, c'est `securityHeaders.frameOptions`
417
- > **du module http**. Même remarque pour `hidePoweredBy` (`config.ts:282`) : Nodefony n'émet aucun
417
+ > **du module http**. Même remarque pour `hidePoweredBy` (`config.ts:288`) : Nodefony n'émet aucun
418
418
  > `X-Powered-By`, l'option est un no-op documenté.
419
419
 
420
420
  ## 🏗️ Le CSP en détail — deux régimes, trois façons de l'étendre
@@ -439,7 +439,7 @@ Le chemin complet, sans surprise :
439
439
 
440
440
  1. **Au boot**, la chaîne CSP est **pré-découpée** autour de `{{nonce}}` (`securityHeaders.ts:58`).
441
441
  Aucun parsing ni regex n'aura lieu pendant une requête.
442
- 2. **Par requête**, `Firewall.applySecurityHeaders()` (`firewall.ts:1045`) lit `context.cspNonce` —
442
+ 2. **Par requête**, `Firewall.applySecurityHeaders()` (`firewall.ts:1152`) lit `context.cspNonce` —
443
443
  ce qui **génère** le jeton à cet instant (`Context.ts:253`) — puis appelle
444
444
  `SecurityHeaders.cspFor()` (`securityHeaders.ts:100`) : un seul `join`.
445
445
  3. **Dans la vue**, le contrôleur relit `context.cspNonce`, qui est **mémoïsé** : l'en-tête et le
@@ -452,7 +452,7 @@ rester imprévisible, jamais pilotable par le client — contrairement au `reque
452
452
  une corrélation entrante.
453
453
 
454
454
  **Placement dans le pipeline** : `applySecurityHeaders` est appelé **après le resolve** et **avant**
455
- le repli statique et le `writeHead` (`http-kernel.ts:1624`). Cet ordre n'est pas cosmétique : il
455
+ le repli statique et le `writeHead` (`http-kernel.ts:1666`). Cet ordre n'est pas cosmétique : il
456
456
  faut que le routeur ait posé les directives `@Csp` de la route pour pouvoir les fusionner, et il faut
457
457
  être avant l'écriture des en-têtes pour pouvoir en poser.
458
458
 
@@ -476,7 +476,7 @@ directive absente est ajoutée en fin. La fonction est **pure et déterministe**
476
476
  les tests fiables.
477
477
 
478
478
  **Coût** : le merge d'un module est payé **une fois**, au (dés)enregistrement
479
- (`Firewall.#rebuildSecurityHeaders()`, `firewall.ts:1101`), jamais par requête. Le merge d'une route
479
+ (`Firewall.#rebuildSecurityHeaders()`, `firewall.ts:1209`), jamais par requête. Le merge d'une route
480
480
  `@Csp` est payé **uniquement sur les routes décorées** (`SecurityHeaders.cspForExtra()`,
481
481
  `securityHeaders.ts:131`) ; le cas courant reste le simple `join`.
482
482
 
@@ -502,15 +502,15 @@ Trois propriétés à retenir :
502
502
 
503
503
  - **Aucun couplage** : la résolution par nom de service évite un cycle de dépendances, et
504
504
  `registerCspOrigins` est optionnel — un module fonctionne dans une app **sans** security.
505
- - **Réversible** : `Firewall.unregisterCspOrigins()` (`firewall.ts:1099`) retire le fragment et
505
+ - **Réversible** : `Firewall.unregisterCspOrigins()` (`firewall.ts:1202`) retire le fragment et
506
506
  reconstruit le CSP de base. C'est ce que fait `@nodefony/frontend` à l'arrêt du serveur Vite.
507
507
  - **Idempotent** : la reconstruction repart **toujours** du `headers.csp` d'origine
508
- (`firewall.ts:1113`), jamais d'un CSP déjà fusionné — pas d'accumulation entre deux
508
+ (`firewall.ts:1212`), jamais d'un CSP déjà fusionné — pas d'accumulation entre deux
509
509
  enregistrements.
510
510
 
511
511
  L'exemple de référence vit dans le framework : en développement, `@nodefony/frontend` déclare les
512
512
  origines du serveur Vite et `'unsafe-eval'` (exigé par le Fast Refresh de React) via
513
- `FrontendService.#viteCspFragment()` (`FrontendService.ts:1034`) — ce qui explique qu'un CSP observé
513
+ `FrontendService.#viteCspFragment()` (`FrontendService.ts:804`) — ce qui explique qu'un CSP observé
514
514
  en dev soit plus large qu'en production, où ce fragment n'existe pas.
515
515
 
516
516
  ## 📜 Normes appliquées
@@ -519,12 +519,12 @@ en dev soit plus large qu'en production, où ce fragment n'existe pas.
519
519
  | ------------------------------------ | -------------------------------- | ------------------------------------------------------------ |
520
520
  | Politique de sécurité du contenu | W3C CSP Level 3 | `mergeCspFragments()` (`csp.ts:56`), directive non dupliquée |
521
521
  | Nonce CSP (unicité, imprévisibilité) | W3C CSP Level 3 §6.7.4 | `Context.cspNonce` — 128 bits CSPRNG (`Context.ts:253`) |
522
- | HSTS | RFC 6797 | posé sur TLS uniquement (`http-kernel.ts:969`) |
523
- | Champ structuré booléen | RFC 8941 | `Origin-Agent-Cluster: ?1` (`securityHeaders.ts:75`) |
524
- | Referrer-Policy | W3C Referrer Policy (enum fermé) | 8 valeurs validées au boot (`config.ts:267`) |
522
+ | HSTS | RFC 6797 | posé sur TLS uniquement (`http-kernel.ts:981`) |
523
+ | Champ structuré booléen | RFC 9651 | `Origin-Agent-Cluster: ?1` (`securityHeaders.ts:75`) |
524
+ | Referrer-Policy | W3C Referrer Policy (enum fermé) | 8 valeurs validées au boot (`config.ts:273`) |
525
525
  | Isolation cross-origin | WHATWG HTML (COOP/COEP/CORP) | `securityHeaders.ts:71` |
526
- | Anti-MIME-sniffing | WHATWG Fetch (`nosniff`) | `secContentTypeOptions` (`http-kernel.ts:1046`) |
527
- | Durcissement en-têtes | OWASP Secure Headers | `computeSecurityHeaderCaches()` (`http-kernel.ts:358`) |
526
+ | Anti-MIME-sniffing | WHATWG Fetch (`nosniff`) | `secContentTypeOptions` (`http-kernel.ts:1141`) |
527
+ | Durcissement en-têtes | OWASP Secure Headers | `computeSecurityHeaderCaches()` (`http-kernel.ts:411`) |
528
528
 
529
529
  ## ⚡ Performance & mémoire
530
530
 
@@ -538,8 +538,8 @@ Le coût est concentré au boot, par construction :
538
538
  court-circuite entièrement ce chemin sinon. La paresse de `Context.cspNonce` (`Context.ts:253`)
539
539
  protège en plus les chemins internes qui n'atteignent jamais le firewall.
540
540
  - **Merge CSP** : jamais dans le chemin chaud. Le fragment d'un module est fusionné à
541
- l'enregistrement (`firewall.ts:1086`) ; celui d'une route ne coûte que sur les routes `@Csp`.
542
- - **Socle transport** : trois `setHeader` sur des chaînes précalculées (`http-kernel.ts:1047-1053`), avec
541
+ l'enregistrement (`firewall.ts:1142`) ; celui d'une route ne coûte que sur les routes `@Csp`.
542
+ - **Socle transport** : trois `setHeader` sur des chaînes précalculées (`http-kernel.ts:1131-1137`), avec
543
543
  un test `!== null` qui annule le coût des en-têtes désactivés.
544
544
 
545
545
  Le module n'attache aucun écouteur d'événement et ne conserve aucun état par requête : il n'entre pas
@@ -550,7 +550,7 @@ dans le périmètre du gate mémoire, qu'il ne peut structurellement pas dégrad
550
550
  L'écran **Firewall** de Studio affiche la section « En-têtes de sécurité » — pilotée par
551
551
  `headers.enabled` (`FirewallDefenses.tsx:219`) — avec le CSP effectif, l'état du nonce par requête, la
552
552
  Referrer-Policy et les valeurs d'isolation. Les données
553
- viennent de `Firewall.describe()` (`firewall.ts:555`), qui projette la config **sans aucun secret**,
553
+ viennent de `Firewall.describe()` (`firewall.ts:588`), qui projette la config **sans aucun secret**,
554
554
  exposée par `GET /nodefony/security/api/firewall`.
555
555
 
556
556
  L'onglet **Configuration** de Studio rend les mêmes options depuis le schéma Zod — chaque champ y
@@ -570,7 +570,7 @@ porte sa description, ce qui en fait la référence toujours à jour des défaut
570
570
  | CSP contenant `'nonce-{{nonce}}'` littéral | `cspNonces` désactivé, placeholder laissé | Nodefony purge le résiduel (`securityHeaders.ts:63`) ; retirer le placeholder |
571
571
  | Une extension `script-src` de module sans effet | Second en-tête / directive dupliquée (ignorée, W3C CSP3 §3) | Déclarer un fragment via `registerCspOrigins()`, jamais un `setHeader` |
572
572
  | Assets tiers cassés après activation de l'isolation | `coep: "require-corp"` exige CORP/CORS sur **chaque** ressource | Retirer `coep` ou faire annoncer les ressources |
573
- | HSTS absent en développement | Posé sur TLS uniquement (`http-kernel.ts:969`) | Comportement conforme RFC 6797 — vérifier sur le port HTTPS |
573
+ | HSTS absent en développement | Posé sur TLS uniquement (`http-kernel.ts:981`) | Comportement conforme RFC 6797 — vérifier sur le port HTTPS |
574
574
  | Le CSP est plus large en dev qu'en prod | `@nodefony/frontend` déclare les origines Vite (`FrontendService.ts:695`) | Attendu : le fragment n'existe pas en production |
575
575
  | Aucun en-tête applicatif | Module security absent ou `headers.enabled: false` | Le socle transport reste actif ; réactiver security pour CSP/Referrer/isolation |
576
576
  | Domaine injoignable après activation de `preload` | Inscription à la liste pré-chargée, sortie très lente | Ne jamais activer sans plan HTTPS sur **tous** les sous-domaines |
package/docs/index.md CHANGED
@@ -59,9 +59,11 @@ Quatre parcours selon ce que tu viens faire. L'ordre compte : chaque étape supp
59
59
  **J'ouvre un login à des humains** — navigateur, comptes, second facteur.
60
60
 
61
61
  1. [Authenticators](authenticators.md) — la session BFF, et pourquoi le login est **déjà fourni**.
62
- 2. [OAuth2](oauth2.md) — « se connecter avec GitHub/Google » et le Shadow User.
63
- 3. [WebAuthn / passkeys](webauthn.md) — se connecter sans mot de passe, résistant au phishing.
64
- 4. [TOTP](totp.md) — le second facteur classique, et l'élévation de privilège (step-up).
62
+ 2. [Page de connexion](login-page.md) — la page servie à `/login`, et comment l'habiller à ton nom.
63
+ 3. [OAuth2](oauth2.md) — « se connecter avec GitHub/Google » et le Shadow User.
64
+ 4. [Keycloak](keycloak.md) — brancher l'annuaire de l'entreprise, du realm au premier login.
65
+ 5. [WebAuthn / passkeys](webauthn.md) — se connecter sans mot de passe, résistant au phishing.
66
+ 6. [TOTP](totp.md) — le second facteur classique, et l'élévation de privilège (step-up).
65
67
 
66
68
  **J'audite avant une mise en production** — la passe qu'on regrette de ne pas avoir faite.
67
69
 
@@ -86,7 +88,9 @@ Le tableau pour choisir en cinq secondes ; les cards en dessous pour le détail.
86
88
  | [CSRF](csrf.md) | requête authentifiée forgée par un site tiers | tu sers un front avec cookie de session |
87
89
  | [CORS](cors.md) | lecture cross-origine de tes réponses | ton front est sur un autre domaine |
88
90
  | [En-têtes](headers.md) | CSP, HSTS, COOP/COEP, Referrer-Policy | tu sers du HTML à un navigateur |
91
+ | [Page de connexion](login-page.md) | la page `/login` servie, habillable | des humains se connectent à ton application |
89
92
  | [OAuth2](oauth2.md) | login social + provisionnement d'identité | « se connecter avec … » |
93
+ | [Keycloak](keycloak.md) | connexion par l'annuaire auto-hébergé | tes comptes vivent dans un realm Keycloak |
90
94
  | [WebAuthn](webauthn.md) | passkeys, connexion résistante au phishing | tu veux supprimer les mots de passe |
91
95
  | [TOTP](totp.md) | second facteur temporel + step-up | 2FA, ou re-preuve avant une action sensible |
92
96
  | [Webhooks](webhooks.md) | notifier un tiers, signé et sans SSRF | un système externe doit réagir à tes événements |
@@ -103,6 +107,9 @@ Le tableau pour choisir en cinq secondes ; les cards en dessous pour le détail.
103
107
  { "icon": "⚖️", "title": "authorization", "href": "authorization.md",
104
108
  "desc": "Décider des droits : rôles hiérarchisés, scopes, et voters métier qui portent le vrai pouvoir applicatif. Le jury vote, la stratégie tranche.",
105
109
  "meta": "juste après les authenticators — authentifier sans autoriser ne protège rien" },
110
+ { "icon": "🚪", "title": "page de connexion", "href": "login-page.md",
111
+ "desc": "La page servie à /login : identifiant, mot de passe, second facteur, boutons des fournisseurs, thèmes clair et sombre. Textes, panneau et couleurs se règlent par la configuration et une feuille de style — sans copier son balisage.",
112
+ "meta": "le piège : une couleur qui dépend du thème se redéfinit pour les DEUX thèmes" },
106
113
  { "icon": "🎫", "title": "tokens", "href": "tokens.md",
107
114
  "desc": "L'identité matérialisée : émission, keystore, rotation, révocation — et le choix structurant du framework, session opaque côté serveur pour le web, JWT pour les API.",
108
115
  "meta": "et pourquoi ce n'est pas « full stateless »" },
@@ -155,6 +162,7 @@ sans ouvrir de port (profil console) :
155
162
  | `nodefony security:user:delete <id>` | supprime, après confirmation ; refuse le dernier administrateur |
156
163
  | `nodefony security:secrets [--write]` | engendre les clés attendues et guide leur câblage |
157
164
  | `nodefony security:token` | émet un jeton d'accès pour la porte MCP |
165
+ | `nodefony security:keycloak:realm [--write\|--check]` | écrit le realm Keycloak déduit de la configuration ([Keycloak](keycloak.md)) |
158
166
 
159
167
  En terminal, le mot de passe est **demandé masqué** et confirmé ; pour un script, `--password <pwd>`
160
168
  l'accepte en argument — au prix de l'historique du shell, que la commande rappelle.
@@ -176,7 +184,7 @@ au boot. Blocs : `areas` (zones + authenticators), `cors`, `csrf`, `headers`, `r
176
184
  <!-- prettier-ignore -->
177
185
  | Domaine | Normes |
178
186
  | --- | --- |
179
- | Auth / challenge (401) | RFC 7235 |
187
+ | Auth / challenge (401) | RFC 9110 §11 |
180
188
  | JWT | RFC 7519, 8725 (BCP) |
181
189
  | OAuth 2 | RFC 9700 (BCP), 8707, 8693, 9449 (DPoP) |
182
190
  | Passkeys | W3C WebAuthn L3, CTAP2 |