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

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 (249) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/index.js +48 -16
  3. package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +62 -24
  4. package/dist/nodefony/command/security-oauth-doctor.js +68 -0
  5. package/dist/nodefony/command/security-secrets.js +52 -29
  6. package/dist/nodefony/command/security-user-add.js +9 -3
  7. package/dist/nodefony/command/security-user-delete.js +10 -7
  8. package/dist/nodefony/command/security-user-list.js +5 -3
  9. package/dist/nodefony/command/security-user-password.js +9 -14
  10. package/dist/nodefony/config/config.js +99 -8
  11. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  12. package/dist/nodefony/contracts/IAccessVoter.js +1 -2
  13. package/dist/nodefony/contracts/index.js +1 -2
  14. package/dist/nodefony/errors/AccessDeniedError.js +1 -2
  15. package/dist/nodefony/errors/ApiKeyError.js +3 -2
  16. package/dist/nodefony/errors/AuthenticationError.js +1 -2
  17. package/dist/nodefony/errors/CsrfError.js +1 -2
  18. package/dist/nodefony/errors/InsecureTransportError.js +23 -0
  19. package/dist/nodefony/errors/InvalidTargetError.js +1 -2
  20. package/dist/nodefony/errors/SsrfError.js +1 -2
  21. package/dist/nodefony/errors/ThrottledError.js +1 -2
  22. package/dist/nodefony/errors/UnverifiableTokenError.js +1 -2
  23. package/dist/nodefony/errors/WebAuthnError.js +1 -2
  24. package/dist/nodefony/errors/index.js +2 -1
  25. package/dist/nodefony/keycloak/keycloakAdmin.js +254 -0
  26. package/dist/nodefony/keycloak/keycloakRealm.js +259 -0
  27. package/dist/nodefony/keycloak/keycloakRealmInput.js +123 -0
  28. package/dist/nodefony/keycloak/scaffold.js +231 -0
  29. package/dist/nodefony/keycloak/security-keycloak-realm.js +130 -0
  30. package/dist/nodefony/scaffold/contribute.js +19 -0
  31. package/dist/nodefony/service/accessTokenVerifier.js +101 -34
  32. package/dist/nodefony/service/apiKeys.js +45 -5
  33. package/dist/nodefony/service/auditService.js +4 -5
  34. package/dist/nodefony/service/authFlow.js +118 -16
  35. package/dist/nodefony/service/authorization.js +1 -2
  36. package/dist/nodefony/service/cors.js +1 -2
  37. package/dist/nodefony/service/csrf.js +1 -2
  38. package/dist/nodefony/service/firewall.js +116 -25
  39. package/dist/nodefony/service/keycloak.js +208 -0
  40. package/dist/nodefony/service/oauth2.js +403 -28
  41. package/dist/nodefony/service/securityHeaders.js +1 -2
  42. package/dist/nodefony/service/tokenService.js +94 -27
  43. package/dist/nodefony/service/totp.js +55 -11
  44. package/dist/nodefony/service/webAuthn.js +24 -7
  45. package/dist/nodefony/service/webhooks.js +4 -5
  46. package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
  47. package/dist/nodefony/src/SecuredArea.js +15 -5
  48. package/dist/nodefony/src/accountBinding.js +96 -0
  49. package/dist/nodefony/src/admin/SecurityAdminApi.js +71 -42
  50. package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
  51. package/dist/nodefony/src/admin/adminAudit.js +1 -3
  52. package/dist/nodefony/src/admin/userRevocationCascade.js +148 -20
  53. package/dist/nodefony/src/apikey/apiKeyFormat.js +1 -2
  54. package/dist/nodefony/src/audit/MemoryAuditStore.js +1 -2
  55. package/dist/nodefony/src/audit/auditBridge.js +1 -2
  56. package/dist/nodefony/src/audit/auditFilters.js +1 -2
  57. package/dist/nodefony/src/audit/auditStoreRegistry.js +1 -2
  58. package/dist/nodefony/src/audit/readAuditContext.js +1 -2
  59. package/dist/nodefony/src/audit/recordAudit.js +1 -2
  60. package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +1 -2
  61. package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +3 -2
  62. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +14 -59
  63. package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
  64. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +80 -19
  65. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +92 -16
  66. package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
  67. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +14 -5
  68. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +5 -12
  69. package/dist/nodefony/src/authenticator/bearer.js +1 -2
  70. package/dist/nodefony/src/authenticator/externalSubject.js +1 -2
  71. package/dist/nodefony/src/authenticator/peekIssuer.js +1 -2
  72. package/dist/nodefony/src/boot/publicRoutesNotice.js +26 -0
  73. package/dist/nodefony/src/crypto/secretCipher.js +1 -2
  74. package/dist/nodefony/src/csp.js +1 -2
  75. package/dist/nodefony/src/csrfToken.js +1 -2
  76. package/dist/nodefony/src/entryPoint.js +38 -0
  77. package/dist/nodefony/src/net/ssrfGuard.js +1 -2
  78. package/dist/nodefony/src/net/transportFailure.js +57 -0
  79. package/dist/nodefony/src/oauth/clientCredentials.js +39 -0
  80. package/dist/nodefony/src/oauth/httpJson.js +3 -3
  81. package/dist/nodefony/src/oauth/metadata.js +28 -12
  82. package/dist/nodefony/src/oauth/oauth2Client.js +61 -35
  83. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +40 -8
  84. package/dist/nodefony/src/oauth/providerDiagnosis.js +282 -0
  85. package/dist/nodefony/src/oauth/providerRoles.js +78 -0
  86. package/dist/nodefony/src/oauth/providers/github.js +48 -12
  87. package/dist/nodefony/src/oauth/providers/oidc.js +90 -8
  88. package/dist/nodefony/src/openToSignup.js +46 -0
  89. package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
  90. package/dist/nodefony/src/realtime/frameAuthorizer.js +1 -2
  91. package/dist/nodefony/src/rolesWithin.js +57 -0
  92. package/dist/nodefony/src/sessionAuthentication.js +25 -0
  93. package/dist/nodefony/src/sessionIdentity.js +29 -3
  94. package/dist/nodefony/src/throttle/LoginThrottler.js +1 -2
  95. package/dist/nodefony/src/token/AnonymousToken.js +1 -2
  96. package/dist/nodefony/src/token/JwtKeystore.js +89 -48
  97. package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
  98. package/dist/nodefony/src/token/RemoteJwtVerifier.js +46 -16
  99. package/dist/nodefony/src/token/UserToken.js +1 -2
  100. package/dist/nodefony/src/token/jwtRuntime.js +1 -2
  101. package/dist/nodefony/src/token/scopeGrants.js +109 -0
  102. package/dist/nodefony/src/token/secretFile.js +60 -4
  103. package/dist/nodefony/src/token/tokenCriteria.js +1 -2
  104. package/dist/nodefony/src/token/tokenFilters.js +1 -2
  105. package/dist/nodefony/src/token/tokenSort.js +1 -2
  106. package/dist/nodefony/src/token/tokenStatus.js +1 -2
  107. package/dist/nodefony/src/token/tokenStoreRegistry.js +1 -2
  108. package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +1 -2
  109. package/dist/nodefony/src/totp/totpCipher.js +1 -2
  110. package/dist/nodefony/src/totp/totpCrypto.js +1 -2
  111. package/dist/nodefony/src/totp/totpOperations.js +1 -2
  112. package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +1 -2
  113. package/dist/nodefony/src/transport/CredentialTransportPolicy.js +50 -0
  114. package/dist/nodefony/src/voter/RoleVoter.js +1 -2
  115. package/dist/nodefony/src/voter/ScopeVoter.js +1 -2
  116. package/dist/nodefony/src/voter/voterRegistry.js +1 -2
  117. package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +1 -2
  118. package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +1 -2
  119. package/dist/nodefony/src/webhook/MemoryWebhookStore.js +1 -2
  120. package/dist/nodefony/src/webhook/WebhookDispatcher.js +1 -2
  121. package/dist/nodefony/src/webhook/webhookCipher.js +1 -2
  122. package/dist/nodefony/src/webhook/webhookDelivery.js +1 -2
  123. package/dist/nodefony/src/webhook/webhookFilters.js +1 -2
  124. package/dist/nodefony/src/webhook/webhookSignature.js +1 -2
  125. package/dist/nodefony/src/webhook/webhookSort.js +1 -2
  126. package/dist/nodefony/src/webhook/webhookStoreRegistry.js +1 -2
  127. package/dist/types/index.d.ts +26 -6
  128. package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +16 -6
  129. package/dist/types/nodefony/command/security-oauth-doctor.d.ts +23 -0
  130. package/dist/types/nodefony/command/security-secrets.d.ts +2 -0
  131. package/dist/types/nodefony/command/security-user-delete.d.ts +4 -3
  132. package/dist/types/nodefony/command/security-user-password.d.ts +4 -4
  133. package/dist/types/nodefony/config/config.d.ts +64 -1
  134. package/dist/types/nodefony/contracts/IAuthenticator.d.ts +1 -1
  135. package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +42 -1
  136. package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +48 -0
  137. package/dist/types/nodefony/contracts/ISecuredArea.d.ts +22 -0
  138. package/dist/types/nodefony/contracts/ITotpSecret.d.ts +4 -1
  139. package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +7 -1
  140. package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +4 -1
  141. package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +6 -0
  142. package/dist/types/nodefony/errors/ApiKeyError.d.ts +3 -1
  143. package/dist/types/nodefony/errors/InsecureTransportError.d.ts +20 -0
  144. package/dist/types/nodefony/errors/index.d.ts +1 -0
  145. package/dist/types/nodefony/keycloak/keycloakAdmin.d.ts +154 -0
  146. package/dist/types/nodefony/keycloak/keycloakRealm.d.ts +138 -0
  147. package/dist/types/nodefony/keycloak/keycloakRealmInput.d.ts +92 -0
  148. package/dist/types/nodefony/keycloak/scaffold.d.ts +98 -0
  149. package/dist/types/nodefony/keycloak/security-keycloak-realm.d.ts +43 -0
  150. package/dist/types/nodefony/scaffold/contribute.d.ts +15 -0
  151. package/dist/types/nodefony/service/accessTokenVerifier.d.ts +35 -14
  152. package/dist/types/nodefony/service/apiKeys.d.ts +13 -2
  153. package/dist/types/nodefony/service/authFlow.d.ts +35 -3
  154. package/dist/types/nodefony/service/firewall.d.ts +17 -3
  155. package/dist/types/nodefony/service/keycloak.d.ts +115 -0
  156. package/dist/types/nodefony/service/oauth2.d.ts +145 -9
  157. package/dist/types/nodefony/service/tokenService.d.ts +7 -1
  158. package/dist/types/nodefony/service/totp.d.ts +28 -4
  159. package/dist/types/nodefony/service/webAuthn.d.ts +23 -1
  160. package/dist/types/nodefony/src/SecuredArea.d.ts +4 -0
  161. package/dist/types/nodefony/src/accountBinding.d.ts +82 -0
  162. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +72 -12
  163. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -26
  164. package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
  165. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +17 -1
  166. package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
  167. package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +11 -3
  168. package/dist/types/nodefony/src/boot/publicRoutesNotice.d.ts +16 -0
  169. package/dist/types/nodefony/src/entryPoint.d.ts +37 -0
  170. package/dist/types/nodefony/src/net/transportFailure.d.ts +22 -0
  171. package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
  172. package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
  173. package/dist/types/nodefony/src/oauth/metadata.d.ts +24 -1
  174. package/dist/types/nodefony/src/oauth/oauth2Client.d.ts +50 -1
  175. package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +46 -2
  176. package/dist/types/nodefony/src/oauth/providerDiagnosis.d.ts +117 -0
  177. package/dist/types/nodefony/src/oauth/providerRoles.d.ts +58 -0
  178. package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +34 -1
  179. package/dist/types/nodefony/src/openToSignup.d.ts +36 -0
  180. package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +7 -1
  181. package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +6 -0
  182. package/dist/types/nodefony/src/rolesWithin.d.ts +27 -0
  183. package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
  184. package/dist/types/nodefony/src/sessionIdentity.d.ts +22 -0
  185. package/dist/types/nodefony/src/token/JwtKeystore.d.ts +47 -5
  186. package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +29 -2
  187. package/dist/types/nodefony/src/token/scopeGrants.d.ts +77 -0
  188. package/dist/types/nodefony/src/token/secretFile.d.ts +22 -0
  189. package/dist/types/nodefony/src/transport/CredentialTransportPolicy.d.ts +47 -0
  190. package/docs/api-keys.md +182 -175
  191. package/docs/audit.md +141 -141
  192. package/docs/authenticators.md +129 -96
  193. package/docs/authorization.md +116 -69
  194. package/docs/cors.md +64 -64
  195. package/docs/csrf.md +77 -72
  196. package/docs/external-jwt.md +83 -11
  197. package/docs/firewall.md +119 -83
  198. package/docs/headers.md +82 -82
  199. package/docs/index.md +16 -8
  200. package/docs/keycloak.md +835 -0
  201. package/docs/login-page.md +361 -0
  202. package/docs/oauth2.md +291 -166
  203. package/docs/obtenir-un-jeton.md +131 -34
  204. package/docs/tokens.md +197 -140
  205. package/docs/totp.md +177 -136
  206. package/docs/webauthn.md +157 -154
  207. package/docs/webhooks.md +128 -125
  208. package/keycloak/themes/nodefony/account/resources/css/nodefony.css +66 -0
  209. package/keycloak/themes/nodefony/account/resources/img/favicon.ico +0 -0
  210. package/keycloak/themes/nodefony/account/resources/img/logo.png +0 -0
  211. package/keycloak/themes/nodefony/account/theme.properties +14 -0
  212. package/keycloak/themes/nodefony/admin/resources/css/nodefony.css +66 -0
  213. package/keycloak/themes/nodefony/admin/resources/img/favicon.ico +0 -0
  214. package/keycloak/themes/nodefony/admin/resources/img/logo.png +0 -0
  215. package/keycloak/themes/nodefony/admin/theme.properties +12 -0
  216. package/keycloak/themes/nodefony/email/html/template.ftl +73 -0
  217. package/keycloak/themes/nodefony/email/messages/messages_en.properties +2 -0
  218. package/keycloak/themes/nodefony/email/messages/messages_fr.properties +3 -0
  219. package/keycloak/themes/nodefony/email/resources/img/logo.png +0 -0
  220. package/keycloak/themes/nodefony/email/theme.properties +8 -0
  221. package/keycloak/themes/nodefony/login/messages/messages_en.properties +22 -0
  222. package/keycloak/themes/nodefony/login/messages/messages_fr.properties +25 -0
  223. package/keycloak/themes/nodefony/login/resources/css/login.css +1141 -0
  224. package/keycloak/themes/nodefony/login/resources/css/nodefony.css +688 -0
  225. package/keycloak/themes/nodefony/login/resources/css/skins/blueprint.css +70 -0
  226. package/keycloak/themes/nodefony/login/resources/css/skins/console.css +57 -0
  227. package/keycloak/themes/nodefony/login/resources/css/skins/dots.css +67 -0
  228. package/keycloak/themes/nodefony/login/resources/css/skins/enterprise.css +84 -0
  229. package/keycloak/themes/nodefony/login/resources/css/skins/horizon.css +158 -0
  230. package/keycloak/themes/nodefony/login/resources/css/skins/ledger.css +224 -0
  231. package/keycloak/themes/nodefony/login/resources/css/skins/minimal.css +63 -0
  232. package/keycloak/themes/nodefony/login/resources/css/skins/photo-card.css +72 -0
  233. package/keycloak/themes/nodefony/login/resources/img/favicon.ico +0 -0
  234. package/keycloak/themes/nodefony/login/resources/img/logo.png +0 -0
  235. package/keycloak/themes/nodefony/login/template.ftl +423 -0
  236. package/keycloak/themes/nodefony/login/theme.properties +17 -0
  237. package/keycloak/themes/nodefony/welcome/resources/css/nodefony.css +32 -0
  238. package/keycloak/themes/nodefony/welcome/resources/img/logo.png +0 -0
  239. package/keycloak/themes/nodefony/welcome/theme.properties +11 -0
  240. package/keycloak/themes/nodefony-blueprint/login/theme.properties +12 -0
  241. package/keycloak/themes/nodefony-console/login/theme.properties +12 -0
  242. package/keycloak/themes/nodefony-dots/login/theme.properties +12 -0
  243. package/keycloak/themes/nodefony-enterprise/login/theme.properties +12 -0
  244. package/keycloak/themes/nodefony-horizon/login/theme.properties +12 -0
  245. package/keycloak/themes/nodefony-ledger/login/theme.properties +12 -0
  246. package/keycloak/themes/nodefony-minimal/login/theme.properties +12 -0
  247. package/keycloak/themes/nodefony-photo-card/login/theme.properties +12 -0
  248. package/package.json +19 -17
  249. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
