@nodefony/security 10.0.0-alpha.1

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 (258) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +182 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/index.js +151 -0
  6. package/dist/nodefony/command/security-secrets.js +158 -0
  7. package/dist/nodefony/command/security-token.js +335 -0
  8. package/dist/nodefony/command/security-user-add.js +131 -0
  9. package/dist/nodefony/command/security-user-delete.js +102 -0
  10. package/dist/nodefony/command/security-user-list.js +77 -0
  11. package/dist/nodefony/config/config.js +366 -0
  12. package/dist/nodefony/config/defineModuleConfig.js +35 -0
  13. package/dist/nodefony/contracts/IAccessVoter.js +13 -0
  14. package/dist/nodefony/contracts/IApiKey.js +1 -0
  15. package/dist/nodefony/contracts/IAuditEvent.js +1 -0
  16. package/dist/nodefony/contracts/IAuditStore.js +1 -0
  17. package/dist/nodefony/contracts/IAuthenticator.js +1 -0
  18. package/dist/nodefony/contracts/IAuthorizationService.js +1 -0
  19. package/dist/nodefony/contracts/IFirewall.js +1 -0
  20. package/dist/nodefony/contracts/IFirewallDescription.js +1 -0
  21. package/dist/nodefony/contracts/IJwtKeystore.js +1 -0
  22. package/dist/nodefony/contracts/IOAuthProvider.js +1 -0
  23. package/dist/nodefony/contracts/ISecuredArea.js +1 -0
  24. package/dist/nodefony/contracts/IToken.js +1 -0
  25. package/dist/nodefony/contracts/ITokenStore.js +1 -0
  26. package/dist/nodefony/contracts/ITotpSecret.js +1 -0
  27. package/dist/nodefony/contracts/ITotpSecretStore.js +1 -0
  28. package/dist/nodefony/contracts/IWebAuthnCredential.js +1 -0
  29. package/dist/nodefony/contracts/IWebAuthnCredentialStore.js +1 -0
  30. package/dist/nodefony/contracts/IWebhookEndpoint.js +1 -0
  31. package/dist/nodefony/contracts/IWebhookStore.js +1 -0
  32. package/dist/nodefony/contracts/index.js +2 -0
  33. package/dist/nodefony/errors/AccessDeniedError.js +14 -0
  34. package/dist/nodefony/errors/ApiKeyError.js +21 -0
  35. package/dist/nodefony/errors/AuthenticationError.js +14 -0
  36. package/dist/nodefony/errors/CsrfError.js +23 -0
  37. package/dist/nodefony/errors/InvalidTargetError.js +39 -0
  38. package/dist/nodefony/errors/SsrfError.js +17 -0
  39. package/dist/nodefony/errors/ThrottledError.js +21 -0
  40. package/dist/nodefony/errors/UnverifiableTokenError.js +42 -0
  41. package/dist/nodefony/errors/WebAuthnError.js +21 -0
  42. package/dist/nodefony/errors/index.js +9 -0
  43. package/dist/nodefony/service/accessTokenVerifier.js +77 -0
  44. package/dist/nodefony/service/apiKeys.js +310 -0
  45. package/dist/nodefony/service/auditService.js +145 -0
  46. package/dist/nodefony/service/authFlow.js +332 -0
  47. package/dist/nodefony/service/authorization.js +95 -0
  48. package/dist/nodefony/service/cors.js +81 -0
  49. package/dist/nodefony/service/csrf.js +97 -0
  50. package/dist/nodefony/service/firewall.js +699 -0
  51. package/dist/nodefony/service/oauth2.js +153 -0
  52. package/dist/nodefony/service/securityHeaders.js +80 -0
  53. package/dist/nodefony/service/tokenService.js +486 -0
  54. package/dist/nodefony/service/totp.js +209 -0
  55. package/dist/nodefony/service/webAuthn.js +343 -0
  56. package/dist/nodefony/service/webhooks.js +539 -0
  57. package/dist/nodefony/src/RoleHierarchyWalker.js +77 -0
  58. package/dist/nodefony/src/SecuredArea.js +51 -0
  59. package/dist/nodefony/src/admin/SecurityAdminApi.js +495 -0
  60. package/dist/nodefony/src/admin/WebhookAdminApi.js +378 -0
  61. package/dist/nodefony/src/admin/adminAudit.js +37 -0
  62. package/dist/nodefony/src/admin/userRevocationCascade.js +40 -0
  63. package/dist/nodefony/src/apikey/apiKeyFormat.js +107 -0
  64. package/dist/nodefony/src/audit/MemoryAuditStore.js +121 -0
  65. package/dist/nodefony/src/audit/auditBridge.js +82 -0
  66. package/dist/nodefony/src/audit/auditFilters.js +60 -0
  67. package/dist/nodefony/src/audit/auditStoreRegistry.js +25 -0
  68. package/dist/nodefony/src/audit/readAuditContext.js +24 -0
  69. package/dist/nodefony/src/audit/recordAudit.js +16 -0
  70. package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +36 -0
  71. package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +164 -0
  72. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +224 -0
  73. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +174 -0
  74. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +176 -0
  75. package/dist/nodefony/src/authenticator/SessionAuthenticator.js +92 -0
  76. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +95 -0
  77. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +63 -0
  78. package/dist/nodefony/src/authenticator/bearer.js +2 -0
  79. package/dist/nodefony/src/authenticator/externalSubject.js +36 -0
  80. package/dist/nodefony/src/authenticator/peekIssuer.js +56 -0
  81. package/dist/nodefony/src/crypto/secretCipher.js +79 -0
  82. package/dist/nodefony/src/csp.js +54 -0
  83. package/dist/nodefony/src/csrfToken.js +65 -0
  84. package/dist/nodefony/src/net/ssrfGuard.js +130 -0
  85. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +37 -0
  86. package/dist/nodefony/src/oauth/providers/github.js +65 -0
  87. package/dist/nodefony/src/oauth/providers/oidc.js +48 -0
  88. package/dist/nodefony/src/realtime/UserRealtimeToken.js +94 -0
  89. package/dist/nodefony/src/realtime/frameAuthorizer.js +279 -0
  90. package/dist/nodefony/src/realtime/realtimeContracts.js +1 -0
  91. package/dist/nodefony/src/sessionIdentity.js +35 -0
  92. package/dist/nodefony/src/throttle/LoginThrottler.js +97 -0
  93. package/dist/nodefony/src/token/AnonymousToken.js +40 -0
  94. package/dist/nodefony/src/token/JwtKeystore.js +160 -0
  95. package/dist/nodefony/src/token/MemoryTokenStore.js +236 -0
  96. package/dist/nodefony/src/token/RemoteJwtVerifier.js +231 -0
  97. package/dist/nodefony/src/token/UserToken.js +67 -0
  98. package/dist/nodefony/src/token/jwtRuntime.js +19 -0
  99. package/dist/nodefony/src/token/secretFile.js +134 -0
  100. package/dist/nodefony/src/token/tokenCriteria.js +35 -0
  101. package/dist/nodefony/src/token/tokenFilters.js +72 -0
  102. package/dist/nodefony/src/token/tokenSort.js +40 -0
  103. package/dist/nodefony/src/token/tokenStatus.js +35 -0
  104. package/dist/nodefony/src/token/tokenStoreRegistry.js +25 -0
  105. package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +97 -0
  106. package/dist/nodefony/src/totp/totpCipher.js +30 -0
  107. package/dist/nodefony/src/totp/totpCrypto.js +226 -0
  108. package/dist/nodefony/src/totp/totpOperations.js +129 -0
  109. package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +18 -0
  110. package/dist/nodefony/src/voter/RoleVoter.js +32 -0
  111. package/dist/nodefony/src/voter/ScopeVoter.js +52 -0
  112. package/dist/nodefony/src/voter/voterRegistry.js +20 -0
  113. package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +121 -0
  114. package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +18 -0
  115. package/dist/nodefony/src/webhook/MemoryWebhookStore.js +87 -0
  116. package/dist/nodefony/src/webhook/WebhookDispatcher.js +208 -0
  117. package/dist/nodefony/src/webhook/webhookCipher.js +27 -0
  118. package/dist/nodefony/src/webhook/webhookDelivery.js +102 -0
  119. package/dist/nodefony/src/webhook/webhookFilters.js +56 -0
  120. package/dist/nodefony/src/webhook/webhookSignature.js +51 -0
  121. package/dist/nodefony/src/webhook/webhookSort.js +48 -0
  122. package/dist/nodefony/src/webhook/webhookStoreRegistry.js +18 -0
  123. package/dist/types/index.d.ts +157 -0
  124. package/dist/types/nodefony/command/security-secrets.d.ts +24 -0
  125. package/dist/types/nodefony/command/security-token.d.ts +44 -0
  126. package/dist/types/nodefony/command/security-user-add.d.ts +28 -0
  127. package/dist/types/nodefony/command/security-user-delete.d.ts +25 -0
  128. package/dist/types/nodefony/command/security-user-list.d.ts +28 -0
  129. package/dist/types/nodefony/config/config.d.ts +295 -0
  130. package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
  131. package/dist/types/nodefony/contracts/IAccessVoter.d.ts +23 -0
  132. package/dist/types/nodefony/contracts/IApiKey.d.ts +75 -0
  133. package/dist/types/nodefony/contracts/IAuditEvent.d.ts +94 -0
  134. package/dist/types/nodefony/contracts/IAuditStore.d.ts +80 -0
  135. package/dist/types/nodefony/contracts/IAuthenticator.d.ts +66 -0
  136. package/dist/types/nodefony/contracts/IAuthorizationService.d.ts +28 -0
  137. package/dist/types/nodefony/contracts/IFirewall.d.ts +64 -0
  138. package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +120 -0
  139. package/dist/types/nodefony/contracts/IJwtKeystore.d.ts +40 -0
  140. package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +51 -0
  141. package/dist/types/nodefony/contracts/ISecuredArea.d.ts +57 -0
  142. package/dist/types/nodefony/contracts/IToken.d.ts +41 -0
  143. package/dist/types/nodefony/contracts/ITokenStore.d.ts +240 -0
  144. package/dist/types/nodefony/contracts/ITotpSecret.d.ts +41 -0
  145. package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +88 -0
  146. package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +56 -0
  147. package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +118 -0
  148. package/dist/types/nodefony/contracts/IWebhookEndpoint.d.ts +82 -0
  149. package/dist/types/nodefony/contracts/IWebhookStore.d.ts +85 -0
  150. package/dist/types/nodefony/contracts/index.d.ts +9 -0
  151. package/dist/types/nodefony/errors/AccessDeniedError.d.ts +10 -0
  152. package/dist/types/nodefony/errors/ApiKeyError.d.ts +17 -0
  153. package/dist/types/nodefony/errors/AuthenticationError.d.ts +10 -0
  154. package/dist/types/nodefony/errors/CsrfError.d.ts +19 -0
  155. package/dist/types/nodefony/errors/InvalidTargetError.d.ts +34 -0
  156. package/dist/types/nodefony/errors/SsrfError.d.ts +13 -0
  157. package/dist/types/nodefony/errors/ThrottledError.d.ts +16 -0
  158. package/dist/types/nodefony/errors/UnverifiableTokenError.d.ts +37 -0
  159. package/dist/types/nodefony/errors/WebAuthnError.d.ts +17 -0
  160. package/dist/types/nodefony/errors/index.d.ts +8 -0
  161. package/dist/types/nodefony/service/accessTokenVerifier.d.ts +29 -0
  162. package/dist/types/nodefony/service/apiKeys.d.ts +103 -0
  163. package/dist/types/nodefony/service/auditService.d.ts +30 -0
  164. package/dist/types/nodefony/service/authFlow.d.ts +123 -0
  165. package/dist/types/nodefony/service/authorization.d.ts +33 -0
  166. package/dist/types/nodefony/service/cors.d.ts +48 -0
  167. package/dist/types/nodefony/service/csrf.d.ts +57 -0
  168. package/dist/types/nodefony/service/firewall.d.ts +148 -0
  169. package/dist/types/nodefony/service/oauth2.d.ts +66 -0
  170. package/dist/types/nodefony/service/securityHeaders.d.ts +66 -0
  171. package/dist/types/nodefony/service/tokenService.d.ts +103 -0
  172. package/dist/types/nodefony/service/totp.d.ts +58 -0
  173. package/dist/types/nodefony/service/webAuthn.d.ts +123 -0
  174. package/dist/types/nodefony/service/webhooks.d.ts +160 -0
  175. package/dist/types/nodefony/src/RoleHierarchyWalker.d.ts +21 -0
  176. package/dist/types/nodefony/src/SecuredArea.d.ts +31 -0
  177. package/dist/types/nodefony/src/admin/SecurityAdminApi.d.ts +82 -0
  178. package/dist/types/nodefony/src/admin/WebhookAdminApi.d.ts +30 -0
  179. package/dist/types/nodefony/src/admin/adminAudit.d.ts +27 -0
  180. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +31 -0
  181. package/dist/types/nodefony/src/apikey/apiKeyFormat.d.ts +43 -0
  182. package/dist/types/nodefony/src/audit/MemoryAuditStore.d.ts +33 -0
  183. package/dist/types/nodefony/src/audit/auditBridge.d.ts +49 -0
  184. package/dist/types/nodefony/src/audit/auditFilters.d.ts +56 -0
  185. package/dist/types/nodefony/src/audit/auditStoreRegistry.d.ts +37 -0
  186. package/dist/types/nodefony/src/audit/readAuditContext.d.ts +17 -0
  187. package/dist/types/nodefony/src/audit/recordAudit.d.ts +13 -0
  188. package/dist/types/nodefony/src/authenticator/AnonymousAuthenticator.d.ts +26 -0
  189. package/dist/types/nodefony/src/authenticator/ApiKeyAuthenticator.d.ts +74 -0
  190. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +132 -0
  191. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +78 -0
  192. package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +69 -0
  193. package/dist/types/nodefony/src/authenticator/SessionAuthenticator.d.ts +70 -0
  194. package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +53 -0
  195. package/dist/types/nodefony/src/authenticator/authenticatorRegistry.d.ts +39 -0
  196. package/dist/types/nodefony/src/authenticator/bearer.d.ts +22 -0
  197. package/dist/types/nodefony/src/authenticator/externalSubject.d.ts +27 -0
  198. package/dist/types/nodefony/src/authenticator/peekIssuer.d.ts +31 -0
  199. package/dist/types/nodefony/src/crypto/secretCipher.d.ts +31 -0
  200. package/dist/types/nodefony/src/csp.d.ts +39 -0
  201. package/dist/types/nodefony/src/csrfToken.d.ts +36 -0
  202. package/dist/types/nodefony/src/net/ssrfGuard.d.ts +43 -0
  203. package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +45 -0
  204. package/dist/types/nodefony/src/oauth/providers/github.d.ts +9 -0
  205. package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +35 -0
  206. package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +62 -0
  207. package/dist/types/nodefony/src/realtime/frameAuthorizer.d.ts +171 -0
  208. package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +139 -0
  209. package/dist/types/nodefony/src/sessionIdentity.d.ts +20 -0
  210. package/dist/types/nodefony/src/throttle/LoginThrottler.d.ts +68 -0
  211. package/dist/types/nodefony/src/token/AnonymousToken.d.ts +23 -0
  212. package/dist/types/nodefony/src/token/JwtKeystore.d.ts +43 -0
  213. package/dist/types/nodefony/src/token/MemoryTokenStore.d.ts +66 -0
  214. package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +149 -0
  215. package/dist/types/nodefony/src/token/UserToken.d.ts +41 -0
  216. package/dist/types/nodefony/src/token/jwtRuntime.d.ts +28 -0
  217. package/dist/types/nodefony/src/token/secretFile.d.ts +70 -0
  218. package/dist/types/nodefony/src/token/tokenCriteria.d.ts +20 -0
  219. package/dist/types/nodefony/src/token/tokenFilters.d.ts +76 -0
  220. package/dist/types/nodefony/src/token/tokenSort.d.ts +33 -0
  221. package/dist/types/nodefony/src/token/tokenStatus.d.ts +38 -0
  222. package/dist/types/nodefony/src/token/tokenStoreRegistry.d.ts +38 -0
  223. package/dist/types/nodefony/src/totp/MemoryTotpSecretStore.d.ts +43 -0
  224. package/dist/types/nodefony/src/totp/totpCipher.d.ts +9 -0
  225. package/dist/types/nodefony/src/totp/totpCrypto.d.ts +164 -0
  226. package/dist/types/nodefony/src/totp/totpOperations.d.ts +73 -0
  227. package/dist/types/nodefony/src/totp/totpSecretStoreRegistry.d.ts +27 -0
  228. package/dist/types/nodefony/src/voter/RoleVoter.d.ts +25 -0
  229. package/dist/types/nodefony/src/voter/ScopeVoter.d.ts +30 -0
  230. package/dist/types/nodefony/src/voter/voterRegistry.d.ts +33 -0
  231. package/dist/types/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.d.ts +39 -0
  232. package/dist/types/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.d.ts +26 -0
  233. package/dist/types/nodefony/src/webhook/MemoryWebhookStore.d.ts +37 -0
  234. package/dist/types/nodefony/src/webhook/WebhookDispatcher.d.ts +69 -0
  235. package/dist/types/nodefony/src/webhook/webhookCipher.d.ts +8 -0
  236. package/dist/types/nodefony/src/webhook/webhookDelivery.d.ts +28 -0
  237. package/dist/types/nodefony/src/webhook/webhookFilters.d.ts +64 -0
  238. package/dist/types/nodefony/src/webhook/webhookSignature.d.ts +20 -0
  239. package/dist/types/nodefony/src/webhook/webhookSort.d.ts +39 -0
  240. package/dist/types/nodefony/src/webhook/webhookStoreRegistry.d.ts +31 -0
  241. package/docs/api-keys.md +691 -0
  242. package/docs/audit.md +751 -0
  243. package/docs/authenticators.md +487 -0
  244. package/docs/authorization.md +497 -0
  245. package/docs/cors.md +497 -0
  246. package/docs/csrf.md +392 -0
  247. package/docs/external-jwt.md +181 -0
  248. package/docs/firewall.md +546 -0
  249. package/docs/headers.md +616 -0
  250. package/docs/index.md +207 -0
  251. package/docs/lexique.md +190 -0
  252. package/docs/oauth2.md +575 -0
  253. package/docs/obtenir-un-jeton.md +225 -0
  254. package/docs/tokens.md +520 -0
  255. package/docs/totp.md +804 -0
  256. package/docs/webauthn.md +733 -0
  257. package/docs/webhooks.md +1016 -0
  258. package/package.json +83 -0
