@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,66 @@
1
+ import { Service, Module } from "nodefony";
2
+ /** Données à porter en session entre `authorize` et `callback` (anti-replay). */
3
+ export interface IOAuthAuthorization {
4
+ /** URL d'autorisation vers laquelle rediriger l'utilisateur. */
5
+ readonly url: string;
6
+ /** `state` anti-CSRF à stocker en session (RFC 9700). */
7
+ readonly state: string;
8
+ /** `code_verifier` PKCE à stocker en session, ou `null` (fournisseur sans PKCE). */
9
+ readonly codeVerifier: string | null;
10
+ }
11
+ /**
12
+ * **Social login OAuth 2.0** (P6 J9) — orchestrateur du flux *Authorization Code*
13
+ * au-dessus d'`arctic`.
14
+ *
15
+ * Posture OAuth 2.1 (RFC 9700) : Authorization Code uniquement (jamais implicit /
16
+ * ROPC), **PKCE S256** quand le fournisseur le supporte (RFC 7636), **state**
17
+ * anti-CSRF, **iss** anti-mix-up (RFC 9207) ; aucun jeton n'atteint le navigateur
18
+ * (le login produit une **session BFF**, gérée hors de ce service par le
19
+ * controller + `AuthFlow`).
20
+ *
21
+ * `arctic` est **importé paresseusement** au premier login (cold path — jamais au
22
+ * boot ni par requête), comme `@simplewebauthn`/`jose`. Au boot (si
23
+ * `oauth2.enabled`) : seule la config est validée et les fournisseurs configurés
24
+ * sont confrontés au registre (un nom inconnu = WARNING, pas fatal).
25
+ *
26
+ * Le service ne touche **ni HTTP ni session** : il rend à l'appelant les éléments
27
+ * (URL, state, verifier) que le controller persiste en session — testable sans
28
+ * transport, comme `AuthFlow`.
29
+ */
30
+ declare class OAuth2Service extends Service {
31
+ #private;
32
+ module: Module;
33
+ constructor(module: Module);
34
+ /** `true` si le social login est opérationnel (activé + boot OK). */
35
+ isEnabled(): boolean;
36
+ /** Noms des fournisseurs configurés ET connus du registre (UI : boutons à afficher). */
37
+ listProviders(): string[];
38
+ /**
39
+ * Redirections post-login (succès / échec) — lues par le controller.
40
+ * Surcharge PAR FOURNISSEUR si fournie, sinon valeur globale, sinon défaut.
41
+ */
42
+ getRedirects(provider?: string): {
43
+ success: string;
44
+ failure: string;
45
+ };
46
+ /**
47
+ * Étape 1 — prépare l'URL d'autorisation + les éléments anti-replay à stocker
48
+ * en session (`state`, et `code_verifier` si PKCE).
49
+ *
50
+ * @throws AuthenticationError — fournisseur non configuré / inconnu du registre.
51
+ */
52
+ createAuthorization(provider: string): Promise<IOAuthAuthorization>;
53
+ /**
54
+ * Étape 2 — valide la réponse, échange le `code`, lit le profil et provisionne
55
+ * l'utilisateur local (Shadow User). Retourne l'identifiant à ouvrir en session.
56
+ *
57
+ * @param returnedIss - paramètre `iss` reçu (anti-mix-up RFC 9207), ou `null`.
58
+ * @throws AuthenticationError — `iss` invalide, échange refusé, ou provisioning
59
+ * impossible (lien inconnu + signup interdit).
60
+ */
61
+ exchangeAndProvision(provider: string, code: string, codeVerifier: string | null, returnedIss: string | null): Promise<{
62
+ identifier: string;
63
+ }>;
64
+ }
65
+ export default OAuth2Service;
66
+ export { OAuth2Service };
@@ -0,0 +1,66 @@
1
+ import { type CspFragment } from "../src/csp.js";
2
+ /**
3
+ * Sous-ensemble APPLICATIF de la config `headers` (cf defineSecurityConfig).
4
+ *
5
+ * `hsts` / `frameguard` / `noSniff` n'y figurent PAS volontairement : ces trois
6
+ * en-têtes « transport » sont posés par `@nodefony/http` à l'entrée brute
7
+ * (`onHttpRequest`, AVANT le pipeline) pour couvrir AUSSI les fichiers statiques,
8
+ * les erreurs précoces et un serveur sans module security (secure-by-default).
9
+ * `@nodefony/security` ne les ré-émet pas → une seule source par en-tête.
10
+ */
11
+ export interface ISecurityHeadersOptions {
12
+ enabled: boolean;
13
+ csp: string;
14
+ cspNonces: boolean;
15
+ referrerPolicy: string;
16
+ coop?: string;
17
+ coep?: string;
18
+ corp?: string;
19
+ originAgentCluster?: boolean;
20
+ permissionsPolicy?: string;
21
+ }
22
+ /**
23
+ * En-têtes de sécurité **applicatifs** de Nodefony (couche `@nodefony/security`,
24
+ * complémentaire du socle transport de `@nodefony/http`). Pré-calcule une fois au
25
+ * boot la table d'en-têtes CONSTANTS (CSP statique, Referrer-Policy, isolation
26
+ * cross-origin COOP/COEP/CORP, Origin-Agent-Cluster, Permissions-Policy) → zéro
27
+ * alloc, zéro concat par requête (le firewall la pose telle quelle).
28
+ *
29
+ * **Étape A+B** : en-têtes constants pré-calculés au boot (Referrer/COOP/…). Pour le
30
+ * CSP, deux régimes mutuellement exclusifs : **statique** (posé tel quel via `headers`,
31
+ * 0 alloc/req) OU **nonce par requête** (`cspNonces` + placeholder `{{nonce}}` dans le
32
+ * CSP) → segments pré-split au boot, recomposés par requête via `cspFor(nonce)` (1
33
+ * `join`, aucun parse/regex dans le hot-path). Le nonce lui-même vit sur `Context`.
34
+ *
35
+ * @see Fetch Metadata · W3C CSP Level 3 · WHATWG (COOP/COEP/CORP).
36
+ */
37
+ export declare class SecurityHeaders {
38
+ #private;
39
+ constructor(o: ISecurityHeadersOptions);
40
+ /** Table d'en-têtes applicatifs CONSTANTS (figée), posée telle quelle par le firewall. */
41
+ get headers(): Readonly<Record<string, string>>;
42
+ /** `true` si le CSP est recomposé par requête avec un nonce (→ `cspFor`). */
43
+ get hasNonce(): boolean;
44
+ /**
45
+ * Recompose le CSP en injectant le `nonce` de la requête aux emplacements
46
+ * `{{nonce}}`. 1 `join` (segments pré-split au boot). Le nonce est base64 (jamais
47
+ * `'`/`;`/espace) → aucune évasion possible du token CSP.
48
+ *
49
+ * @param nonce - nonce base64 de la requête (`Context.cspNonce`).
50
+ * @returns la valeur `Content-Security-Policy` à poser sur la réponse.
51
+ */
52
+ cspFor(nonce: string): string;
53
+ /**
54
+ * Recompose le CSP en fusionnant ADDITIVEMENT les directives `extra` d'une
55
+ * route (`@Csp`) dans le CSP de base, PUIS en substituant le `nonce`. Le merge
56
+ * (parse/serialize) n'est payé QUE sur les routes décorées `@Csp` (rares) — le
57
+ * cas courant reste {@link cspFor} (1 `join`). Une directive déjà présente voit
58
+ * ses sources complétées (jamais dupliquée — W3C CSP3 §3).
59
+ *
60
+ * @param nonce - nonce base64 de la requête (ignoré si le CSP n'a pas de nonce).
61
+ * @param extra - directives additionnelles de la route (`directive → sources`).
62
+ * @returns la valeur `Content-Security-Policy` à poser sur la réponse.
63
+ */
64
+ cspForExtra(nonce: string, extra: CspFragment): string;
65
+ }
66
+ export default SecurityHeaders;
@@ -0,0 +1,103 @@
1
+ import { Service, Module } from "nodefony";
2
+ import type * as Jose from "jose";
3
+ import type { IUser } from "@nodefony/user";
4
+ /**
5
+ * Réponse d'émission de jetons — nommage RFC 6749 §5.1 (snake_case, JSON).
6
+ * Le JWT part en `Authorization: Bearer`, JAMAIS en cookie ni en URL.
7
+ */
8
+ export interface ITokenResponse {
9
+ access_token: string;
10
+ refresh_token: string;
11
+ token_type: "Bearer";
12
+ /** Durée de vie de l'access token (s). */
13
+ expires_in: number;
14
+ /** Scopes accordés (séparés par des espaces). */
15
+ scope: string;
16
+ }
17
+ /**
18
+ * Orchestrateur des jetons longue durée (P6 J4) — émission/refresh des JWT +
19
+ * **maintenance du store** (le seam `ITokenStore.gc()` n'a pas d'autre appelant).
20
+ *
21
+ * Au boot (si `jwt.enabled`) : résout le store pluggable (`tokenStore.store`),
22
+ * crée le keystore Ed25519, les pose au container (`tokenStore`/`jwtKeystore`,
23
+ * consommés par le `JwtAuthenticator` et les endpoints framework), puis arme un
24
+ * **timer de gc** `unref` (n'empêche pas l'arrêt) avec **jitter** de phase
25
+ * (étale les balayages entre process d'un cluster sur un store partagé). À
26
+ * l'arrêt (`onTerminate`) : `clearInterval`/`clearTimeout`.
27
+ *
28
+ * Émission = « password grant » M2M/CLI : credential vérifié par le service
29
+ * `users` → access (JWT signé, 15 min) + refresh (secret opaque haute entropie,
30
+ * stocké **haché**). Refresh = rotation + détection de rejeu (RFC 9700 §4.14).
31
+ */
32
+ declare class TokenService extends Service {
33
+ #private;
34
+ module: Module;
35
+ constructor(module: Module);
36
+ /** `true` si l'émission JWT (signature + refresh) est opérationnelle. */
37
+ isEnabled(): boolean;
38
+ /**
39
+ * Émetteur sous lequel cette application accepte d'être DÉCOUVERTE, ou `null`.
40
+ *
41
+ * C'est la question que pose `@nodefony/framework` au moment de monter (ou
42
+ * non) `/.well-known/oauth-authorization-server` et `/.well-known/jwks.json` :
43
+ * il ne lit pas la configuration de sécurité, il obtient une réponse. `null`
44
+ * = aucune route, donc `404` — pas de document creux, pas de demi-mesure.
45
+ *
46
+ * @returns l'émetteur canonique publiable, ou `null` si rien ne doit l'être
47
+ */
48
+ publishedIssuer(): string | null;
49
+ /**
50
+ * Jeu de clés **publiques** de signature, tel qu'il doit être servi.
51
+ *
52
+ * Ne contient que des paramètres publics (RFC 8037/7517) — le keystore ne
53
+ * sérialise jamais `d`. Rien n'est calculé ici : la route est une porte, la
54
+ * matière vient du keystore.
55
+ *
56
+ * @returns le JWKS public
57
+ * @throws Error si la capacité JWT n'est pas active (garde de programmation :
58
+ * les routes ne sont montées que si {@link publishedIssuer} répond)
59
+ */
60
+ getPublicJWKS(): Promise<Jose.JSONWebKeySet>;
61
+ /**
62
+ * Une passe de purge du store (`ITokenStore.gc()`) — point d'entrée public d'un
63
+ * ordonnanceur : le {@link GcScheduler} l'appelle, mais le futur worker cron
64
+ * (`security:token-gc` / k8s CronJob) peut l'appeler à sa place (poser alors
65
+ * `tokenStore.gcIntervalS: 0`). L'anti-empilement et la capture d'erreur vivent
66
+ * dans le GcScheduler (via `onError`) — ici, la passe métier nue.
67
+ *
68
+ * ⚠️ Un store **local** (`memory`/`file`) est par-process (mémoires disjointes) :
69
+ * seul SON process peut le purger → le timer in-process reste indispensable. Un
70
+ * store **partagé** (ORM) peut être délégué au worker cron (un seul balayage).
71
+ *
72
+ * @returns nombre d'entrées purgées.
73
+ */
74
+ runGc(): Promise<number>;
75
+ /**
76
+ * Émet un couple access/refresh après vérification d'un credential
77
+ * identifiant/mot de passe (grant M2M/CLI). Throttling NIST partagé si activé.
78
+ *
79
+ * @throws ThrottledError (429) — backoff actif.
80
+ * @throws AuthenticationError (401, message uniforme) — credential invalide.
81
+ */
82
+ issueForCredentials(identifier: unknown, password: unknown, requestedScopes?: string[], resource?: unknown): Promise<ITokenResponse>;
83
+ issueTokens(user: IUser, requestedScopes?: string[], resource?: unknown, accessTtlS?: number): Promise<ITokenResponse>;
84
+ /**
85
+ * Rotation d'un refresh token (RFC 9700 §4.14) : valide le refresh présenté,
86
+ * émet un nouveau couple, révoque l'ancien. Un refresh **déjà révoqué**
87
+ * re-présenté = rejeu → toute la famille est coupée.
88
+ *
89
+ * @param rawRefresh - le refresh token présenté, en clair
90
+ * @param resource - ressource visée (RFC 8707 §2.2). Sur un `refresh_token`,
91
+ * la politique « may limit the acceptable resources to those that
92
+ * were originally granted […] or a subset thereof » : un jeton ne
93
+ * portant qu'une seule audience, le seul sous-ensemble possible est
94
+ * elle-même. Demander autre chose est donc refusé, jamais ignoré —
95
+ * sinon la rotation devient le chemin par lequel on obtient une
96
+ * audience qu'on n'a pas su demander à l'émission.
97
+ * @throws AuthenticationError (401) — refresh inconnu/expiré/révoqué, sujet banni.
98
+ * @throws InvalidTargetError (400) — `resource` demandée ≠ celle accordée
99
+ */
100
+ refresh(rawRefresh: unknown, resource?: unknown): Promise<ITokenResponse>;
101
+ }
102
+ export default TokenService;
103
+ export { TokenService };
@@ -0,0 +1,58 @@
1
+ import { Service, Module } from "nodefony";
2
+ import type { IPage } from "nodefony";
3
+ import type { ITotpEnrollmentSummary, ITotpListQuery } from "../contracts/ITotpSecretStore.js";
4
+ import { type ITotpEnrollment, type ITotpActivation, type ITotpStatus, type ITotpLoginResult } from "../src/totp/totpOperations.js";
5
+ /**
6
+ * **2FA TOTP** (P6.17, RFC 6238) — service d'orchestration du second facteur.
7
+ *
8
+ * Coquille fine : au boot (si `totp.enabled`) il résout le **store** de secrets
9
+ * pluggable + la **clé de chiffrement** AES-256-GCM, puis délègue toute la logique
10
+ * aux opérations pures `totpOperations` (testées sans serveur). Le TOTP est un
11
+ * facteur de **login step-up** (le code n'est présenté qu'à la connexion, calque
12
+ * WebAuthn/OAuth), pas un authenticator du firewall — `session.user` n'est posé
13
+ * qu'une fois le second facteur validé (Zero Trust 401 protège tout le reste).
14
+ *
15
+ * **Clé de chiffrement** : le secret TOTP est réversible (le serveur le relit pour
16
+ * calculer le code) → chiffré, jamais haché. La clé vient de `totp.encryptionKey`
17
+ * (dérivée HKDF). Absente : en dev une clé **éphémère** est générée + WARNING (les
18
+ * secrets ne survivent pas au redémarrage) ; en **production** c'est fatal — 2FA
19
+ * désactivé (une clé éphémère rendrait les secrets illisibles après redémarrage /
20
+ * sur les autres pods). Politique calquée sur RedisIdempotencyStore.
21
+ */
22
+ declare class TotpService extends Service {
23
+ #private;
24
+ module: Module;
25
+ constructor(module: Module);
26
+ /** `true` si le 2FA est opérationnel (activé en config, boot OK). */
27
+ isEnabled(): boolean;
28
+ /** Démarre l'enrôlement (secret + QR affichés 1×). */
29
+ beginEnrollment(userId: string, account: string): Promise<ITotpEnrollment>;
30
+ /** Confirme l'enrôlement par un 1ᵉʳ code → active + codes de récupération clairs. */
31
+ confirmEnrollment(userId: string, code: string): Promise<ITotpActivation>;
32
+ /** Vérifie un second facteur au login (code TOTP ou code de récupération). */
33
+ verifyLogin(userId: string, code: string): Promise<ITotpLoginResult>;
34
+ /** Désactive le 2FA d'un utilisateur. */
35
+ disable(userId: string): Promise<void>;
36
+ /** État 2FA d'un utilisateur (absent / pending / activé + codes restants). */
37
+ status(userId: string): Promise<ITotpStatus>;
38
+ /**
39
+ * Page d'enrôlements 2FA — pagination **native au store** (jamais un parcours
40
+ * complet). Vue sans secret ni condensats, garantie par le contrat du store.
41
+ *
42
+ * @param query - fenêtre + filtres ({@link ITotpListQuery}).
43
+ * @returns la page d'enrôlements.
44
+ */
45
+ listPage(query: ITotpListQuery): Promise<IPage<ITotpEnrollmentSummary>>;
46
+ /**
47
+ * Nombre d'enrôlements correspondant aux filtres — le KPI « couverture 2FA »
48
+ * sans énumérer.
49
+ *
50
+ * @param query - filtres ({@link ITotpListQuery}) ; `limit` ignoré.
51
+ * @returns le compte exact.
52
+ */
53
+ countEnrollments(query: ITotpListQuery): Promise<number>;
54
+ /** 2FA activé pour cet utilisateur ? (raccourci pour le flow de login). */
55
+ isEnabledFor(userId: string): Promise<boolean>;
56
+ }
57
+ export default TotpService;
58
+ export { TotpService };
@@ -0,0 +1,123 @@
1
+ import { Service, Module } from "nodefony";
2
+ import type { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from "@simplewebauthn/server";
3
+ import type { IPage } from "nodefony";
4
+ import type { IWebAuthnCredential } from "../contracts/IWebAuthnCredential.js";
5
+ import type { IWebAuthnCredentialSummary, IWebAuthnListQuery } from "../contracts/IWebAuthnCredentialStore.js";
6
+ /** Sujet d'une cérémonie d'enregistrement — l'utilisateur qui crée un passkey. */
7
+ export interface IWebAuthnUser {
8
+ /** Identifiant applicatif stable (sub / username) — devient le `userHandle`. */
9
+ readonly id: string;
10
+ /** Nom de compte affiché par l'OS (email, identifiant…). */
11
+ readonly name: string;
12
+ /** Nom complet optionnel (UX de l'invite système). */
13
+ readonly displayName?: string;
14
+ }
15
+ /** Résultat d'une authentification WebAuthn vérifiée. */
16
+ export interface IWebAuthnAssertionResult {
17
+ /** Le credential résolu (état déjà mis à jour : compteur, sauvegarde, usage). */
18
+ readonly credential: IWebAuthnCredential;
19
+ /** Identifiant de l'utilisateur propriétaire du credential. */
20
+ readonly userId: string;
21
+ }
22
+ /**
23
+ * **WebAuthn / passkeys** (P6 J9) — orchestrateur des deux cérémonies FIDO2
24
+ * (WebAuthn L3 §7.1 enregistrement, §7.2 authentification).
25
+ *
26
+ * MFA **phishing-resistant** : la clé privée ne quitte jamais l'authenticator
27
+ * (Touch ID, Windows Hello, clé FIDO). Le serveur ne manipule QUE des clés
28
+ * publiques + vérifie des signatures. La vérification cryptographique (parsing
29
+ * CBOR/COSE, signatures ES256/RS256/EdDSA) est déléguée à `@simplewebauthn/server`
30
+ * (lib auditée de l'écosystème), **importée paresseusement** au 1ᵉʳ usage (cold
31
+ * path — l'enregistrement/login n'est pas le hot path par requête).
32
+ *
33
+ * Au boot (si `passkeys.enabled`) : résout le RP (rpID/rpName/origines depuis la
34
+ * config, sinon le domaine de l'app) + le store de credentials pluggable
35
+ * (`webAuthnCredentialStore` du container, sinon le builtin mémoire) et le pose
36
+ * au container — une application peut donc fournir le sien avant le boot.
37
+ *
38
+ * Il n'existe **pas** d'authenticator passkey : `WebAuthnController`
39
+ * (`@nodefony/framework`) mène les deux cérémonies via ce service, puis
40
+ * `AuthFlow.establishSessionFor()` ouvre la session BFF ; les requêtes
41
+ * suivantes sont ré-authentifiées par `SessionAuthenticator`.
42
+ *
43
+ * **Anti-rejeu** : le challenge serveur est porté HORS de ce service (en session
44
+ * BFF par le controller) ; chaque `verify*` reçoit le `expectedChallenge` qu'il
45
+ * a émis — un challenge n'est jamais réutilisable.
46
+ */
47
+ declare class WebAuthnService extends Service {
48
+ #private;
49
+ module: Module;
50
+ constructor(module: Module);
51
+ /** `true` si les cérémonies sont opérationnelles (passkeys activés, boot OK). */
52
+ isEnabled(): boolean;
53
+ /**
54
+ * Prépare les options de `navigator.credentials.create()` — le défi à signer +
55
+ * les contraintes (RP, type d'attestation, sélection d'authenticator). Le
56
+ * challenge renvoyé doit être stocké côté serveur (session) par l'appelant.
57
+ *
58
+ * `excludeCredentials` liste les passkeys déjà enregistrés du même utilisateur
59
+ * pour empêcher un double enregistrement sur le même authenticator (§7.1).
60
+ */
61
+ generateRegistrationOptions(user: IWebAuthnUser): Promise<PublicKeyCredentialCreationOptionsJSON>;
62
+ /**
63
+ * Vérifie la réponse d'enregistrement (challenge, origine, rpIdHash, flags,
64
+ * format d'attestation) et **persiste** le nouveau credential.
65
+ *
66
+ * @param expectedChallenge - le challenge émis par {@link generateRegistrationOptions} (session).
67
+ * @param userId - propriétaire du credential (utilisateur authentifié/en création).
68
+ * @param requestOrigin - origine HTTP de la requête (validée si aucune origine n'est configurée).
69
+ * @throws AuthenticationError (401) — vérification échouée.
70
+ * @throws WebAuthnError (409) — plafond `passkeys.maxPerUser` atteint.
71
+ */
72
+ verifyRegistration(response: RegistrationResponseJSON, expectedChallenge: string, userId: string, requestOrigin?: string): Promise<IWebAuthnCredential>;
73
+ /**
74
+ * Prépare les options de `navigator.credentials.get()`. Sans `userId`
75
+ * (usernameless) : `allowCredentials` est omis → l'authenticator propose ses
76
+ * passkeys découvrables (UX cible). Avec `userId` : ciblage des credentials
77
+ * connus de cet utilisateur.
78
+ *
79
+ * @param userId - identité **déjà prouvée** (session en cours) et jamais un
80
+ * identifiant reçu d'un appelant non authentifié : `allowCredentials`
81
+ * révélerait alors qu'un compte porte une passkey et **lesquelles**, or un
82
+ * `credentialId` est corrélable entre sites (W3C WebAuthn L3, « Privacy leak
83
+ * via credential IDs »). Le controller BFF applique cette règle —
84
+ * `WebAuthnController.loginOptions()` ne cible que depuis la session.
85
+ */
86
+ generateAuthenticationOptions(userId?: string): Promise<PublicKeyCredentialRequestOptionsJSON>;
87
+ /**
88
+ * Vérifie une assertion (signature sur `authData ‖ SHA-256(clientDataJSON)`
89
+ * avec la clé publique stockée, §7.2) + applique l'état (compteur anti-clone,
90
+ * sauvegarde, usage). Résout l'utilisateur propriétaire via le credentialId.
91
+ *
92
+ * @param expectedChallenge - le challenge émis par {@link generateAuthenticationOptions} (session).
93
+ * @throws AuthenticationError (401) — credential inconnu ou vérification échouée.
94
+ */
95
+ verifyAuthentication(response: AuthenticationResponseJSON, expectedChallenge: string, requestOrigin?: string): Promise<IWebAuthnAssertionResult>;
96
+ /** Liste les credentials d'un utilisateur (UX « mes appareils »). */
97
+ listUserCredentials(userId: string): Promise<IWebAuthnCredential[]>;
98
+ /**
99
+ * Page de passkeys pour le data plane admin (vue TRANSVERSE : « quels appareils
100
+ * portent des passkeys sur toute la plateforme »).
101
+ *
102
+ * ≠ {@link listUserCredentials}, qui sert la fiche d'UN utilisateur et le chemin
103
+ * chaud du login. Ici on ne matérialise jamais plus d'une page, et la projection
104
+ * du store exclut la clé publique.
105
+ */
106
+ listCredentialsPage(query: IWebAuthnListQuery): Promise<IPage<IWebAuthnCredentialSummary>>;
107
+ /**
108
+ * Nombre de passkeys correspondant aux filtres, ou `-1` si le backend ne sait
109
+ * pas compter à coût raisonnable (Redis).
110
+ */
111
+ countCredentials(query: IWebAuthnListQuery): Promise<number>;
112
+ /** Révoque un credential (retrait d'un appareil). */
113
+ removeCredential(credentialId: string): Promise<void>;
114
+ /**
115
+ * Supprime un credential **du propriétaire** (self-service, anti-IDOR) : la
116
+ * suppression n'aboutit que si le credential appartient bien à `userId`, sinon
117
+ * `false` — 404 indiscernable côté client (on ne révèle pas l'existence d'un
118
+ * credential d'autrui).
119
+ */
120
+ removeUserCredential(userId: string, credentialId: string): Promise<boolean>;
121
+ }
122
+ export default WebAuthnService;
123
+ export { WebAuthnService };
@@ -0,0 +1,160 @@
1
+ import { Service, Module } from "nodefony";
2
+ import { type IWebhookCounts } from "../src/webhook/webhookFilters.js";
3
+ import { Buffer } from "node:buffer";
4
+ import type { IPage } from "nodefony";
5
+ import type { IWebhookListQuery } from "../contracts/IWebhookStore.js";
6
+ import type { IWebhookEndpoint, IWebhookDelivery, WebhookEndpointSummary, WebhookEndpointUpdate } from "../contracts/IWebhookEndpoint.js";
7
+ import { type IDeliveryResult } from "../src/webhook/webhookDelivery.js";
8
+ /** Entrée de création d'un endpoint (les champs système sont dérivés). */
9
+ export interface IWebhookRegisterInput {
10
+ /** URL de destination (validée anti-SSRF). */
11
+ readonly url: string;
12
+ /** Actions d'audit souscrites (`"*"` = toutes). */
13
+ readonly events: readonly string[];
14
+ /** Libellé humain optionnel. */
15
+ readonly description?: string | null;
16
+ /** Actif dès la création ? Défaut : `true`. */
17
+ readonly enabled?: boolean;
18
+ /** Identité de l'admin créateur (traçabilité). */
19
+ readonly createdBy?: string | null;
20
+ /** Slot multi-tenant (réservé). */
21
+ readonly tenantId?: string | null;
22
+ /** Métadonnées extensibles. */
23
+ readonly metadata?: Record<string, unknown>;
24
+ }
25
+ /** Résultat de création/rotation : le secret en clair n'est exposé qu'ici, **une fois**. */
26
+ export interface IWebhookSecretReveal {
27
+ /** Endpoint (sans secret chiffré). */
28
+ readonly endpoint: WebhookEndpointSummary;
29
+ /** Secret de signature en clair (`whsec_…`) — à communiquer au consommateur. */
30
+ readonly secret: string;
31
+ }
32
+ /** Politique de livraison lue par le dispatcher (Slice B). */
33
+ export interface IWebhookDeliveryPolicy {
34
+ readonly timestampToleranceS: number;
35
+ readonly maxRetries: number;
36
+ readonly autoDisableThreshold: number;
37
+ readonly deliveryTimeoutMs: number;
38
+ readonly maxConcurrent: number;
39
+ readonly maxQueue: number;
40
+ readonly allowHttp: boolean;
41
+ readonly denyPrivateIps: boolean;
42
+ }
43
+ /**
44
+ * **Webhooks sortants** (P6.13) — service d'orchestration du registre d'endpoints.
45
+ *
46
+ * Coquille fine : au boot (si `webhooks.enabled`) il résout le **store**
47
+ * d'endpoints pluggable + la **clé de chiffrement** des secrets de signature, puis
48
+ * expose le CRUD (register/list/update/rotate/revoke). Le secret de signature est
49
+ * **chiffré au repos** (réversible : relu pour signer chaque livraison, jamais
50
+ * haché). La livraison signée elle-même (Standard Webhooks v1) vit dans le
51
+ * dispatcher (Slice B), qui consomme {@link getSnapshot}/{@link getSigningKey}.
52
+ *
53
+ * Toute URL est validée **anti-SSRF** à l'enregistrement (et re-pinnée à la
54
+ * livraison). Politique de clé calquée sur TOTP/RedisIdempotencyStore : absente en
55
+ * dev = clé éphémère + WARNING ; en production = fatal (webhooks désactivés).
56
+ */
57
+ declare class WebhookService extends Service {
58
+ #private;
59
+ module: Module;
60
+ constructor(module: Module);
61
+ /** Le service est-il opérationnel (activé + store + clé) ? */
62
+ isReady(): boolean;
63
+ /**
64
+ * Champs de tri que le backend **actuellement branché** sait honorer, en
65
+ * vocabulaire public. Le data plane admin les passe en allowlist au traducteur
66
+ * de requête de page : hors de cette liste, un `?order=` est refusé en 400.
67
+ *
68
+ * La liste vient du store, jamais d'une constante recopiée ici : un backend
69
+ * qui ne trierait pas refuserait alors le tri **sans qu'aucune règle
70
+ * supplémentaire ne soit écrite**. Store absent (webhooks désactivés) ⇒ aucune
71
+ * capacité annoncée, donc aucun tri promis.
72
+ *
73
+ * @returns les champs triables, ou un tableau vide.
74
+ */
75
+ sortableFields(): readonly string[];
76
+ /**
77
+ * Enregistre un endpoint : valide l'URL (anti-SSRF), génère un secret de
78
+ * signature, le chiffre au repos. Retourne l'endpoint + le secret **en clair**
79
+ * (la seule occasion de le lire pour le copier).
80
+ *
81
+ * @throws SsrfError si l'URL est invalide / cible non publique.
82
+ */
83
+ register(input: IWebhookRegisterInput): Promise<IWebhookSecretReveal>;
84
+ /**
85
+ * Page d'endpoints (vue publique, sans secret) — pagination **native au
86
+ * store** : la console n'a jamais tout le registre en RAM.
87
+ *
88
+ * @param query - fenêtre + filtres ({@link IWebhookListQuery}).
89
+ * @returns la page, chaque endpoint réduit à sa vue publique.
90
+ */
91
+ listPage(query: IWebhookListQuery): Promise<IPage<WebhookEndpointSummary>>;
92
+ /**
93
+ * Nombre d'endpoints correspondant aux filtres (`COUNT` natif au store).
94
+ *
95
+ * @param query - filtres ({@link IWebhookListQuery}) ; `limit` ignoré.
96
+ * @returns le compte exact, ou `-1` si le backend ne sait pas compter.
97
+ */
98
+ countEndpoints(query: IWebhookListQuery): Promise<number>;
99
+ /**
100
+ * Les compteurs de tête de la console — posés sur la collection ENTIÈRE, pas
101
+ * sur la page affichée.
102
+ *
103
+ * Un endpoint peut être **actif ET en échec** : les facettes se recoupent, et
104
+ * aucune n'est déduite d'une autre par soustraction. Chaque compteur vaut
105
+ * `null` si le backend ne sait pas compter.
106
+ *
107
+ * @param query - filtres à appliquer avant comptage (sans fenêtre).
108
+ */
109
+ countWebhookFacets(query?: Partial<IWebhookListQuery>): Promise<IWebhookCounts>;
110
+ /** Un endpoint par id (vue publique), ou `null`. */
111
+ getEndpoint(id: string): Promise<WebhookEndpointSummary | null>;
112
+ /**
113
+ * Met à jour les champs mutables (url/events/enabled/description/metadata).
114
+ * Une nouvelle `url` est re-validée anti-SSRF. Retourne l'endpoint mis à jour,
115
+ * ou `null` si absent.
116
+ */
117
+ update(id: string, patch: Pick<WebhookEndpointUpdate, "url" | "events" | "enabled" | "description" | "metadata">): Promise<WebhookEndpointSummary | null>;
118
+ /** Active/désactive un endpoint (révocation douce = `false`). */
119
+ setEnabled(id: string, enabled: boolean): Promise<WebhookEndpointSummary | null>;
120
+ /**
121
+ * Régénère le secret de signature (rotation) et retourne le nouveau en clair.
122
+ * L'ancien cesse immédiatement d'être valide. `null` si l'endpoint est absent.
123
+ */
124
+ rotateSecret(id: string): Promise<IWebhookSecretReveal | null>;
125
+ /**
126
+ * Révèle le secret en clair d'un endpoint (réversible — usage admin, à auditer
127
+ * par l'appelant). `null` si absent.
128
+ */
129
+ revealSecret(id: string): Promise<string | null>;
130
+ /** Supprime un endpoint. Retourne `false` si absent. */
131
+ delete(id: string): Promise<boolean>;
132
+ /**
133
+ * Historique des dernières livraisons d'un endpoint (plus récentes d'abord) —
134
+ * ce que Nodefony a ENVOYÉ + la réponse observée. RAM, borné, par pod
135
+ * (observabilité éphémère, non persistée). `[]` si aucune livraison.
136
+ */
137
+ listDeliveries(id: string): IWebhookDelivery[];
138
+ /** Snapshot mémoire (sync) des endpoints — itération du dispatcher (si >0). */
139
+ getSnapshot(): IWebhookEndpoint[];
140
+ /**
141
+ * Nombre d'endpoints (0-alloc) — court-circuit hot-path du dispatcher.
142
+ *
143
+ * @remarks C'est ICI que la fraîcheur se joue, pas seulement dans
144
+ * {@link getSnapshot} : un pod démarré avant toute création de webhook a un
145
+ * cache VIDE, court-circuite sur ce zéro et n'atteindrait jamais le snapshot.
146
+ */
147
+ endpointCount(): number;
148
+ /** Déchiffre le secret de signature d'un endpoint (pour signer une livraison). */
149
+ decryptEndpointSecret(endpoint: IWebhookEndpoint): Buffer;
150
+ /** Politique de livraison (tolérance/retries/timeout…) issue de la config. */
151
+ getDeliveryPolicy(): IWebhookDeliveryPolicy;
152
+ /**
153
+ * Enregistre le résultat d'une livraison (appelé par le dispatcher) :
154
+ * lastDelivery*, compteur d'échecs consécutifs, et **auto-désactivation** de
155
+ * l'endpoint au-delà du seuil (façon GitHub). Le succès remet le compteur à 0.
156
+ */
157
+ markDelivery(id: string, result: IDeliveryResult): Promise<void>;
158
+ }
159
+ export { WebhookService };
160
+ export default WebhookService;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Résout la hiérarchie de rôles — `ROLE_ADMIN` hérite `ROLE_USER`, etc.
3
+ *
4
+ * Aplatissement DFS **précalculé au boot** (lecture O(1) au runtime, hot-path) +
5
+ * **détection de cycles au boot** (throw avec le chemin complet, pas de fail-silent).
6
+ * Niveau A de l'autorisation (P6.8).
7
+ */
8
+ export declare class RoleHierarchyWalker {
9
+ #private;
10
+ constructor(hierarchy?: Record<string, readonly string[]>);
11
+ /**
12
+ * L'utilisateur (rôles plats) possède-t-il le rôle requis, hiérarchie résolue ?
13
+ *
14
+ * @param userRoles - rôles plats de l'utilisateur.
15
+ * @param required - rôle exigé.
16
+ */
17
+ hasRole(userRoles: readonly string[], required: string): boolean;
18
+ /** Ensemble complet des rôles atteignables (plats + hérités). */
19
+ reachableRoles(userRoles: readonly string[]): Set<string>;
20
+ }
21
+ export default RoleHierarchyWalker;
@@ -0,0 +1,31 @@
1
+ import type { ContextType } from "@nodefony/http";
2
+ import type { ISecuredArea } from "../contracts/ISecuredArea.js";
3
+ import type { ISecurityAreaConfig } from "../config/defineModuleConfig.js";
4
+ /**
5
+ * Zone sécurisée concrète — pattern d'URL compilé + métadonnées d'authentification.
6
+ *
7
+ * Objet **léger** (pas un Service DI : zéro besoin d'event/log par zone, hot-path).
8
+ * Le firewall en instancie une par entrée `areas` de la config, triées par
9
+ * spécificité au boot.
10
+ */
11
+ export declare class SecuredArea implements ISecuredArea {
12
+ readonly name: string;
13
+ readonly pattern: RegExp;
14
+ readonly security: boolean;
15
+ readonly stateless: boolean;
16
+ readonly mode: "first" | "all";
17
+ readonly authenticators: readonly string[];
18
+ readonly host?: string;
19
+ readonly realtime: boolean;
20
+ readonly resource?: string;
21
+ constructor(name: string, config: ISecurityAreaConfig);
22
+ /**
23
+ * Cœur du match — pathname (+ host éventuel) déjà extraits, SANS `context`.
24
+ * Réutilisable par le verrou WebSocket (une frame n'a qu'un path) : source
25
+ * UNIQUE de la décision de zone (invariant `api.request {path}` ≤ `GET {path}`).
26
+ */
27
+ matchPath(pathname: string, host?: string): boolean;
28
+ /** La requête tombe-t-elle dans cette zone ? (host éventuel + pathname). */
29
+ match(context: ContextType): boolean;
30
+ }
31
+ export default SecuredArea;