@@ -5,7 +5,7 @@ lang: fr
5
5
  module: "@nodefony/security"
6
6
  topic: obtenir-un-jeton
7
7
  coverageModule: security
8
- coverageFiles: "security-token,tokenService,secretFile"
8
+ coverageFiles: "security-mcp-token,tokenService,secretFile"
9
9
  section: "Sécurité"
10
10
  audience: [developer, devops]
11
11
  tags:
@@ -33,7 +33,7 @@ source: "src/packages/@nodefony/security/docs/obtenir-un-jeton.md"
33
33
  > l'application répond un couple access/refresh. Cette page décrit l'autre voie, celle dont un
34
34
  > client MCP ou un script d'exploitation a besoin : **l'application signe elle-même un jeton, en
35
35
  > ligne de commande**, sans qu'aucun serveur n'écoute et sans qu'aucun mot de passe ne transite.
36
- > Ancré sur `src/packages/@nodefony/security/nodefony/command/security-token.ts`.
36
+ > Ancré sur `src/packages/@nodefony/security/nodefony/command/security-mcp-token.ts`.
37
37
 
38
38
  📍 [Documentation](../../../../../docs/index.md) › [Sécurité](index.md) › **Obtenir un jeton**
39
39
 
@@ -43,7 +43,7 @@ Trois questions, et une seule commande y répond.
43
43
 