@@ -0,0 +1,17 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Erreur de **gestion** d'un credential WebAuthn (enrôlement) — porte un `code`
4
+ * HTTP que l'adaptateur framework mappe par duck-typing (il n'importe jamais les
5
+ * classes de `@nodefony/security`).
6
+ *
7
+ * - `409` — plafond `passkeys.maxPerUser` atteint.
8
+ *
9
+ * Distincte de la **cérémonie** elle-même (défi/signature/origine invalides →
10
+ * `AuthenticationError` 401, message uniforme anti-énumération) : ici la
11
+ * cérémonie a réussi cryptographiquement, c'est la politique du serveur qui
12
+ * refuse d'enregistrer un credential de plus.
13
+ */
14
+ export declare class WebAuthnError extends nodefonyError {
15
+ constructor(message: string, code: 409);
16
+ }
17
+ export default WebAuthnError;
@@ -0,0 +1,8 @@
1
+ export { AuthenticationError } from "./AuthenticationError.js";
2
+ export { AccessDeniedError } from "./AccessDeniedError.js";
3
+ export { ThrottledError } from "./ThrottledError.js";
4
+ export { UnverifiableTokenError } from "./UnverifiableTokenError.js";
5
+ export { InvalidTargetError } from "./InvalidTargetError.js";
6
+ export { CsrfError } from "./CsrfError.js";
7
+ export { SsrfError } from "./SsrfError.js";
8
+ export { WebAuthnError } from "./WebAuthnError.js";
@@ -0,0 +1,29 @@
1
+ import { Service, Module } from "nodefony";
2
+ import { RemoteJwtVerifier } from "../src/token/RemoteJwtVerifier.js";
3
+ /**
4
+ * Pose (ou non) le vérificateur de jetons d'accès TIERS dans le conteneur.
5
+ *
6
+ * Ce service est une **décision de câblage**, pas de la cryptographie : toute la
7
+ * mécanique vit dans {@link RemoteJwtVerifier}, et la doctrine de refus dans le
8
+ * cœur (`nodefony/src/oauth/`). Ici, on lit la configuration et on tranche une
9
+ * seule question — cette application accepte-t-elle des jetons émis ailleurs ?
10
+ *
11
+ * **Aucun émetteur déclaré = rien n'est posé**, et c'est le comportement voulu :
12
+ * une porte protégée qui ne trouve pas de vérificateur refuse de servir en le
13
+ * disant (503 + CRITIC), là où un vérificateur présent mais vide refuserait
14
+ * chaque jeton un par un — même résultat pour l'appelant, diagnostic beaucoup
15
+ * plus difficile pour l'exploitant.
16
+ *
17
+ * Le coût est nul quand la capacité n'est pas utilisée : rien n'est instancié,
18
+ * aucune requête n'est faite au démarrage. La découverte des clés n'a lieu qu'au
19
+ * PREMIER jeton réellement présenté, et une seule fois par émetteur.
20
+ */
21
+ declare class AccessTokenVerifierService extends Service {
22
+ #private;
23
+ module: Module;
24
+ constructor(module: Module);
25
+ /** Le vérificateur, ou `null` si aucun émetteur n'est déclaré. */
26
+ get verifier(): RemoteJwtVerifier | null;
27
+ }
28
+ export default AccessTokenVerifierService;
29
+ export { AccessTokenVerifierService };
@@ -0,0 +1,103 @@
1
+ import { Service, Module, type IPage } from "nodefony";
2
+ import { type ITokenCounts } from "../src/token/tokenFilters.js";
3
+ import type { ITokenListQuery } from "../contracts/ITokenStore.js";
4
+ import type { IApiKeyView, IApiKeyCreated, IApiKeyCapabilities, ICreateApiKeyOptions } from "../contracts/IApiKey.js";
5
+ /**
6
+ * Gestion des **clés API personnelles (PAT, P6.12)** — émission, listing et
7
+ * révocation, au-dessus du `ITokenStore` **partagé** (posé au container par le
8
+ * `TokenService`, qui en possède aussi le `gc`). Un PAT et un refresh token
9
+ * cohabitent dans la même table (`kind`) ; ce service ne traite que `kind:"pat"`.
10
+ *
11
+ * **Sécurité** : le secret (256 bits aléatoires) n'est rendu en clair qu'à la
12
+ * création (`IApiKeyCreated.token`, RFC « shown once ») ; seul son `sha256` est
13
+ * persisté. Création/révocation s'appliquent **toujours à un porteur donné**
14
+ * (jamais à autrui) — l'identité est résolue côté endpoint (session BFF). La
15
+ * vérification d'une clé présentée vit, elle, dans `ApiKeyAuthenticator`.
16
+ *
17
+ * Le store est résolu **paresseusement** du container (`tokenStore`) au premier
18
+ * usage : indépendant de l'ordre de boot des services.
19
+ */
20
+ declare class ApiKeyService extends Service {
21
+ #private;
22
+ module: Module;
23
+ constructor(module: Module);
24
+ /** `true` si les clés API sont activées en config. */
25
+ isEnabled(): boolean;
26
+ /**
27
+ * Champs de tri que le backend **actuellement branché** sait honorer, en
28
+ * vocabulaire public. Le data plane admin les passe en allowlist au traducteur
29
+ * de requête de page : hors de cette liste, un `?order=` est refusé en 400.
30
+ *
31
+ * La liste vient du store, jamais d'une constante recopiée ici : c'est ce qui
32
+ * fait qu'un backend à capacité réduite (Redis, dont le `SCAN` n'a pas d'ordre
33
+ * global) refuse le tri **sans qu'aucune règle supplémentaire ne soit écrite**.
34
+ * Store absent ou indisponible → aucune capacité annoncée, donc aucun tri promis.
35
+ *
36
+ * @returns les champs triables, ou un tableau vide.
37
+ */
38
+ sortableFields(): readonly string[];
39
+ /**
40
+ * Émet une nouvelle clé API pour un porteur — renvoie sa vue publique **+ le
41
+ * token en clair** (affiché une seule fois).
42
+ *
43
+ * @throws ApiKeyError 400 — nom vide/trop long, scope hors catalogue, expiry invalide.
44
+ * @throws ApiKeyError 409 — plafond `maxPerSubject` atteint.
45
+ * @throws ApiKeyError 503 — store indisponible.
46
+ */
47
+ createForSubject(subjectId: string, subjectType: "user" | "service", opts: ICreateApiKeyOptions): Promise<IApiKeyCreated>;
48
+ /** Liste les clés (PAT) d'un porteur — vue publique, sans secret, récentes d'abord. */
49
+ listForSubject(subjectId: string): Promise<IApiKeyView[]>;
50
+ /**
51
+ * Liste **paginée** des clés (PAT) du système, tous porteurs confondus — vue
52
+ * d'ADMINISTRATION (gouvernance / réponse à incident), publique et sans secret.
53
+ * Réservé au data plane admin (RBAC `ROLE_NODEFONY_ADMIN`) : l'identité du porteur
54
+ * (`subjectId`) est exposée pour la supervision.
55
+ *
56
+ * Pagination **native au store** (jamais un `listAll()` matérialisé en RAM) : `kind`
57
+ * est forcé à `"pat"` ; les autres filtres (`subjectId`/`revoked`) + la fenêtre
58
+ * (`limit`/`offset`/`cursor`) viennent de `query`. Tri `createdAt` DESC par défaut.
59
+ *
60
+ * @param query - filtres + fenêtre de page ({@link ITokenListQuery}, `kind` ignoré).
61
+ * @returns une page de vues publiques ({@link IApiKeyView}, sans secret).
62
+ */
63
+ listPagePat(query: ITokenListQuery): Promise<IPage<IApiKeyView>>;
64
+ /**
65
+ * Les compteurs de tête de la console — posés sur la collection ENTIÈRE, pas
66
+ * sur la page affichée.
67
+ *
68
+ * Les trois états partitionnent, mais chacun est **compté** : une partition
69
+ * est une propriété du domaine d'aujourd'hui, pas une garantie du code, et un
70
+ * quatrième état la briserait en silence si l'un se déduisait des autres.
71
+ *
72
+ * `kind` reste forcé à `"pat"` comme pour la liste : ces cartes surplombent
73
+ * un tableau de clés d'API, pas de jetons de rafraîchissement.
74
+ *
75
+ * @param query - filtres à appliquer avant comptage (sans fenêtre).
76
+ */
77
+ countKeyFacets(query?: Partial<ITokenListQuery>): Promise<ITokenCounts>;
78
+ /**
79
+ * Révoque **n'importe quelle** clé (PAT) — action d'ADMINISTRATION (réponse à
80
+ * incident : clé compromise), SANS contrainte de porteur (≠ `revokeForSubject`).
81
+ * Audité avec l'acteur admin ET le porteur cible. Idempotent.
82
+ *
83
+ * @param id - identifiant public de la clé.
84
+ * @param actorId - identité de l'admin qui révoque (tracée pour l'audit).
85
+ * @returns la vue publique mise à jour, ou `null` si introuvable / pas un PAT.
86
+ */
87
+ revokeAnyPat(id: string, actorId: string): Promise<IApiKeyView | null>;
88
+ /**
89
+ * Capacités/contraintes d'émission (plafond, scopes, préfixe, durée par défaut)
90
+ * — pour un formulaire de création honnête côté console. Aucune valeur sensible.
91
+ */
92
+ describeCapabilities(): IApiKeyCapabilities;
93
+ /**
94
+ * Révoque une clé du porteur. **Anti-énumération** : une clé inexistante OU
95
+ * appartenant à autrui renvoie `false` (« introuvable pour ce porteur ») —
96
+ * jamais un 403 qui révélerait son existence. Idempotent (déjà révoquée → `true`).
97
+ *
98
+ * @returns `true` si la clé du porteur a été trouvée (et révoquée), sinon `false`.
99
+ */
100
+ revokeForSubject(subjectId: string, id: string): Promise<boolean>;
101
+ }
102
+ export default ApiKeyService;
103
+ export { ApiKeyService };
@@ -0,0 +1,30 @@
1
+ import { Service, Module, type IPage } from "nodefony";
2
+ import type { IAuditEvent, IAuditEventDraft } from "../contracts/IAuditEvent.js";
3
+ import type { IAuditListQuery, IAuditSink } from "../contracts/IAuditStore.js";
4
+ /**
5
+ * Journal d'audit de sécurité (P6.14) — collecte les **événements** de sécurité
6
+ * (login, refus d'accès, jeton émis/révoqué, défense CSRF/CORS, verrou WS) émis
7
+ * EXPLICITEMENT par le firewall, les authenticators et les controllers. Distinct
8
+ * du log de trafic (`JsonAuditLogger`, P3.1, 1 PDU/requête) : ici on trace les
9
+ * **transitions d'état** de sécurité, jamais le hot-path par requête.
10
+ *
11
+ * Propriétaire du {@link IAuditStore} (référence mémoire append-only) : le pose au
12
+ * container (`auditStore`, consommé par le data plane P6.15) et arme le `gc` de
13
+ * rétention (timer `unref`). Implémente {@link IAuditSink} — `record` est
14
+ * **fire-and-forget** (jamais bloquant) et **no-op à coût nul** si désactivé.
15
+ *
16
+ * Slot : store **pluggable** (ORM/Loki, multi-pod) = lot futur, comme
17
+ * `tokenStoreRegistry`. Le socle n'embarque que la référence mémoire.
18
+ */
19
+ declare class AuditService extends Service implements IAuditSink {
20
+ #private;
21
+ module: Module;
22
+ constructor(module: Module);
23
+ record(draft: IAuditEventDraft): void;
24
+ subscribe(listener: (event: IAuditEvent) => void): () => void;
25
+ /** Lit une page du journal (délègue au store) ; vide si l'audit est inactif. */
26
+ listPage(query: IAuditListQuery): Promise<IPage<IAuditEvent>>;
27
+ /** `true` si l'audit est actif (config `audit.enabled`). */
28
+ isEnabled(): boolean;
29
+ }
30
+ export default AuditService;
@@ -0,0 +1,123 @@
1
+ import { Service, Module } from "nodefony";
2
+ import type { ContextType, ISession } from "@nodefony/http";
3
+ /**
4
+ * Projection PUBLIQUE de l'utilisateur — ce qui sort en JSON vers le client.
5
+ * Jamais l'entité brute : le hash (`IPasswordAuthenticatedUser.password`) ne
6
+ * doit traverser ni la sérialisation ni un log.
7
+ */
8
+ export interface ISafeUser {
9
+ id: string;
10
+ username: string;
11
+ roles: string[];
12
+ }
13
+ /**
14
+ * Issue d'un `login` : soit l'identité est établie (session ouverte), soit un
15
+ * **second facteur** est requis (2FA) — le mot de passe seul n'a PAS authentifié.
16
+ */
17
+ export type ILoginOutcome = {
18
+ status: "authenticated";
19
+ user: ISafeUser;
20
+ } | {
21
+ status: "mfa_required";
22
+ methods: ["totp"];
23
+ };
24
+ /**
25
+ * Flux de session BFF — login/logout/me côté serveur (P6 J3).
26
+ *
27
+ * C'est le GUICHET : le credential est présenté UNE fois (`login`), vérifié par
28
+ * le {@link IPasswordVerifier} (hash, leurre anti-timing, re-hash migration),
29
+ * puis remplacé par un cookie de session opaque `HttpOnly` — le mot de passe ne
30
+ * recircule jamais, le navigateur ne stocke aucun token lisible par JS.
31
+ *
32
+ * Anti session-fixation (OWASP) : l'ID de session est TOUJOURS régénéré au
33
+ * login — un ID pré-posé par un attaquant (cookie forcé avant le guichet) ne
34
+ * survit pas à l'authentification ; l'ancienne entrée storage est détruite.
35
+ *
36
+ * Throttling NIST SP 800-63B : le MÊME `LoginThrottler` que la porte Basic
37
+ * (instance partagée via le container, posée par le firewall au boot) — un
38
+ * attaquant ne contourne pas le backoff en changeant de porte.
39
+ *
40
+ * Les handlers HTTP (`SessionAuthController`, `@nodefony/framework`) sont des
41
+ * adaptateurs minces au-dessus de ce service — la logique reste testable sans
42
+ * transport.
43
+ */
44
+ declare class AuthFlow extends Service {
45
+ #private;
46
+ module: Module;
47
+ constructor(module: Module);
48
+ /**
49
+ * Authentifie le couple identifiant/mot de passe et OUVRE la session BFF.
50
+ *
51
+ * Ordre NIST : throttle AVANT le verifier (un identifiant bloqué ne coûte
52
+ * aucun hash argon2 — le backoff protège aussi le serveur du DoS), échec
53
+ * compté, succès remis à zéro.
54
+ *
55
+ * @param context - contexte HTTP courant (porte la session/le cookie).
56
+ * @param identifier - identifiant saisi (body JSON, non typé à la frontière).
57
+ * @param password - mot de passe saisi.
58
+ * @returns `authenticated` (identité établie) ou `mfa_required` (2ᵉ facteur requis).
59
+ * @throws ThrottledError (429 + `Retry-After`) — backoff actif.
60
+ * @throws AuthenticationError (401, message uniforme) — credential absent ou
61
+ * invalide.
62
+ */
63
+ login(context: ContextType, identifier: unknown, password: unknown): Promise<ILoginOutcome>;
64
+ /**
65
+ * Ouvre la session BFF pour un utilisateur **déjà authentifié par un autre
66
+ * facteur** (passkey/WebAuthn, OAuth, magic link…) — aucun mot de passe à
67
+ * vérifier ici, la preuve a été apportée en amont par l'appelant.
68
+ *
69
+ * Même anti-fixation que {@link login} (ID régénéré, ancienne entrée
70
+ * détruite). L'identité est re-résolue + revalidée (compte actif/non
71
+ * verrouillé) via la source unique `resolveSessionIdentity` — un compte banni
72
+ * entre la preuve et l'ouverture de session est rejeté.
73
+ *
74
+ * @param identifier - identifiant de l'utilisateur prouvé (ex. `sub` du credential).
75
+ * @throws AuthenticationError (401, message uniforme) — identifiant absent, ou
76
+ * compte disparu/inactif/verrouillé.
77
+ */
78
+ establishSessionFor(context: ContextType, identifier: unknown, reason?: string): Promise<ISafeUser>;
79
+ /**
80
+ * Valide le **second facteur** (code TOTP ou code de récupération) après un
81
+ * `login` ayant renvoyé `mfa_required`, puis OUVRE la session BFF. Le défi
82
+ * PENDING déposé en session par `login` est lu, vérifié, puis invalidé (usage
83
+ * unique) ; l'identité n'est établie qu'ICI. **Throttlé** sur l'identité en
84
+ * attente (anti brute-force du code à 6 chiffres).
85
+ *
86
+ * @param context - contexte HTTP (porte la session PENDING).
87
+ * @param code - code présenté (TOTP ou code de récupération).
88
+ * @returns la projection publique de l'utilisateur authentifié.
89
+ * @throws ThrottledError (429) — trop de tentatives.
90
+ * @throws AuthenticationError (401, uniforme) — aucun défi en cours, code absent
91
+ * ou invalide (la session N'est PAS ouverte ; le défi reste pour un retry).
92
+ */
93
+ completeMfaLogin(context: ContextType, code: unknown): Promise<ISafeUser>;
94
+ /**
95
+ * Garantit une session pour la requête courante — la démarre (+ cookie) si
96
+ * elle n'existe pas encore. Sert aux cérémonies **pré-authentification**
97
+ * (login WebAuthn) : elles doivent porter un challenge côté serveur AVANT que
98
+ * l'utilisateur soit connecté. La session anonyme ainsi créée devient la
99
+ * session authentifiée au login — {@link establishSessionFor} régénère l'ID
100
+ * (anti-fixation préservée), donc un challenge déposé ici n'ouvre aucune brèche.
101
+ *
102
+ * @returns la session (existante ou neuve), ou `null` si le service de session
103
+ * est indisponible.
104
+ */
105
+ ensureSession(context: ContextType): Promise<ISession | null>;
106
+ /**
107
+ * Détruit la session courante (storage + cookie). Idempotent : sans session
108
+ * active, ne fait rien.
109
+ *
110
+ * @returns `true` si une session a réellement été détruite.
111
+ */
112
+ logout(context: ContextType): Promise<boolean>;
113
+ /**
114
+ * Identité portée par la session courante, re-résolue auprès du provider
115
+ * (mêmes contrôles que le `SessionAuthenticator` — source unique).
116
+ *
117
+ * @returns la projection publique, ou `null` (pas de session, session
118
+ * orpheline, compte verrouillé/désactivé) — le handler répond 401.
119
+ */
120
+ me(context: ContextType): Promise<ISafeUser | null>;
121
+ }
122
+ export default AuthFlow;
123
+ export { AuthFlow };
@@ -0,0 +1,33 @@
1
+ import { Service, Module, Severity, Msgid, Message, Pdu } from "nodefony";
2
+ import type { IAuthorizationService } from "../contracts/IAuthorizationService.js";
3
+ import type { IToken } from "../contracts/IToken.js";
4
+ /**
5
+ * Service d'autorisation Nodefony (niveau C, P6 J6) — décide d'un accès via un
6
+ * jury de {@link IAccessVoter}.
7
+ *
8
+ * Stratégie **affirmative + DENY veto** : un seul `DENY` bloque (veto) ; sinon un
9
+ * `GRANT` suffit ; **silence total** (tous `ABSTAIN`, ou aucun voter compétent)
10
+ * → `DENY` (**Zero Trust** : fermé par défaut). Tout refus est audité (WARNING) ;
11
+ * les accès accordés restent silencieux (pas de spam — l'audit d'octroi explicite
12
+ * viendra avec `@AuditLog`, P6.14).
13
+ *
14
+ * Les voters sont découverts au boot via le `voterRegistry` (built-in `role` +
15
+ * ceux des apps/plugins) — aucun nom en dur ici. Consommé par les décorateurs
16
+ * (`@IsGranted`, J7) et le verrou de frame WS (« 1 garde = N transports »).
17
+ *
18
+ * Perf : aucune allocation par appel (`decide` itère les voters et teste
19
+ * `supports()` en place) ; les voters sont instanciés UNE fois au boot.
20
+ */
21
+ declare class Authorization extends Service implements IAuthorizationService {
22
+ #private;
23
+ module: Module;
24
+ constructor(module: Module);
25
+ /**
26
+ * Le token a-t-il le droit `attribute` sur `subject` ? Affirmative + DENY veto,
27
+ * défaut `DENY` (Zero Trust). Voir {@link IAuthorizationService.decide}.
28
+ */
29
+ decide(token: IToken, attribute: string, subject?: unknown): Promise<boolean>;
30
+ log(pci: unknown, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
31
+ }
32
+ export default Authorization;
33
+ export { Authorization };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Sous-ensemble de la config `cors` consommé par la politique (cf defineSecurityConfig).
3
+ */
4
+ export interface ICorsOptions {
5
+ enabled: boolean;
6
+ origins: readonly string[];
7
+ credentials: boolean;
8
+ methods: readonly string[];
9
+ allowedHeaders: readonly string[];
10
+ exposedHeaders: readonly string[];
11
+ maxAgeS: number;
12
+ }
13
+ /** En-têtes de réponse CORS à poser (nom canonique → valeur). */
14
+ export type CorsHeaders = Record<string, string>;
15
+ /**
16
+ * Politique CORS de Nodefony — Same-Origin Policy assouplie côté serveur
17
+ * (Fetch Standard / W3C CORS protocol). Logique PURE et synchrone : décide les
18
+ * en-têtes `Access-Control-*` à poser pour une origine donnée. Instanciée une
19
+ * fois au boot par le firewall, testable sans serveur.
20
+ *
21
+ * Invariants de sécurité (OWASP) :
22
+ * - **Jamais `*` + credentials** : interdit au boot (refine Zod). Ici, `*` n'est
23
+ * émis QUE si `credentials=false` ; avec credentials, l'origine est reflétée.
24
+ * - **Reflet d'origine ⇒ `Vary: Origin`** : signalé via {@link reflectsOrigin}
25
+ * pour que l'appelant pose l'en-tête `Vary` (correction de cache).
26
+ * - **Origine non whitelistée ⇒ aucun en-tête** : la réponse n'est pas partageable
27
+ * (le navigateur bloque), zéro information divulguée.
28
+ *
29
+ * @see Fetch Standard (CORS protocol) · OWASP CORS.
30
+ */
31
+ export declare class Cors {
32
+ #private;
33
+ constructor(options: ICorsOptions);
34
+ /** `true` si la valeur Allow-Origin reflète l'origine (⇒ l'appelant doit poser `Vary: Origin`). */
35
+ reflectsOrigin(allowOrigin: string): boolean;
36
+ /**
37
+ * En-têtes d'une réponse au **preflight** `OPTIONS` (méthodes/headers autorisés
38
+ * + cache + credentials), ou `null` si l'origine n'est pas autorisée (réponse
39
+ * 204 nue → le navigateur bloque).
40
+ */
41
+ preflightHeaders(origin: string): CorsHeaders | null;
42
+ /**
43
+ * En-têtes d'une réponse à une **requête réelle** cross-origin (Allow-Origin +
44
+ * credentials + headers exposés au JS), ou `null` si l'origine n'est pas autorisée.
45
+ */
46
+ actualHeaders(origin: string): CorsHeaders | null;
47
+ }
48
+ export default Cors;
@@ -0,0 +1,57 @@
1
+ /** Sous-ensemble de la config `csrf` consommé par la défense (cf defineSecurityConfig). */
2
+ export interface ICsrfOptions {
3
+ enabled: boolean;
4
+ fetchMetadata: boolean;
5
+ checkOrigin: boolean;
6
+ strictSameSite: boolean;
7
+ }
8
+ /** En-têtes bruts d'une requête, extraits par le firewall (clés HTTP en lowercase). */
9
+ export interface ICsrfRequest {
10
+ /** Méthode HTTP (`context.method`). */
11
+ method: string | null | undefined;
12
+ /** `Sec-Fetch-Site` — provenance tamponnée par le navigateur (défense primaire). */
13
+ secFetchSite: string | undefined;
14
+ /** `Origin` — origine du document initiateur (fallback). */
15
+ origin: string | undefined;
16
+ /** `Referer` — utilisé seulement si `Origin` est absent (fallback). */
17
+ referer: string | undefined;
18
+ /** Hôte cible (`context.domain` / en-tête `Host`) — pour le test same-host du fallback. */
19
+ host: string | undefined;
20
+ }
21
+ /**
22
+ * Défense CSRF par défaut de Nodefony — **Fetch Metadata d'abord** (modèle Go 1.25
23
+ * `CrossOriginProtection` / OWASP 2025), repli `Origin`/`Referer` pour les vieux
24
+ * navigateurs. Logique PURE et synchrone (aucun I/O, aucune alloc sur le hot-path
25
+ * GET) → testable sans serveur, instanciée une seule fois au boot par le firewall.
26
+ *
27
+ * Chaîne de décision sur une requête state-changing :
28
+ *
29
+ * 1. **Origine de confiance** (alias multi-domaine `csrf.trustedOrigins` ∪ whitelist
30
+ * CORS) → laisser passer même en cross-site : un alias légitime de l'app, ou ce
31
+ * que CORS autorise déjà, n'est pas du CSRF (cohérence CSRF ↔ CORS).
32
+ * 2. **Fetch Metadata** (`Sec-Fetch-Site`, infalsifiable) : `same-origin`/`none`
33
+ * → OK ; `same-site` → OK sauf `strictSameSite` ; `cross-site` → **403** ;
34
+ * valeur inconnue → on délègue au repli (forward-compat, W3C « SHOULD ignore »).
35
+ * 3. **Repli `Origin`/`Referer`** : aucune des deux → client non-navigateur, hors
36
+ * vecteur CSRF → OK ; sinon same-host requis, mismatch → **403**.
37
+ *
38
+ * @see RFC 9110 §9.2.1 (méthodes sûres) · W3C Fetch Metadata · RFC 6265bis §8.8.1.
39
+ */
40
+ export declare class Csrf {
41
+ #private;
42
+ /**
43
+ * `true` si la méthode mute l'état (hors {@link SAFE_METHODS}). Permet à
44
+ * l'appelant (firewall) de court-circuiter le hot-path GET sans lire d'en-tête.
45
+ */
46
+ static isStateChanging(method: string | null | undefined): boolean;
47
+ constructor(options: ICsrfOptions, allowedOrigins?: readonly string[]);
48
+ /**
49
+ * Valide la provenance d'une requête. No-op (retour immédiat) sur une méthode
50
+ * sûre — coût nul sur le GET dominant. Lève {@link CsrfError} (403) sinon.
51
+ *
52
+ * @throws CsrfError - mutation `cross-site` (Fetch Metadata) ou `Origin`/`Referer`
53
+ * étranger aux origines de l'app (repli).
54
+ */
55
+ enforce(req: ICsrfRequest): void;
56
+ }
57
+ export default Csrf;
@@ -0,0 +1,148 @@
1
+ import { Service, Module, Severity, Msgid, Message, Pdu } from "nodefony";
2
+ import type { IProtectedResourceInput } from "nodefony";
3
+ import type { ContextType } from "@nodefony/http";
4
+ import { SecuredArea } from "../src/SecuredArea.js";
5
+ import { RoleHierarchyWalker } from "../src/RoleHierarchyWalker.js";
6
+ import { type CspFragment } from "../src/csp.js";
7
+ import type { IFirewall } from "../contracts/IFirewall.js";
8
+ import type { IAuthenticator } from "../contracts/IAuthenticator.js";
9
+ import type { ISecuredArea } from "../contracts/ISecuredArea.js";
10
+ import type { IFirewallDescription, IRoleHierarchyDescription } from "../contracts/IFirewallDescription.js";
11
+ /**
12
+ * Orchestrateur de sécurité Nodefony — refonte 2026 (P6).
13
+ *
14
+ * `isSecure()` (hot-path, court-circuit si aucune zone) ne fait QUE matcher la
15
+ * zone et poser `context.security`. `handleSecurity()` (lazy, seulement sur une
16
+ * zone protégée) exécute la chaîne d'authentication selon le `mode` de la zone
17
+ * (`first` : le premier qui reconnaît la requête authentifie ; `all` : tous
18
+ * doivent passer, le dernier porte l'identité) → propage l'utilisateur dans
19
+ * l'ALS → applique le **Zero Trust** (zone protégée sans preuve acceptée → 401,
20
+ * sauf anonymat explicite via l'authenticator `anonymous`).
21
+ *
22
+ * **Fail-closed** : config invalide au boot (Zod, nom d'authenticator inconnu)
23
+ * → le firewall capture TOUT le trafic et répond 401 (jamais une app servie
24
+ * sans sa sécurité). Erreur interne pendant l'authentification (source
25
+ * d'identité down, câblage manquant) → log ERROR serveur + 401 générique
26
+ * (aucun détail ne fuite au client).
27
+ *
28
+ * Conformité : tout 401 porte un challenge `WWW-Authenticate` (RFC 7235) fourni
29
+ * par le premier authenticator de la zone qui en déclare un.
30
+ *
31
+ * CORS, CSRF et autorisation par décorateurs viennent se brancher en S4/S5.
32
+ * Toutes les structures sont **lazy** (perf : une app sans zone = zéro alloc).
33
+ */
34
+ declare class Firewall extends Service implements IFirewall {
35
+ #private;
36
+ module: Module;
37
+ constructor(module: Module);
38
+ /** Hiérarchie de rôles résolue (niveau A de l'autorisation, P6.8). */
39
+ get roleHierarchy(): RoleHierarchyWalker;
40
+ /**
41
+ * `true` si l'un des rôles de l'utilisateur couvre `required` (hiérarchie
42
+ * comprise). Surface lue par le verrou de frame WS ({@link buildFrameAuthorizer})
43
+ * pour le RBAC par canal — délègue au {@link RoleHierarchyWalker}.
44
+ */
45
+ hasRole(userRoles: readonly string[], required: string): boolean;
46
+ registerAuthenticator(authenticator: IAuthenticator): void;
47
+ getArea(name: string): ISecuredArea | undefined;
48
+ describe(): IFirewallDescription;
49
+ /**
50
+ * Hiérarchie de rôles déclarée + résolution transitive (data plane Studio).
51
+ * Brut = ce que l'app a écrit ; `inherits` = aplati précalculé par le walker.
52
+ */
53
+ describeRoleHierarchy(): IRoleHierarchyDescription;
54
+ /**
55
+ * Ce que l'application PROTÈGE, à publier en RFC 9728 — une entrée par
56
+ * ressource déclarée par une zone.
57
+ *
58
+ * ⭐ **Même donnée que le défi, donc impossible qu'ils divergent.** Le `401`
59
+ * d'une zone porte `resource_metadata="…/.well-known/oauth-protected-resource
60
+ * /<chemin de `area.resource`>"` ; ce que rend cette méthode est la source de
61
+ * ce que `@nodefony/framework` monte à cette URL. Une seconde déclaration —
62
+ * une clé « ressources publiées » à côté des zones — se serait périmée au
63
+ * premier renommage, et le symptôme aurait été un `404` que rien n'explique.
64
+ *
65
+ * 🔴 **Les serveurs d'autorisation sont les émetteurs de confiance, pas une
66
+ * liste à part.** `authorization_servers` répond à « qui peut délivrer un
67
+ * jeton pour cette ressource ? » — c'est exactement l'allowlist
68
+ * `resourceServer.issuers`, la seule que le vérificateur consulte. Publier
69
+ * autre chose reviendrait à envoyer le client demander un jeton à un émetteur
70
+ * dont on refuse ensuite la signature.
71
+ *
72
+ * Aucun émetteur de confiance ⇒ **rien à publier** : un document sans serveur
73
+ * d'autorisation apprendrait au client qu'un jeton est nécessaire sans jamais
74
+ * lui dire où l'obtenir (et la RFC 9728 comme la spécification MCP l'excluent).
75
+ *
76
+ * Appelée UNE fois, au montage des routes (`onKernelReady`) — hors hot path,
77
+ * d'où l'allocation directe plutôt qu'un cache à invalider.
78
+ *
79
+ * @returns les ressources protégées déclarées, éventuellement vide
80
+ */
81
+ publishedProtectedResources(): readonly IProtectedResourceInput[];
82
+ /**
83
+ * Match de zone par pathname (+ host) SANS contexte — source UNIQUE consultée
84
+ * par `isSecure` (HTTP) ET le verrou WebSocket (la frame `api.request` n'a
85
+ * qu'un path). Hot-path : patterns pré-compilés + pathname fourni → 0 alloc.
86
+ */
87
+ matchPath(pathname: string, host?: string): SecuredArea | null;
88
+ /** Match rapide de zone — pose `context.security`. `true` si zone capturée. */
89
+ isSecure(context: ContextType): boolean;
90
+ /**
91
+ * Pipeline complet de la zone : chaîne d'authenticators (selon `mode`) → ALS
92
+ * → Zero Trust. Rejette (401, challenge RFC 7235 posé) ou résout.
93
+ */
94
+ handleSecurity(context: ContextType): Promise<ContextType>;
95
+ /**
96
+ * Défense CSRF (P6 J5/étape 2) — branchée dans le pipeline HTTP de `@nodefony/http`
97
+ * pour TOUTE requête (zone ou non), APRÈS le resolve (les marqueurs `@CsrfProtect`/
98
+ * `@CsrfExempt` de la route sont disponibles). Trois rôles :
99
+ *
100
+ * - **Émission** : sur une requête SÛRE vers une route `@CsrfProtect`, minte le
101
+ * synchronizer token (`context.csrfToken`) — HttpContext pose ensuite le cookie
102
+ * lisible `csrf-token`. Sinon, hot-path GET = retour immédiat (aucun en-tête lu).
103
+ * - **Étape 1 (globale)** : sur une mutation, défense Fetch Metadata / Origin
104
+ * (rejet cross-site même sur route publique). Skippée si `@CsrfExempt` (webhook,
105
+ * auth par signature/clé) ou `bypassFirewall` (callbacks OAuth).
106
+ * - **Étape 2 (opt-in)** : sur une mutation `@CsrfProtect`, exige EN PLUS le
107
+ * synchronizer token (en-tête `x-csrf-token` ≡ cookie + HMAC valide).
108
+ *
109
+ * @throws CsrfError (403) — provenance tierce, ou synchronizer token absent/invalide.
110
+ */
111
+ enforceCsrf(context: ContextType): void;
112
+ /**
113
+ * Politique CORS (P6 J5) — appelée par `HttpKernel.handleHttp()`
114
+ * (`http-kernel.ts:1169`), en TÊTE du pipeline : avant le routing, avant le parse
115
+ * du corps, donc bien avant `handleFrontController` et le firewall. Un preflight
116
+ * n'a pas de route déclarée — router d'abord lèverait un 405.
117
+ * Pose les en-têtes `Access-Control-*` et **court-circuite le
118
+ * preflight** `OPTIONS` en `204` (le preflight ne porte jamais de credentials,
119
+ * Fetch Standard → il ne doit ni router ni s'authentifier). No-op hors requête
120
+ * cross-origin (pas d'`Origin`), CORS désactivé, ou réponse non-HTTP (WS).
121
+ *
122
+ * @returns `204` si la requête est un preflight (l'appelant court-circuite la
123
+ * réponse), sinon `undefined` (la requête réelle suit le pipeline normal).
124
+ */
125
+ handleCors(context: ContextType): number | undefined;
126
+ /**
127
+ * En-têtes de sécurité APPLICATIFS (P6 J5) — CSP, Referrer-Policy, isolation
128
+ * cross-origin (COOP/COEP/CORP), Origin-Agent-Cluster, Permissions-Policy.
129
+ * Posés sur toute réponse du pipeline (branché dans `handleHttp`). Complète le
130
+ * socle transport de `@nodefony/http` (nosniff/frame/HSTS, posé à l'entrée brute)
131
+ * SANS le ré-émettre. No-op si désactivé ou réponse non-HTTP (WS).
132
+ *
133
+ * En-têtes constants = table figée pré-calculée au boot (0 alloc/concat). Le CSP
134
+ * nonce/req (étape B) ajoute 1 `join` + 1 `setHeader` UNIQUEMENT si activé.
135
+ */
136
+ applySecurityHeaders(context: ContextType): void;
137
+ /**
138
+ * Déclare des directives CSP additionnelles pour `moduleName` (cf `IFirewall`).
139
+ * No-op si les en-têtes applicatifs sont désactivés (pas de CSP à étendre).
140
+ * Recompose `#securityHeaders` (merge + re-split nonce) — hors hot-path.
141
+ */
142
+ registerCspOrigins(moduleName: string, fragment: CspFragment): void;
143
+ /** Retire les directives CSP de `moduleName` et recompose si nécessaire. */
144
+ unregisterCspOrigins(moduleName: string): void;
145
+ log(pci: unknown, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
146
+ }
147
+ export default Firewall;
148
+ export { Firewall };