@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,41 @@
1
+ import type { TotpAlgorithm } from "../src/totp/totpCrypto.js";
2
+ /**
3
+ * Secret TOTP d'un utilisateur (2FA) — **un seul par utilisateur** (clé = `userId`).
4
+ *
5
+ * Le secret partagé `K` est **chiffré au repos** ({@link ITotpSecret.secretEnc}) :
6
+ * le serveur doit pouvoir le **relire** pour recalculer le code à chaque login →
7
+ * réversible, donc chiffré (AES-256-GCM), **jamais** haché (≠ mot de passe / clé
8
+ * API). Les codes de récupération, eux, sont **hachés** (verify-only).
9
+ */
10
+ export interface ITotpSecret {
11
+ /** Identifiant de l'utilisateur propriétaire (clé naturelle). */
12
+ readonly userId: string;
13
+ /**
14
+ * Secret partagé `K` **chiffré** (blob opaque : `iv.tag.ciphertext` base64url).
15
+ * Produit/lu par le service détenteur de la clé — le store ne voit que des octets.
16
+ */
17
+ readonly secretEnc: string;
18
+ /** Fonction HMAC du code (RFC 6238 §1.2). */
19
+ readonly algorithm: TotpAlgorithm;
20
+ /** Nombre de chiffres du code. */
21
+ readonly digits: number;
22
+ /** Période d'un code en secondes. */
23
+ readonly period: number;
24
+ /** Condensats `sha256` des codes de récupération **non encore consommés**. */
25
+ recoveryCodes: string[];
26
+ /**
27
+ * Horodatage de **confirmation** de l'enrôlement (epoch ms), ou `null` tant que
28
+ * l'utilisateur n'a pas prouvé qu'il lit bien les codes (anti-lock-out).
29
+ */
30
+ confirmedAt: number | null;
31
+ /**
32
+ * Dernière tranche temporelle `T` ayant validé un code (RFC 6238 §5.2) — un code
33
+ * déjà consommé dans sa fenêtre **ne doit pas resservir** (anti-rejeu).
34
+ */
35
+ lastUsedStep: number | null;
36
+ /** Horodatage de création (epoch ms). */
37
+ readonly createdAt: number;
38
+ /** Horodatage du dernier usage réussi (epoch ms), ou `null`. */
39
+ lastUsedAt: number | null;
40
+ }
41
+ export default ITotpSecret;
@@ -0,0 +1,88 @@
1
+ import type { IPage, IPageQuery } from "nodefony";
2
+ import type { ITotpSecret } from "./ITotpSecret.js";
3
+ /**
4
+ * Vue d'un enrôlement 2FA pour l'INTROSPECTION admin (« qui a activé le 2FA,
5
+ * qui est resté en attente de confirmation »).
6
+ *
7
+ * **Sans secret, par construction du contrat** — ni `secretEnc` (le secret
8
+ * partagé, réversible : il permettrait de générer les codes de la victime), ni
9
+ * `recoveryCodes` (leurs condensats, matière à attaque hors ligne). La garantie
10
+ * porte sur ce qui SORT du store : quel que soit le backend, ces champs ne
11
+ * peuvent pas remonter par ce chemin, même si un appelant les demandait. Le
12
+ * NOMBRE de codes restants, lui, est exposé — c'est l'information
13
+ * d'exploitation (qui se verrouillera au prochain changement d'appareil).
14
+ */
15
+ export interface ITotpEnrollmentSummary {
16
+ /** Utilisateur propriétaire. */
17
+ readonly userId: string;
18
+ /** Fonction HMAC du code (RFC 6238 §1.2). */
19
+ readonly algorithm: string;
20
+ /** Nombre de chiffres du code. */
21
+ readonly digits: number;
22
+ /** Période d'un code en secondes. */
23
+ readonly period: number;
24
+ /** Confirmation de l'enrôlement (epoch ms), ou `null` = en attente. */
25
+ readonly confirmedAt: number | null;
26
+ /** Création de l'enrôlement (epoch ms). */
27
+ readonly createdAt: number;
28
+ /** Dernier usage réussi (epoch ms), ou `null` = jamais servi. */
29
+ readonly lastUsedAt: number | null;
30
+ /** Nombre de codes de récupération NON consommés (jamais les condensats). */
31
+ readonly recoveryCodesLeft: number;
32
+ }
33
+ /**
34
+ * Requête de listing des enrôlements 2FA — {@link IPageQuery} + les filtres qui
35
+ * ont un sens ici. `q` (hérité) = **préfixe** d'`userId` (retrouver un id
36
+ * partiel collé depuis la console).
37
+ */
38
+ export interface ITotpListQuery extends IPageQuery {
39
+ /**
40
+ * `true` = enrôlements confirmés seulement, `false` = en attente seulement
41
+ * (comptes à relancer : un secret jamais confirmé ne protège personne),
42
+ * omis = les deux.
43
+ */
44
+ confirmed?: boolean;
45
+ }
46
+ /** Champs mutables d'un secret TOTP (patch partiel). */
47
+ export interface TotpSecretUpdate {
48
+ /** Confirmation de l'enrôlement (epoch ms) — passe le secret en « actif ». */
49
+ confirmedAt?: number | null;
50
+ /** Stock résiduel de codes de récupération hachés (après consommation). */
51
+ recoveryCodes?: string[];
52
+ /** Dernière tranche `T` validée (anti-rejeu RFC 6238 §5.2). */
53
+ lastUsedStep?: number;
54
+ /** Horodatage du dernier usage réussi (epoch ms). */
55
+ lastUsedAt?: number;
56
+ }
57
+ /**
58
+ * Store **pluggable** du secret TOTP par utilisateur — découple le cœur du backend
59
+ * de persistance (mémoire / fichier / ORM / Redis). Convention-frère
60
+ * d'`IWebAuthnCredentialStore` / `ITokenStore` : le builtin `memory` est sans
61
+ * dépendance, les adapters lourds s'enregistrent depuis leur propre module.
62
+ *
63
+ * Modèle **1 secret / utilisateur** (clé = `userId`) — `save` est un **upsert**.
64
+ */
65
+ export interface ITotpSecretStore {
66
+ /** Secret TOTP de l'utilisateur, ou `null` si non enrôlé. */
67
+ findByUser(userId: string): Promise<ITotpSecret | null>;
68
+ /** Crée ou remplace le secret de l'utilisateur (upsert — ré-enrôlement). */
69
+ save(secret: ITotpSecret): Promise<void>;
70
+ /** Applique un patch partiel (confirmation, anti-rejeu, consommation de codes). */
71
+ update(userId: string, patch: TotpSecretUpdate): Promise<void>;
72
+ /** Supprime le secret de l'utilisateur (désactivation du 2FA). */
73
+ delete(userId: string): Promise<void>;
74
+ /**
75
+ * Page d'enrôlements 2FA pour le data plane admin — ne matérialise jamais
76
+ * plus d'une page, filtres appliqués au store, **secrets exclus** (cf
77
+ * {@link ITotpEnrollmentSummary}).
78
+ *
79
+ * Ordre contractuel : `createdAt` DESC, départagé par `userId` ASC.
80
+ */
81
+ listPage(query: ITotpListQuery): Promise<IPage<ITotpEnrollmentSummary>>;
82
+ /**
83
+ * Nombre d'enrôlements correspondant aux filtres (`COUNT` natif) — le KPI
84
+ * « couverture 2FA » sans énumérer.
85
+ */
86
+ countEnrollments(query: ITotpListQuery): Promise<number>;
87
+ }
88
+ export default ITotpSecretStore;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Enregistrement d'un **credential WebAuthn / passkey** côté serveur — le
3
+ * `credentialRecord` de WebAuthn L3 §7.1 (produit par la cérémonie
4
+ * d'enregistrement, relu et mis à jour à chaque authentification §7.2).
5
+ *
6
+ * Le serveur ne stocke QUE de la donnée publique : la clé privée ne quitte
7
+ * jamais l'authenticator (Touch ID, Windows Hello, clé FIDO…). Une fuite de ce
8
+ * store ne compromet aucun compte — une clé publique est inexploitable seule.
9
+ */
10
+ export interface IWebAuthnCredential {
11
+ /** Identifiant du credential (base64url) — unique, fourni par l'authenticator. */
12
+ readonly id: string;
13
+ /** Identifiant de l'utilisateur propriétaire (sub / userHandle applicatif). */
14
+ readonly userId: string;
15
+ /** Clé publique **COSE** encodée base64url — vérifie la signature des assertions. */
16
+ readonly publicKey: string;
17
+ /**
18
+ * Compteur de signatures (WebAuthn §6.1.1) — anti-clone : SHOULD croître à
19
+ * chaque authentification. `0` = authenticator sans compteur (les passkeys
20
+ * synchronisées iCloud/Google le laissent souvent à 0, ce n'est pas une erreur).
21
+ */
22
+ signCount: number;
23
+ /** Transports annoncés (`usb` | `nfc` | `ble` | `internal` | `hybrid`). */
24
+ readonly transports: readonly string[];
25
+ /**
26
+ * BE flag (Backup Eligibility, §6.1.3) — credential multi-appareils
27
+ * (synchronisable). Fixé à l'enregistrement, ne change **jamais**.
28
+ */
29
+ readonly backupEligible: boolean;
30
+ /** BS flag (Backup State) — actuellement sauvegardé. Peut évoluer dans le temps. */
31
+ backupState: boolean;
32
+ /**
33
+ * La cérémonie a-t-elle réalisé une **vérification d'utilisateur** (biométrie/
34
+ * PIN) au moins une fois (`uvInitialized`, §7.2) — base du step-up MFA.
35
+ */
36
+ uvInitialized: boolean;
37
+ /**
38
+ * Surnom optionnel de la passkey (« MacBook de Chris »).
39
+ *
40
+ * ⚠️ **Emplacement réservé — rien ne l'écrit aujourd'hui.** Le champ est porté
41
+ * par ce contrat, par les trois stores (memory, drizzle, redis) et par la vue
42
+ * admin de Studio, mais **aucune API publique ne le renseigne** : ni endpoint,
43
+ * ni setter. En pratique il vaut donc toujours `undefined`, et l'écran retombe
44
+ * sur son libellé de repli (`Passkey ···1234`).
45
+ *
46
+ * Le garder coûte zéro (il traverse déjà toute la chaîne) ; le renseigner
47
+ * demande une décision produit — un endpoint de renommage, avec la question de
48
+ * qui a le droit de renommer la passkey de qui.
49
+ */
50
+ nickname?: string;
51
+ /** Création (epoch ms). */
52
+ readonly createdAt: number;
53
+ /** Dernière authentification réussie (epoch ms), ou `null` si jamais utilisé. */
54
+ lastUsedAt: number | null;
55
+ }
56
+ export default IWebAuthnCredential;
@@ -0,0 +1,118 @@
1
+ import type { IPage, IPageQuery } from "nodefony";
2
+ import type { IWebAuthnCredential } from "./IWebAuthnCredential.js";
3
+ /**
4
+ * Vue d'une passkey pour l'INTROSPECTION admin (« quels appareils portent des
5
+ * passkeys, lesquelles meurent avec leur appareil »).
6
+ *
7
+ * **Sans `publicKey`, par construction du contrat.** La clé publique n'est pas un
8
+ * secret — c'est sa nature d'être publique — mais elle n'a aucune valeur
9
+ * d'exploitation dans une console : c'est de la matière cryptographique brute que
10
+ * personne ne lit, et une projection minimale est plus facile à garder juste. Ce
11
+ * qui S'EXPLOITE est ici : `backupState` (une passkey non sauvegardée disparaît
12
+ * avec l'appareil → l'utilisateur se verrouille dehors) et `signCount` (le
13
+ * compteur anti-clone du §6.1.1).
14
+ */
15
+ export interface IWebAuthnCredentialSummary {
16
+ /** Identifiant du credential (base64url) — la clé naturelle. */
17
+ readonly id: string;
18
+ /** Utilisateur propriétaire. */
19
+ readonly userId: string;
20
+ /** Canaux de l'authenticator (`internal`, `hybrid`, `usb`…). */
21
+ readonly transports: readonly string[];
22
+ /** Le credential PEUT être sauvegardé/synchronisé (BE flag). */
23
+ readonly backupEligible: boolean;
24
+ /** Le credential EST sauvegardé (BS flag) — sinon il meurt avec l'appareil. */
25
+ readonly backupState: boolean;
26
+ /** Une vérification utilisateur (biométrie/PIN) a déjà eu lieu. */
27
+ readonly uvInitialized: boolean;
28
+ /** Compteur de signatures (anti-clone WebAuthn §6.1.1). */
29
+ readonly signCount: number;
30
+ /** Nom donné à l'appareil par l'utilisateur, si renseigné. */
31
+ readonly nickname?: string;
32
+ /** Enrôlement (epoch ms). */
33
+ readonly createdAt: number;
34
+ /** Dernière authentification réussie (epoch ms), ou `null` = jamais servie. */
35
+ readonly lastUsedAt: number | null;
36
+ }
37
+ /**
38
+ * Requête de listing des passkeys — {@link IPageQuery} + les filtres qui ont un
39
+ * sens ici. `q` (hérité) = **préfixe** d'`userId`.
40
+ *
41
+ * ⚠️ À ne pas confondre avec {@link IWebAuthnCredentialStore.findByUser} : ce
42
+ * listing est le chemin FROID d'introspection admin ; `findByUser` est le chemin
43
+ * chaud du login, non paginé par nature.
44
+ */
45
+ export interface IWebAuthnListQuery extends IPageQuery {
46
+ /** Restreindre à un porteur (sa liste d'appareils). */
47
+ userId?: string;
48
+ /**
49
+ * `true` = passkeys sauvegardées/synchronisées seulement, `false` = celles
50
+ * liées à un seul appareil (les porteurs à risque de verrouillage), omis = les deux.
51
+ */
52
+ backedUp?: boolean;
53
+ }
54
+ /** État mis à jour après une authentification réussie (WebAuthn §7.2). */
55
+ export interface WebAuthnAuthUpdate {
56
+ /** Nouveau compteur de signatures (anti-clone). */
57
+ signCount: number;
58
+ /** Nouvel état de sauvegarde (BS flag). */
59
+ backupState: boolean;
60
+ /** UV réalisée durant cette cérémonie. */
61
+ uvInitialized: boolean;
62
+ /** Horodatage de l'usage (epoch ms). */
63
+ lastUsedAt: number;
64
+ }
65
+ /**
66
+ * Store **pluggable** des credentials WebAuthn — découple le cœur du backend de
67
+ * persistance (mémoire / ORM / Redis). Convention-frère d'`ITokenStore` :
68
+ * le builtin `memory` est sans dépendance, les adapters lourds s'enregistrent
69
+ * depuis leur propre module (inversion de dépendance).
70
+ */
71
+ export interface IWebAuthnCredentialStore {
72
+ /** Credential par son id (base64url), ou `null` — résolution à l'authentification. */
73
+ findById(credentialId: string): Promise<IWebAuthnCredential | null>;
74
+ /**
75
+ * Tous les credentials d'un utilisateur — pour `allowCredentials`
76
+ * (authentification ciblée) et l'UX « mes appareils ».
77
+ *
78
+ * **Volontairement NON paginé** : `allowCredentials` doit être COMPLET ou il
79
+ * est faux — un authenticator dont la passkey manque de la liste ne peut pas
80
+ * répondre au défi, et le protocole WebAuthn n'offre aucun « page suivante »
81
+ * (le navigateur reçoit une liste unique et choisit). Ce qui borne cet appel
82
+ * est le plafond d'enrôlement (`passkeys.maxPerUser`), pas une pagination.
83
+ */
84
+ findByUser(userId: string): Promise<IWebAuthnCredential[]>;
85
+ /**
86
+ * Nombre de credentials d'un utilisateur — **natif** par backend (`COUNT`,
87
+ * `countDocuments`, `SCARD`), jamais un `findByUser().length`.
88
+ *
89
+ * Sert le plafond d'enrôlement (`passkeys.maxPerUser`) : le chemin
90
+ * d'enregistrement ne doit pas charger N credentials pour en compter le
91
+ * nombre, et le plafond est ce qui garantit que {@link findByUser} reste borné.
92
+ */
93
+ countByUser(userId: string): Promise<number>;
94
+ /** Persiste un nouveau credential (fin de la cérémonie d'enregistrement). */
95
+ save(credential: IWebAuthnCredential): Promise<void>;
96
+ /** Met à jour l'état post-authentification (compteur, sauvegarde, UV, usage). */
97
+ update(credentialId: string, patch: WebAuthnAuthUpdate): Promise<void>;
98
+ /** Révoque un credential (l'utilisateur retire un appareil). */
99
+ delete(credentialId: string): Promise<void>;
100
+ /**
101
+ * Page de passkeys pour le data plane admin — ne matérialise jamais plus d'une
102
+ * page, filtres appliqués au store, `publicKey` exclue (cf
103
+ * {@link IWebAuthnCredentialSummary}).
104
+ *
105
+ * Ordre contractuel (backends `offset`) : `createdAt` DESC, départagé par `id`
106
+ * ASC. Les backends **curseur** (Redis, dont l'index par utilisateur est un
107
+ * Set) n'ont pas d'ordre global : ils rendent des pages de taille variable et
108
+ * le client boucle sur `nextCursor` — capacité réduite déclarée, pas un défaut.
109
+ */
110
+ listPage(query: IWebAuthnListQuery): Promise<IPage<IWebAuthnCredentialSummary>>;
111
+ /**
112
+ * Nombre de passkeys correspondant aux filtres (`COUNT` natif), ou **`-1`** si
113
+ * le backend ne sait pas compter à coût raisonnable (Redis : un total exigerait
114
+ * un `SCAN` complet O(N) sur un chemin froid — refusé).
115
+ */
116
+ countCredentials(query: IWebAuthnListQuery): Promise<number>;
117
+ }
118
+ export default IWebAuthnCredentialStore;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Endpoint webhook sortant — une **destination** que Nodefony notifie quand un
3
+ * événement souscrit survient (modèle façon GitHub/Stripe).
4
+ *
5
+ * Le `secret` de signature est **chiffré au repos** (réversible, AES-256-GCM) :
6
+ * contrairement à une clé API (hachée, vérifiée seulement), le serveur doit le
7
+ * **relire** pour signer chaque livraison (HMAC). Le clair n'est jamais stocké,
8
+ * seulement {@link secretEnc} (blob opaque).
9
+ */
10
+ export interface IWebhookEndpoint {
11
+ /** Identifiant public stable (`wh_<random>`). */
12
+ readonly id: string;
13
+ /** URL de destination (validée anti-SSRF à l'enregistrement). */
14
+ readonly url: string;
15
+ /** Secret de signature **chiffré** au repos (blob `gcm1.…`). Jamais en clair. */
16
+ readonly secretEnc: string;
17
+ /**
18
+ * Actions d'audit souscrites (ex. `"login.success"`, `"user.created"`).
19
+ * `"*"` = toutes. La livraison ne part que si l'action de l'événement matche.
20
+ */
21
+ readonly events: readonly string[];
22
+ /** Endpoint actif ? (désactivé = aucune livraison). */
23
+ readonly enabled: boolean;
24
+ /** Libellé humain optionnel (console admin). */
25
+ readonly description: string | null;
26
+ /** Slot multi-tenant (réservé P17) — `null` = global. */
27
+ readonly tenantId: string | null;
28
+ /** Identité de l'admin créateur (soft ref, traçabilité). */
29
+ readonly createdBy: string | null;
30
+ /** Création (epoch ms). */
31
+ readonly createdAt: number;
32
+ /** Dernière modification (epoch ms). */
33
+ readonly updatedAt: number;
34
+ /** Dernière tentative de livraison (epoch ms) ou `null`. */
35
+ readonly lastDeliveryAt: number | null;
36
+ /** Code HTTP de la dernière livraison, ou `null` (jamais livré / erreur réseau). */
37
+ readonly lastDeliveryStatus: number | null;
38
+ /** Message d'erreur de la dernière livraison, ou `null`. */
39
+ readonly lastDeliveryError: string | null;
40
+ /** Échecs consécutifs (auto-désactivation au-delà d'un seuil, façon GitHub). */
41
+ readonly failureCount: number;
42
+ /** Métadonnées extensibles (jamais de secret). */
43
+ readonly metadata: Record<string, unknown>;
44
+ }
45
+ /**
46
+ * Champs mutables d'un endpoint (PATCH). `id`/`createdAt`/`createdBy`/`tenantId`
47
+ * sont immuables après création.
48
+ */
49
+ export type WebhookEndpointUpdate = Partial<Pick<IWebhookEndpoint, "url" | "secretEnc" | "events" | "enabled" | "description" | "updatedAt" | "lastDeliveryAt" | "lastDeliveryStatus" | "lastDeliveryError" | "failureCount" | "metadata">>;
50
+ /**
51
+ * Vue **publique** d'un endpoint (DTO console admin) — **sans** le secret
52
+ * chiffré. Le secret en clair n'est renvoyé qu'une fois, à la création/rotation.
53
+ */
54
+ export type WebhookEndpointSummary = Omit<IWebhookEndpoint, "secretEnc">;
55
+ /**
56
+ * Trace d'une livraison (historique « récentes », façon GitHub/Stripe) — ce que
57
+ * Nodefony a **envoyé** à l'endpoint + la **réponse** observée. Stocké en RAM,
58
+ * borné, **par pod** (observabilité éphémère, non persistée). Aucun secret (le
59
+ * `webhook-signature` n'en révèle rien ; le corps signé est l'événement public).
60
+ */
61
+ export interface IWebhookDelivery {
62
+ /** Horodatage de la tentative (epoch ms). */
63
+ readonly ts: number;
64
+ /** `webhook-id` du message livré. */
65
+ readonly messageId: string;
66
+ /** Type d'événement (= action d'audit, ex. `login.failure`). */
67
+ readonly type: string;
68
+ /** Numéro de tentative (0 = 1ʳᵉ ; > 0 = retry). */
69
+ readonly attempt: number;
70
+ /** Livraison acceptée (2xx) ? */
71
+ readonly ok: boolean;
72
+ /** Code HTTP, ou `null` (réseau/timeout/SSRF). */
73
+ readonly status: number | null;
74
+ /** Message d'erreur, ou `null` si OK. */
75
+ readonly error: string | null;
76
+ /** Durée de la tentative (ms). */
77
+ readonly durationMs: number;
78
+ /** Corps JSON envoyé (enveloppe `{id,timestamp,type,data}`), tronqué. */
79
+ readonly requestBody: string;
80
+ /** Début du corps de réponse du destinataire (tronqué), ou `null`. */
81
+ readonly responseBody: string | null;
82
+ }
@@ -0,0 +1,85 @@
1
+ import type { IPage, IPageQuery, ISortableSource } from "nodefony";
2
+ import type { IWebhookEndpoint, WebhookEndpointUpdate } from "./IWebhookEndpoint.js";
3
+ /**
4
+ * Requête de **listing paginé** d'endpoints webhook (data plane admin) — le
5
+ * contrat de page standard du core ({@link IPageQuery}) enrichi des filtres
6
+ * propres aux endpoints.
7
+ *
8
+ * `q` (hérité) = sous-chaîne **insensible à la casse** cherchée dans `url` **ou**
9
+ * `description` — la question posée par un humain devant la console (« où part
10
+ * mon webhook stripe ? ») porte sur ces deux champs, jamais sur l'id.
11
+ */
12
+ export interface IWebhookListQuery extends IPageQuery {
13
+ /** `true` = actifs seulement, `false` = désactivés seulement, omis = les deux. */
14
+ enabled?: boolean;
15
+ /**
16
+ * Ne garder que les endpoints **abonnés à cet événement** (appartenance au
17
+ * tableau `events`). Répond à la question d'exploitation « qui écoute
18
+ * `user.created` ? ». Non portable au `Criteria` générique (containment dans un
19
+ * tableau JSON) → chaque backend l'implémente nativement.
20
+ */
21
+ event?: string;
22
+ /**
23
+ * `true` = seulement les endpoints **en échec** (au moins un échec consécutif
24
+ * courant, `failureCount > 0`), `false` = seulement ceux qui vont bien, omis =
25
+ * les deux.
26
+ *
27
+ * Au contrat plutôt que déduit d'un `order` : c'est la question d'exploitation
28
+ * la plus fréquente — « qu'est-ce qui casse ? » — et la carte qui l'affiche
29
+ * doit pouvoir être cliquée pour filtrer le tableau sur la même population.
30
+ * Portable partout (comparaison sur une colonne entière indexable).
31
+ */
32
+ failing?: boolean;
33
+ }
34
+ /**
35
+ * Persistance des **endpoints webhook** (configuration durable, pas un cache).
36
+ * Backend interchangeable (Memory dev/test · Drizzle SQL · Mongoose) via le
37
+ * registre {@link ../src/webhook/webhookStoreRegistry}. **Redis n'est PAS un
38
+ * store d'endpoints** (config durable ≠ éphémère) — il servira la *queue de
39
+ * livraison* cross-pod (slice cluster), pas ce contrat.
40
+ *
41
+ * Volume attendu : faible (dizaines d'endpoints), lecture fréquente par le
42
+ * dispatcher (qui en garde un snapshot mémoire), écriture rare (CRUD admin).
43
+ */
44
+ export interface IWebhookStore extends ISortableSource {
45
+ /** Insère un nouvel endpoint. */
46
+ save(endpoint: IWebhookEndpoint): Promise<void>;
47
+ /** Charge un endpoint par id, ou `null`. */
48
+ findById(id: string): Promise<IWebhookEndpoint | null>;
49
+ /** Applique un patch partiel (champs mutables) ; no-op si id absent. */
50
+ update(id: string, patch: WebhookEndpointUpdate): Promise<void>;
51
+ /** Supprime un endpoint ; no-op si id absent. */
52
+ delete(id: string): Promise<void>;
53
+ /**
54
+ * **Tous** les endpoints — réservé au **snapshot du dispatcher** (le service
55
+ * garde en mémoire la table complète des abonnements pour router un événement
56
+ * sans I/O, et la recharge au boot puis après chaque écriture CRUD).
57
+ *
58
+ * ⚠️ Énumération complète assumée **par conception** ici : le dispatcher doit
59
+ * connaître TOUS les abonnements pour ne pas rater une livraison. C'est un
60
+ * cold-path (boot + CRUD admin), jamais une requête d'affichage. Pour lister
61
+ * dans une console, utiliser {@link IWebhookStore.listPage}.
62
+ */
63
+ listAll(): Promise<IWebhookEndpoint[]>;
64
+ /**
65
+ * Liste **paginée** d'endpoints pour le data plane admin — ne matérialise
66
+ * jamais plus d'une page, filtres {@link IWebhookListQuery} appliqués **au
67
+ * store** (jamais après un chargement complet).
68
+ *
69
+ * Ordre par défaut : `createdAt` DESC (le plus récent d'abord), départagé par
70
+ * `id` ASC — sans ce tiebreaker deux endpoints créés dans la même milliseconde
71
+ * pourraient changer de page entre deux appels et l'un d'eux ne jamais
72
+ * apparaître. Un `order` explicite le remplace, dans la limite de
73
+ * {@link IWebhookStore.sortableFields} ; il s'applique **avant** le découpage
74
+ * en pages, jamais sur la tranche déjà extraite.
75
+ */
76
+ listPage(query: IWebhookListQuery): Promise<IPage<IWebhookEndpoint>>;
77
+ /**
78
+ * Nombre d'endpoints correspondant aux filtres (`COUNT` natif) — base du
79
+ * `total` d'une page et des compteurs de la console.
80
+ *
81
+ * @returns le compte exact ; `-1` si le backend ne sait pas compter à coût
82
+ * raisonnable (« je ne sais pas » explicite, jamais un total inventé).
83
+ */
84
+ countEndpoints(query: IWebhookListQuery): Promise<number>;
85
+ }
@@ -0,0 +1,9 @@
1
+ export type { IToken } from "./IToken.js";
2
+ export type { IAuthenticator } from "./IAuthenticator.js";
3
+ export type { ISecuredArea } from "./ISecuredArea.js";
4
+ export type { IFirewall } from "./IFirewall.js";
5
+ export type { IAccessVoter } from "./IAccessVoter.js";
6
+ export type { IAuthorizationService } from "./IAuthorizationService.js";
7
+ export { VoterVote } from "./IAccessVoter.js";
8
+ export type { IAccessTokenRecord, ITokenStore, ITokenListQuery, ITokenUsage, IResourcePermission, TokenRevokeReason, TokenStatus, } from "./ITokenStore.js";
9
+ export type { IJwtKeystore, IJwtSigningKey } from "./IJwtKeystore.js";
@@ -0,0 +1,10 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Accès refusé — `code = 403`. Levée par l'autorisation (un `@IsGranted` non
4
+ * satisfait, un voter DENY) : l'utilisateur EST authentifié mais n'a pas le droit.
5
+ * À distinguer d'{@link AuthenticationError} (401 = pas authentifié).
6
+ */
7
+ export declare class AccessDeniedError extends nodefonyError {
8
+ constructor(message?: string | Error);
9
+ }
10
+ export default AccessDeniedError;
@@ -0,0 +1,17 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Erreur de **gestion** d'une clé API (création/révocation) — 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
+ * - `400` — entrée invalide (nom vide, scope hors catalogue) ;
8
+ * - `409` — plafond `apiKeys.maxPerSubject` atteint ;
9
+ * - `503` — store de jetons indisponible (clés activées mais non provisionnées).
10
+ *
11
+ * Distincte de l'**authentification** d'une clé présentée (→ `AuthenticationError`
12
+ * 401, message uniforme anti-énumération).
13
+ */
14
+ export declare class ApiKeyError extends nodefonyError {
15
+ constructor(message: string, code: 400 | 409 | 503);
16
+ }
17
+ export default ApiKeyError;
@@ -0,0 +1,10 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Échec d'authentification — `code = 401`. Levée par un {@link IAuthenticator}
4
+ * quand le credential est absent/invalide, ou par le firewall en Zero Trust
5
+ * (zone protégée + visiteur anonyme + route sans `@Anonymous`).
6
+ */
7
+ export declare class AuthenticationError extends nodefonyError {
8
+ constructor(message?: string | Error);
9
+ }
10
+ export default AuthenticationError;
@@ -0,0 +1,19 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Mutation cross-site bloquée — `code = 403` (RFC 9110 §15.5.4 : le serveur a
4
+ * compris la requête mais refuse de l'honorer).
5
+ *
6
+ * Levée par {@link Csrf} sur une méthode state-changing (POST/PUT/PATCH/DELETE)
7
+ * dont la provenance est tierce : `Sec-Fetch-Site: cross-site` (défense primaire
8
+ * Fetch Metadata, W3C — infalsifiable par un script attaquant) ou, à défaut de
9
+ * Fetch Metadata, un `Origin`/`Referer` étranger aux origines de l'app (fallback).
10
+ *
11
+ * Le message reste GÉNÉRIQUE : la politique CSRF (en-têtes inspectés, whitelist)
12
+ * ne fuite jamais au client. Distincte du 401 (qui es-tu ?) et de l'AccessDenied
13
+ * (autorisé mais rôle insuffisant) : ici l'identité importe peu, c'est la
14
+ * PROVENANCE de la requête qui est rejetée.
15
+ */
16
+ export declare class CsrfError extends nodefonyError {
17
+ constructor(message?: string | Error);
18
+ }
19
+ export default CsrfError;
@@ -0,0 +1,34 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * La ressource demandée à l'émission ne peut pas être servie — `code = 400`,
4
+ * code d'erreur OAuth `invalid_target` (RFC 8707 §2).
5
+ *
6
+ * Le paramètre `resource` dit POUR QUI le jeton est demandé. Trois raisons de
7
+ * refuser, et la RFC les couvre d'un seul code : « The requested resource is
8
+ * invalid, missing, unknown, or malformed. »
9
+ *
10
+ * **Pourquoi refuser plutôt qu'ignorer.** Un `resource` accepté puis jeté rend
11
+ * un jeton parfaitement valide… pour quelqu'un d'autre. Le client croit tenir
12
+ * une clé pour la porte A, la présente, reçoit un `401`, et n'a aucun moyen de
13
+ * comprendre que sa demande n'a jamais été honorée : l'erreur se manifeste chez
14
+ * la ressource, loin de l'endroit où elle a été commise. Refuser à l'émission
15
+ * met le diagnostic là où la faute est.
16
+ *
17
+ * **Le message est constant** et ne nomme pas les audiences acceptées : les
18
+ * énumérer offrirait la carte des ressources protégées de l'application à qui
19
+ * possède un simple identifiant. La valeur refusée, elle, vient du client — la
20
+ * lui rendre ne lui apprend rien.
21
+ */
22
+ export declare class InvalidTargetError extends nodefonyError {
23
+ /** Code d'erreur OAuth à rendre au client (RFC 6749 §5.2 / RFC 8707 §2). */
24
+ readonly oauthError = "invalid_target";
25
+ /** Ce que le client a demandé, tel qu'il l'a écrit — pour le journal. */
26
+ readonly requested?: string;
27
+ /**
28
+ * @param description - raison, destinée à `error_description` (aucune fuite :
29
+ * elle qualifie la DEMANDE, jamais la configuration du serveur)
30
+ * @param requested - la valeur refusée, pour le journal
31
+ */
32
+ constructor(description: string, requested?: string);
33
+ }
34
+ export default InvalidTargetError;
@@ -0,0 +1,13 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * URL rejetée par la protection SSRF — `code = 422`. Levée quand une URL sortante
4
+ * (endpoint webhook, fetch applicatif…) est syntaxiquement valide mais cible une
5
+ * ressource **interdite** : protocole non autorisé, identifiants embarqués, hôte
6
+ * non résolvable, ou IP non publique (loopback, privée, link-local, métadonnées
7
+ * cloud `169.254.169.254`…). Sémantique alignée sur GitHub (422 à l'enregistrement
8
+ * d'un webhook invalide).
9
+ */
10
+ export declare class SsrfError extends nodefonyError {
11
+ constructor(message?: string | Error);
12
+ }
13
+ export default SsrfError;
@@ -0,0 +1,16 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Tentatives de login trop rapprochées — `code = 429` (RFC 6585 §4).
4
+ *
5
+ * Levée par `UserPasswordAuthenticator` quand le backoff progressif
6
+ * ({@link LoginThrottler}) bloque encore l'identifiant saisi. Distincte du 401 :
7
+ * un client légitime doit savoir QU'ATTENDRE (header `Retry-After`, posé par le
8
+ * firewall), pas re-soumettre en boucle. Le message reste générique — la
9
+ * politique de throttle (seuils, compteurs) n'est jamais détaillée au client.
10
+ */
11
+ export declare class ThrottledError extends nodefonyError {
12
+ /** Secondes restantes avant la prochaine tentative autorisée (header `Retry-After`). */
13
+ readonly retryAfterS: number;
14
+ constructor(retryAfterS: number);
15
+ }
16
+ export default ThrottledError;
@@ -0,0 +1,37 @@
1
+ import { nodefonyError } from "nodefony";
2
+ /**
3
+ * Le jeton n'a pas pu être VÉRIFIÉ — `code = 503`, et surtout **pas 401**.
4
+ *
5
+ * Levée quand ce qui sait valider un jeton est absent ou en panne : aucun
6
+ * vérificateur posé au conteneur, émetteur injoignable, jeu de clés
7
+ * inutilisable. Le jeton n'est alors ni valide ni invalide — on n'en sait
8
+ * rien, et c'est une information différente.
9
+ *
10
+ * **Pourquoi une erreur distincte.** Répondre 401 à une panne envoie le client
11
+ * chercher un autre jeton, qui échouera pareil : la boucle de renouvellement
12
+ * remplace la panne par une tempête de requêtes, pendant que le tableau de bord
13
+ * affiche une hausse d'« échecs d'authentification » qui ne désigne aucun
14
+ * coupable. Le 503 dit la vérité — le service ne peut pas répondre — et le
15
+ * client légitime attend au lieu d'insister.
16
+ *
17
+ * C'est la même distinction que celle tenue par le vérificateur lui-même, où
18
+ * un refus rend `null` et une panne lève : la porte doit la conserver jusqu'à
19
+ * la réponse, sinon elle est perdue là où elle sert.
20
+ *
21
+ * Le message est **constant**, et c'est structurel : il est rendu au client. La
22
+ * cause technique — nom de l'émetteur défaillant, URL du jeu de clés, erreur
23
+ * réseau — vit dans {@link detail}, que seul le journal lit. Composer la cause
24
+ * dans le message revient à publier la topologie interne de l'authentification
25
+ * à qui présente un jeton quelconque, et le rendu d'erreur de développement y
26
+ * ajoute la pile d'appels par-dessus.
27
+ */
28
+ export declare class UnverifiableTokenError extends nodefonyError {
29
+ /** Cause technique, destinée au JOURNAL — jamais au client. */
30
+ readonly detail?: string;
31
+ /**
32
+ * @param detail - cause technique pour le journal ; n'apparaît jamais dans le
33
+ * message rendu au client
34
+ */
35
+ constructor(detail?: string);
36
+ }
37
+ export default UnverifiableTokenError;