44
44
  ```mermaid
45
45
  flowchart LR
46
- A["nodefony security:token"] --> B{"L'application possède<br/>sa clé de signature"}
46
+ A["nodefony security:mcp:token"] --> B{"L'application possède<br/>sa clé de signature"}
47
47
  B --> C["Jeton signé pour<br/>UNE audience"]
48
48
  C --> D["--write : posé où<br/>l'agent le lit"]
49
49
  C --> E["--json : capturé<br/>par un script"]
@@ -51,11 +51,11 @@ flowchart LR
51
51
  ```
52
52
 
53
53
  **Aucun serveur n'est en marche.** La commande démarre le noyau jusqu'à `onReady` — services prêts,
54
- aucune écoute réseau (`security-token.ts:36`). C'est ce qui la rend utilisable quand l'application
54
+ aucune écoute réseau (`security-mcp-token.ts#options`). C'est ce qui la rend utilisable quand l'application
55
55
  ne tourne pas, en intégration continue, ou dans un conteneur d'amorçage.
56
56
 
57
57
  **Le jeton vise une porte, et une seule.** L'audience est celle de la porte visée — la porte MCP par
58
- défaut, `/nodefony/mcp` (`src/nodefony/src/mcp/protocol.ts:95`). Un jeton d'une autre audience est
58
+ défaut, `/nodefony/mcp` (`src/nodefony/src/cli/aiMcpReport.ts#MCP_ENDPOINT_PATH`). Un jeton d'une autre audience est
59
59
  refusé, et c'est toute la raison d'être de la liaison d'audience
60
60
  ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)).
61
61
 
@@ -69,7 +69,7 @@ refusé, et c'est toute la raison d'être de la liaison d'audience
69
69
  - **Audience** — la ressource pour laquelle un jeton est valable. Déclarée par l'émetteur ; un
70
70
  porteur ne choisit pas la sienne.
71
71
  - **Scope** — un pouvoir nommé, porté par le jeton. `admin:read` lit, `admin:write` mute
72
- (`src/nodefony/src/kernel/adminPlane/adminCaller.ts:53`).
72
+ (`src/nodefony/src/kernel/adminPlane/adminCaller.ts#ADMIN_SCOPE_READ`).
73
73
 
74
74
  ## Qu'est-ce que ça résout — la faille du `curl` avec mot de passe
75
75
 
@@ -85,13 +85,13 @@ pas de mot de passe, pas de réseau — et rien à effacer d'un historique.
85
85
 
86
86
  ```bash
87
87
  # 1) Le jeton, pour la porte MCP de cette application
88
- npx nodefony security:token
88
+ npx nodefony security:mcp:token
89
89
 
90
90
  # 2) Le poser là où l'agent le lit — jamais dans un fichier suivi par git
91
- npx nodefony security:token --write
91
+ npx nodefony security:mcp:token --write
92
92
 
93
93
  # 3) Pour un script : la sortie machine
94
- npx nodefony security:token --json
94
+ npx nodefony security:mcp:token --json
95
95
  # {
96
96
  # "access_token": "eyJ…",
97
97
  # "resource": "http://localhost:5151/nodefony/mcp",
@@ -125,25 +125,32 @@ Puis `npm run build` — le runtime lit le `dist`, pas la source.
125
125
 
126
126
  ### Les options, et ce que chacune décide
127
127
 
128
- | Option | Défaut | Ce qu'elle change |
129
- | ---------------------- | ----------------------------- | -------------------------------------------------------- |
130
- | `[identifier]` | `admin` | le compte porteur du jeton |
131
- | `-s, --scope <liste>` | `admin:read` | les pouvoirs demandés — ajouter `admin:write` pour muter |
132
- | `-r, --resource <uri>` | la porte MCP de l'application | viser une **autre** audience |
133
- | `-a, --agent <noms>` | ceux détectés | quels agents servir — `none` pour aucun |
134
- | `-t, --ttl <minutes>` | celle de la config (15 min) | la durée de validité, **bornée à 30 jours** |
135
- | `-w, --write` | proposé en terminal | poser `NF_MCP_TOKEN` chez les agents présents |
136
- | `-j, --json` | — | sortie machine, sans invite |
128
+ | Option | Défaut | Ce qu'elle change |
129
+ | ---------------------- | -------------------------------------------- | -------------------------------------------------------- |
130
+ | `[identifier]` | `admin` | le compte porteur du jeton |
131
+ | `-s, --scope <liste>` | `admin:read` | les pouvoirs demandés — ajouter `admin:write` pour muter |
132
+ | `-r, --resource <uri>` | la porte MCP de l'application | viser une **autre** audience |
133
+ | `-a, --agent <noms>` | ceux détectés | quels agents servir — `none` pour aucun |
134
+ | `-t, --ttl <minutes>` | 8 h avec `--write`, sinon la config (15 min) | la durée de validité, **bornée à 30 jours** |
135
+ | `-w, --write` | proposé en terminal | poser `NF_MCP_TOKEN` chez les agents présents |
136
+ | `-j, --json` | — | sortie machine, sans invite |
137
137
 
138
138
  Le plafond de `--ttl` n'est pas décoratif : un jeton posé dans un fichier **est une clé**, et une
139
- clé se remplace (`security-token.ts:67-84`).
139
+ clé se remplace (`security-mcp-token.ts#ttlSeconds`).
140
+
141
+ **Posé chez un agent, le jeton vit 8 h par défaut** (`security-mcp-token.ts#tokenTtlSeconds`). L'en-tête d'un
142
+ agent est figé : rien ne le rafraîchit, et le défaut de la configuration (15 min, taillé pour un
143
+ client d'API qui renouvelle son jeton) produisait un 401 au bout d'un quart d'heure. Huit heures
144
+ couvrent une journée de travail et bornent une fuite à quelques heures ; `--ttl` explicite
145
+ l'emporte toujours, et le défaut global `jwt.accessTtlS` ne bouge pas. La commande affiche l'heure
146
+ d'**expiration** (`expires_at` en `--json`) : c'est elle qui explique le 401 du lendemain.
140
147
 
141
148
  ### Où `--write` pose la valeur
142
149
 
143
- La variable est `NF_MCP_TOKEN` (`src/nodefony/src/cli/aiMcpReport.ts:32`). La commande ne sert que
150
+ La variable est `NF_MCP_TOKEN` (`src/nodefony/src/cli/aiMcpReport.ts#MCP_TOKEN_ENV`). La commande ne sert que
144
151
  les agents dont la présence est **constatée** dans le projet — on ne crée pas la configuration d'un
145
152
  outil que personne n'utilise ici. La table des cibles vit au cœur
146
- (`src/nodefony/src/cli/agentTargets.ts:280`) : Claude, Gemini, Vibe, Codex.
153
+ (`src/nodefony/src/cli/agentTargets.ts#AGENT_TARGETS`) : Claude, Gemini, Vibe, Codex.
147
154
 
148
155
  Deux garanties qui ne se négocient pas :
149
156
 
@@ -170,38 +177,128 @@ codex mcp add nodefony --url http://localhost:5151/nodefony/mcp \
170
177
  Pour câbler `.mcp.json` d'un seul geste : `npx nodefony ai:mcp --auth`. La déclaration de la porte
171
178
  appartient à [devkit](../../devkit/docs/index.md) ; cette page ne fait qu'émettre le porteur.
172
179
 
180
+ ## 🔐 Ce que la porte vérifie — un jeton de cette application face à une ressource
181
+
182
+ Une ressource protégée — une porte MCP d'application (`nodefony create controller <nom> --kind mcp
183
+ --expose oauth`), ou la porte de développement quand elle déclare un serveur d'autorisation — ne
184
+ vérifie pas le jeton elle-même : elle le confie au service `accessTokenVerifier`, avec **son** URI
185
+ comme audience. Ce service reconnaît **toujours** l'émetteur de l'application, dès que sa capacité
186
+ JWT est active (`security.jwt.enabled`, vrai par défaut), **sans rien déclarer** dans
187
+ `resourceServer.issuers` (`src/packages/@nodefony/security/nodefony/service/accessTokenVerifier.ts#AccessTokenVerifierService.#build`).
188
+ Une application qui signe un jeton pour sa propre porte peut donc l'y présenter.
189
+
190
+ **L'aiguillage se fait sur l'émetteur revendiqué, comparé EXACTEMENT** à `security.jwt.issuer`
191
+ (`accessTokenVerifier.ts#AccessTokenVerifierService.#build`). Égal : le jeton est jugé par les règles de l'authentificateur `jwt`,
192
+ par la même fonction (`src/packages/@nodefony/security/nodefony/src/authenticator/JwtAuthenticator.ts#JwtAuthenticator.#verify`).
193
+ Différent : il est jugé par la liste fermée des émetteurs tiers (Keycloak…, voir
194
+ [Accepter les jetons d'un émetteur tiers](external-jwt.md)) — inconnu d'elle, il est refusé sans
195
+ aucune requête sortante.
196
+
197
+ Un jeton de l'application n'est accepté que si **chaque** contrôle passe, dans cet ordre :
198
+
199
+ | # | Contrôle | Refusé, par exemple | Norme |
200
+ | --- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
201
+ | 1 | signature par une clé du jeu LOCAL, algorithme `EdDSA` seul | `alg: none`, `HS256` signé avec la clé publique, clé d'un tiers sous le même `kid` | [RFC 8725](https://www.rfc-editor.org/rfc/rfc8725.html) §3.1, §3.5 |
202
+ | 2 | `iss` = l'émetteur de l'application | `https://app.example/` quand l'émetteur est `https://app.example` (aiguillé ailleurs) | RFC 8725 §3.8 |
203
+ | 3 | `aud` contient l'URI de la porte | un jeton émis pour une AUTRE porte de la même application | [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html), RFC 8725 §3.9 |
204
+ | 4 | `typ: at+jwt` | un jeton de rafraîchissement présenté comme jeton d'accès, un `typ` absent | [RFC 9068](https://www.rfc-editor.org/rfc/rfc9068.html), RFC 8725 §3.11 |
205
+ | 5 | `exp` et `nbf` | expiré, pas encore valide | [RFC 7519](https://www.rfc-editor.org/rfc/rfc7519.html) §4.1.4-5 |
206
+ | 6 | `sub` et `jti` présents | un jeton sans identifiant — donc impossible à révoquer | |
207
+ | 7 | `jti` absent de la liste de révocation | un jeton révoqué individuellement | |
208
+ | 8 | émis APRÈS le seuil `invalidBefore` du compte | tout jeton antérieur à un changement de mot de passe ou à une déconnexion globale | |
209
+ | 9 | le compte existe, est actif et n'est pas verrouillé | le jeton d'un compte désactivé, verrouillé ou supprimé | RFC 8725 §3.10 |
210
+
211
+ Les contrôles 7 et 8 viennent **avant** la lecture du compte : un jeton révoqué est refusé sans
212
+ interroger l'annuaire (`JwtAuthenticator.ts#JwtAuthenticator.#verify`). Une PANNE de la liste de révocation est un
213
+ `503` ; une panne de l'annuaire, elle, se lit comme un compte absent — `401`. Dans les deux cas le
214
+ jeton n'est jamais servi : c'est une limite de diagnostic, pas d'accès. Le jeton accepté donne à la porte un principal :
215
+ son sujet (`sub`, l'identifiant du compte local) et ses scopes, qui décident des outils servis.
216
+
217
+ | Verdict du vérificateur | Réponse de la porte |
218
+ | -------------------------------------------------------------- | -------------------------------------------------------------- |
219
+ | principal | servi — les outils réservés que ses scopes ouvrent en plus |
220
+ | refus (l'un des contrôles) | `401` + `WWW-Authenticate`, qui désigne le document RFC 9728 |
221
+ | invérifiable (clés, liste de révocation ou annuaire absents) | `503`, et le journal crie — jamais un jeton accepté par défaut |
222
+ | aucun vérificateur (capacité JWT coupée, aucun émetteur tiers) | `503` |
223
+
224
+ **Révoquer un jeton émis**, c'est révoquer le compte qui le porte : `npx nodefony
225
+ security:user:password <compte>` pose le seuil du contrôle 8 — le prochain appel reçoit `401`, avant
226
+ l'expiration du jeton. C'est éprouvé sur un serveur réel par le banc de vérité du générateur
227
+ (étape « la porte MCP protégée s'ouvre… et se referme révoquée »).
228
+
229
+ **Ce que la règle n'élargit pas.** Aucun jeton nouveau ne devient valide : l'émetteur ne signe que
230
+ pour les audiences de `jwt.audiences`, et l'audience reste exigée à chaque porte. La zone de
231
+ pare-feu `external-jwt` ne prend, elle, que les jetons des émetteurs qu'ELLE déclare : la règle ne
232
+ lui ouvre rien. Et si `resourceServer.issuers` est mal configurée, **aucun** vérificateur n'est
233
+ posé, jetons de l'application compris — une configuration fausse ne se sert pas à moitié
234
+ (`accessTokenVerifier.ts#AccessTokenVerifierService.#build`).
235
+
236
+ > ⚠️ **En production, pose `NF_JWT_ISSUER`.** Sans elle, l'émetteur vaut `"nodefony"`, qui n'est
237
+ > pas une URL : les jetons se vérifient, mais l'application ne publie ni ses clés ni ses métadonnées,
238
+ > et une porte qui annonce `https://…` comme serveur d'autorisation désigne une adresse dont
239
+ > l'émetteur réel n'est pas celui des jetons.
240
+
241
+ Pour une porte d'application, l'audience à déclarer est l'URI publique de la porte. Dans une
242
+ application générée, écris-la **sur place** dans `jwt.audiences` de `nodefony/config/security.ts` :
243
+
244
+ ```typescript
245
+ audiences: ctx.isProd
246
+ ? []
247
+ : [
248
+ // … les audiences du gabarit
249
+ `${publicBaseUrl(ctx.env)}/api/mcp`,
250
+ ],
251
+ ```
252
+
253
+ ⚠️ N'y importe pas la fonction `…GateResource` du fichier de la porte : ce fichier importe
254
+ `security.ts`, et le cycle ferait lire `publicBaseUrl` avant sa définition — `ReferenceError` au
255
+ démarrage.
256
+
173
257
  ## 🔄 Renouveler — c'est réémettre, pas rafraîchir
174
258
 
175
259
  Il n'y a pas de refresh token ici : ce jeton est signé hors bande, pour une porte, avec une durée
176
- courte. Le renouveler, c'est **relancer la commande**. Et comme `--write` ne remplace jamais une
177
- valeur en place, une rotation se demande :
260
+ courte. Le renouveler, c'est **relancer la commande** : `--write` **remplace** le jeton chez les
261
+ agents qui le portent déjà, sans reposer de question, et ne câble aucun agent nouveau sans le
262
+ proposer (`agentTargets.ts#poseVariable`). Puis relancer l'agent, qui lit sa configuration au
263
+ démarrage.
178
264
 
179
265
  ```bash
180
- # 1) retirer l'ancienne valeur là où elle est posée (le fichier est nommé par la commande)
181
- # 2) réémettre
182
- npx nodefony security:token --write --ttl 1440
266
+ npx nodefony security:mcp:token --write --ttl 1440
183
267
  ```
184
268
 
269
+ Un jeton expiré ne casse rien de visible chez l'agent : la porte le sert en **anonyme** et retient
270
+ les outils réservés. `nodefony doctor` le signale dans sa famille « Fraîcheur du build », avec
271
+ cette commande pour geste (`freshness.ts#checkAgentTokens`) — pour les fichiers du projet
272
+ seulement : le dossier de l'utilisateur est partagé entre applications, et `nodefony ai:mcp` en
273
+ rend compte.
274
+
185
275
  Le flux de rotation **avec détection de rejeu** est celui des refresh tokens de la porte HTTP — il
186
276
  est décrit dans [tokens](tokens.md), pas ici.
187
277
 
188
278
  ## ⚠️ Pièges (symptôme → cause → correction)
189
279
 
190
- | Symptôme | Cause | Correction |
191
- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
192
- | `impossible d'émettre un jeton pour cette porte ici` | **en développement** : l'audience n'est pas déclarée à l'émetteur — c'est une liste blanche, pas un défaut ouvert | `use("@nodefony/security", { jwt: { audiences: ["<la porte>"] } })`, puis `npm run build` |
193
- | Le même message, mais l'environnement affiché n'est pas `development` | la porte MCP est servie par un module `policy: "dev"` : **elle n'existe pas** dans cet environnement | `NODE_ENV=development npx nodefony security:token`, ou viser une autre porte avec `--resource` |
194
- | Le jeton est émis, mais avec **moins** de scopes que demandé | l'émetteur retire ceux que ce porteur ne peut pas obtenir, et il le dit ([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) §3.3) | lire le champ `scopes` de la sortie — c'est celui qui fait foi (`security-token.ts:494`) |
195
- | L'agent reçoit `401` alors que le jeton vient d'être posé | la valeur a été écrite dans un dossier que **cet** agent ne lit pas | `--agent <nom>` pour viser explicitement, ou l'`export` dans le shell qui lance l'agent |
196
- | `--write` ne fait rien et n'écrit aucun fichier | aucun agent n'est **constaté** dans ce projet | la commande liste alors les emplacements connus et donne la ligne d'`export` |
280
+ | Symptôme | Cause | Correction |
281
+ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
282
+ | `impossible d'émettre un jeton pour cette porte ici` | **en développement** : l'audience n'est pas déclarée à l'émetteur — c'est une liste blanche, pas un défaut ouvert | `use("@nodefony/security", { jwt: { audiences: ["<la porte>"] } })`, puis `npm run build` |
283
+ | Le même message, mais l'environnement affiché n'est pas `development` | la porte MCP est servie par un module `policy: "dev"` : **elle n'existe pas** dans cet environnement | `NODE_ENV=development npx nodefony security:mcp:token`, ou viser une autre porte avec `--resource` |
284
+ | Le jeton est émis, mais avec **moins** de scopes que demandé | l'émetteur retire ceux que ce porteur ne peut pas obtenir, et il le dit ([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) §3.3) | lire le champ `scopes` de la sortie — c'est celui qui fait foi (`security-mcp-token.ts#SecurityMcpToken.generate`) |
285
+ | L'agent reçoit `401` alors que le jeton vient d'être posé | la valeur a été écrite dans un dossier que **cet** agent ne lit pas | `--agent <nom>` pour viser explicitement, ou l'`export` dans le shell qui lance l'agent |
286
+ | `--write` ne fait rien et n'écrit aucun fichier | aucun agent n'est **constaté** dans ce projet | la commande liste alors les emplacements connus et donne la ligne d'`export` |
197
287
 
198
288
  > 🔴 **Le premier réflexe est faux.** Devant un refus, on cherche un serveur éteint — la commande
199
289
  > n'en utilise aucun. Le message écarte cette fausse piste dès sa deuxième ligne
200
- > (`security-token.ts:456-484`) ; il a lui-même été corrigé pour cesser d'accuser une cause unique
290
+ > (`security-mcp-token.ts#SecurityMcpToken.generate`) ; il a lui-même été corrigé pour cesser d'accuser une cause unique
201
291
  > qu'il ne constatait pas.
202
292
 
203
293
  ## 🧪 Tests & couverture
204
294
 
295
+ - **unit** : `accessTokenVerifier.attack.test.ts` éprouve la vérification d'un jeton de
296
+ l'application face à une ressource — chacun des neuf contrôles ci-dessus par un jeton qui le viole,
297
+ l'aiguillage entre émetteur local et tiers, les entrées hostiles, la configuration fausse — et
298
+ chacune de ses règles a été vue tomber en la débranchant ;
299
+ - **serveur réel** : le banc de vérité du générateur (`verify-generated.mjs`) ouvre la porte
300
+ d'une application engendrée avec `security:mcp:token`, la voit refuser un jeton d'une autre audience,
301
+ puis refuser le premier jeton après `security:user:password` ;
205
302
  - **unit** : `securityCommands.test.ts` couvre les deux règles pures de la commande — un jeton
206
303
  **mort-né** doit s'annoncer avant d'être copié, et `--ttl` doit s'écrire en minutes, refuser une
207
304
  valeur aberrante et se borner à 30 jours (`ttlSeconds` est exportée pour cela).