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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/index.js +48 -16
  3. package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +62 -24
  4. package/dist/nodefony/command/security-oauth-doctor.js +68 -0
  5. package/dist/nodefony/command/security-secrets.js +52 -29
  6. package/dist/nodefony/command/security-user-add.js +9 -3
  7. package/dist/nodefony/command/security-user-delete.js +10 -7
  8. package/dist/nodefony/command/security-user-list.js +5 -3
  9. package/dist/nodefony/command/security-user-password.js +9 -14
  10. package/dist/nodefony/config/config.js +99 -8
  11. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  12. package/dist/nodefony/contracts/IAccessVoter.js +1 -2
  13. package/dist/nodefony/contracts/index.js +1 -2
  14. package/dist/nodefony/errors/AccessDeniedError.js +1 -2
  15. package/dist/nodefony/errors/ApiKeyError.js +3 -2
  16. package/dist/nodefony/errors/AuthenticationError.js +1 -2
  17. package/dist/nodefony/errors/CsrfError.js +1 -2
  18. package/dist/nodefony/errors/InsecureTransportError.js +23 -0
  19. package/dist/nodefony/errors/InvalidTargetError.js +1 -2
  20. package/dist/nodefony/errors/SsrfError.js +1 -2
  21. package/dist/nodefony/errors/ThrottledError.js +1 -2
  22. package/dist/nodefony/errors/UnverifiableTokenError.js +1 -2
  23. package/dist/nodefony/errors/WebAuthnError.js +1 -2
  24. package/dist/nodefony/errors/index.js +2 -1
  25. package/dist/nodefony/keycloak/keycloakAdmin.js +254 -0
  26. package/dist/nodefony/keycloak/keycloakRealm.js +259 -0
  27. package/dist/nodefony/keycloak/keycloakRealmInput.js +123 -0
  28. package/dist/nodefony/keycloak/scaffold.js +231 -0
  29. package/dist/nodefony/keycloak/security-keycloak-realm.js +130 -0
  30. package/dist/nodefony/scaffold/contribute.js +19 -0
  31. package/dist/nodefony/service/accessTokenVerifier.js +101 -34
  32. package/dist/nodefony/service/apiKeys.js +45 -5
  33. package/dist/nodefony/service/auditService.js +4 -5
  34. package/dist/nodefony/service/authFlow.js +118 -16
  35. package/dist/nodefony/service/authorization.js +1 -2
  36. package/dist/nodefony/service/cors.js +1 -2
  37. package/dist/nodefony/service/csrf.js +1 -2
  38. package/dist/nodefony/service/firewall.js +116 -25
  39. package/dist/nodefony/service/keycloak.js +208 -0
  40. package/dist/nodefony/service/oauth2.js +403 -28
  41. package/dist/nodefony/service/securityHeaders.js +1 -2
  42. package/dist/nodefony/service/tokenService.js +94 -27
  43. package/dist/nodefony/service/totp.js +55 -11
  44. package/dist/nodefony/service/webAuthn.js +24 -7
  45. package/dist/nodefony/service/webhooks.js +4 -5
  46. package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
  47. package/dist/nodefony/src/SecuredArea.js +15 -5
  48. package/dist/nodefony/src/accountBinding.js +96 -0
  49. package/dist/nodefony/src/admin/SecurityAdminApi.js +71 -42
  50. package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
  51. package/dist/nodefony/src/admin/adminAudit.js +1 -3
  52. package/dist/nodefony/src/admin/userRevocationCascade.js +148 -20
  53. package/dist/nodefony/src/apikey/apiKeyFormat.js +1 -2
  54. package/dist/nodefony/src/audit/MemoryAuditStore.js +1 -2
  55. package/dist/nodefony/src/audit/auditBridge.js +1 -2
  56. package/dist/nodefony/src/audit/auditFilters.js +1 -2
  57. package/dist/nodefony/src/audit/auditStoreRegistry.js +1 -2
  58. package/dist/nodefony/src/audit/readAuditContext.js +1 -2
  59. package/dist/nodefony/src/audit/recordAudit.js +1 -2
  60. package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +1 -2
  61. package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +3 -2
  62. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +14 -59
  63. package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
  64. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +80 -19
  65. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +92 -16
  66. package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
  67. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +14 -5
  68. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +5 -12
  69. package/dist/nodefony/src/authenticator/bearer.js +1 -2
  70. package/dist/nodefony/src/authenticator/externalSubject.js +1 -2
  71. package/dist/nodefony/src/authenticator/peekIssuer.js +1 -2
  72. package/dist/nodefony/src/boot/publicRoutesNotice.js +26 -0
  73. package/dist/nodefony/src/crypto/secretCipher.js +1 -2
  74. package/dist/nodefony/src/csp.js +1 -2
  75. package/dist/nodefony/src/csrfToken.js +1 -2
  76. package/dist/nodefony/src/entryPoint.js +38 -0
  77. package/dist/nodefony/src/net/ssrfGuard.js +1 -2
  78. package/dist/nodefony/src/net/transportFailure.js +57 -0
  79. package/dist/nodefony/src/oauth/clientCredentials.js +39 -0
  80. package/dist/nodefony/src/oauth/httpJson.js +3 -3
  81. package/dist/nodefony/src/oauth/metadata.js +28 -12
  82. package/dist/nodefony/src/oauth/oauth2Client.js +61 -35
  83. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +40 -8
  84. package/dist/nodefony/src/oauth/providerDiagnosis.js +282 -0
  85. package/dist/nodefony/src/oauth/providerRoles.js +78 -0
  86. package/dist/nodefony/src/oauth/providers/github.js +48 -12
  87. package/dist/nodefony/src/oauth/providers/oidc.js +90 -8
  88. package/dist/nodefony/src/openToSignup.js +46 -0
  89. package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
  90. package/dist/nodefony/src/realtime/frameAuthorizer.js +1 -2
  91. package/dist/nodefony/src/rolesWithin.js +57 -0
  92. package/dist/nodefony/src/sessionAuthentication.js +25 -0
  93. package/dist/nodefony/src/sessionIdentity.js +29 -3
  94. package/dist/nodefony/src/throttle/LoginThrottler.js +1 -2
  95. package/dist/nodefony/src/token/AnonymousToken.js +1 -2
  96. package/dist/nodefony/src/token/JwtKeystore.js +89 -48
  97. package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
  98. package/dist/nodefony/src/token/RemoteJwtVerifier.js +46 -16
  99. package/dist/nodefony/src/token/UserToken.js +1 -2
  100. package/dist/nodefony/src/token/jwtRuntime.js +1 -2
  101. package/dist/nodefony/src/token/scopeGrants.js +109 -0
  102. package/dist/nodefony/src/token/secretFile.js +60 -4
  103. package/dist/nodefony/src/token/tokenCriteria.js +1 -2
  104. package/dist/nodefony/src/token/tokenFilters.js +1 -2
  105. package/dist/nodefony/src/token/tokenSort.js +1 -2
  106. package/dist/nodefony/src/token/tokenStatus.js +1 -2
  107. package/dist/nodefony/src/token/tokenStoreRegistry.js +1 -2
  108. package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +1 -2
  109. package/dist/nodefony/src/totp/totpCipher.js +1 -2
  110. package/dist/nodefony/src/totp/totpCrypto.js +1 -2
  111. package/dist/nodefony/src/totp/totpOperations.js +1 -2
  112. package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +1 -2
  113. package/dist/nodefony/src/transport/CredentialTransportPolicy.js +50 -0
  114. package/dist/nodefony/src/voter/RoleVoter.js +1 -2
  115. package/dist/nodefony/src/voter/ScopeVoter.js +1 -2
  116. package/dist/nodefony/src/voter/voterRegistry.js +1 -2
  117. package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +1 -2
  118. package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +1 -2
  119. package/dist/nodefony/src/webhook/MemoryWebhookStore.js +1 -2
  120. package/dist/nodefony/src/webhook/WebhookDispatcher.js +1 -2
  121. package/dist/nodefony/src/webhook/webhookCipher.js +1 -2
  122. package/dist/nodefony/src/webhook/webhookDelivery.js +1 -2
  123. package/dist/nodefony/src/webhook/webhookFilters.js +1 -2
  124. package/dist/nodefony/src/webhook/webhookSignature.js +1 -2
  125. package/dist/nodefony/src/webhook/webhookSort.js +1 -2
  126. package/dist/nodefony/src/webhook/webhookStoreRegistry.js +1 -2
  127. package/dist/types/index.d.ts +26 -6
  128. package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +16 -6
  129. package/dist/types/nodefony/command/security-oauth-doctor.d.ts +23 -0
  130. package/dist/types/nodefony/command/security-secrets.d.ts +2 -0
  131. package/dist/types/nodefony/command/security-user-delete.d.ts +4 -3
  132. package/dist/types/nodefony/command/security-user-password.d.ts +4 -4
  133. package/dist/types/nodefony/config/config.d.ts +64 -1
  134. package/dist/types/nodefony/contracts/IAuthenticator.d.ts +1 -1
  135. package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +42 -1
  136. package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +48 -0
  137. package/dist/types/nodefony/contracts/ISecuredArea.d.ts +22 -0
  138. package/dist/types/nodefony/contracts/ITotpSecret.d.ts +4 -1
  139. package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +7 -1
  140. package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +4 -1
  141. package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +6 -0
  142. package/dist/types/nodefony/errors/ApiKeyError.d.ts +3 -1
  143. package/dist/types/nodefony/errors/InsecureTransportError.d.ts +20 -0
  144. package/dist/types/nodefony/errors/index.d.ts +1 -0
  145. package/dist/types/nodefony/keycloak/keycloakAdmin.d.ts +154 -0
  146. package/dist/types/nodefony/keycloak/keycloakRealm.d.ts +138 -0
  147. package/dist/types/nodefony/keycloak/keycloakRealmInput.d.ts +92 -0
  148. package/dist/types/nodefony/keycloak/scaffold.d.ts +98 -0
  149. package/dist/types/nodefony/keycloak/security-keycloak-realm.d.ts +43 -0
  150. package/dist/types/nodefony/scaffold/contribute.d.ts +15 -0
  151. package/dist/types/nodefony/service/accessTokenVerifier.d.ts +35 -14
  152. package/dist/types/nodefony/service/apiKeys.d.ts +13 -2
  153. package/dist/types/nodefony/service/authFlow.d.ts +35 -3
  154. package/dist/types/nodefony/service/firewall.d.ts +17 -3
  155. package/dist/types/nodefony/service/keycloak.d.ts +115 -0
  156. package/dist/types/nodefony/service/oauth2.d.ts +145 -9
  157. package/dist/types/nodefony/service/tokenService.d.ts +7 -1
  158. package/dist/types/nodefony/service/totp.d.ts +28 -4
  159. package/dist/types/nodefony/service/webAuthn.d.ts +23 -1
  160. package/dist/types/nodefony/src/SecuredArea.d.ts +4 -0
  161. package/dist/types/nodefony/src/accountBinding.d.ts +82 -0
  162. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +72 -12
  163. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -26
  164. package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
  165. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +17 -1
  166. package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
  167. package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +11 -3
  168. package/dist/types/nodefony/src/boot/publicRoutesNotice.d.ts +16 -0
  169. package/dist/types/nodefony/src/entryPoint.d.ts +37 -0
  170. package/dist/types/nodefony/src/net/transportFailure.d.ts +22 -0
  171. package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
  172. package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
  173. package/dist/types/nodefony/src/oauth/metadata.d.ts +24 -1
  174. package/dist/types/nodefony/src/oauth/oauth2Client.d.ts +50 -1
  175. package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +46 -2
  176. package/dist/types/nodefony/src/oauth/providerDiagnosis.d.ts +117 -0
  177. package/dist/types/nodefony/src/oauth/providerRoles.d.ts +58 -0
  178. package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +34 -1
  179. package/dist/types/nodefony/src/openToSignup.d.ts +36 -0
  180. package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +7 -1
  181. package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +6 -0
  182. package/dist/types/nodefony/src/rolesWithin.d.ts +27 -0
  183. package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
  184. package/dist/types/nodefony/src/sessionIdentity.d.ts +22 -0
  185. package/dist/types/nodefony/src/token/JwtKeystore.d.ts +47 -5
  186. package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +29 -2
  187. package/dist/types/nodefony/src/token/scopeGrants.d.ts +77 -0
  188. package/dist/types/nodefony/src/token/secretFile.d.ts +22 -0
  189. package/dist/types/nodefony/src/transport/CredentialTransportPolicy.d.ts +47 -0
  190. package/docs/api-keys.md +182 -175
  191. package/docs/audit.md +141 -141
  192. package/docs/authenticators.md +129 -96
  193. package/docs/authorization.md +116 -69
  194. package/docs/cors.md +64 -64
  195. package/docs/csrf.md +77 -72
  196. package/docs/external-jwt.md +83 -11
  197. package/docs/firewall.md +119 -83
  198. package/docs/headers.md +82 -82
  199. package/docs/index.md +16 -8
  200. package/docs/keycloak.md +835 -0
  201. package/docs/login-page.md +361 -0
  202. package/docs/oauth2.md +291 -166
  203. package/docs/obtenir-un-jeton.md +131 -34
  204. package/docs/tokens.md +197 -140
  205. package/docs/totp.md +177 -136
  206. package/docs/webauthn.md +157 -154
  207. package/docs/webhooks.md +128 -125
  208. package/keycloak/themes/nodefony/account/resources/css/nodefony.css +66 -0
  209. package/keycloak/themes/nodefony/account/resources/img/favicon.ico +0 -0
  210. package/keycloak/themes/nodefony/account/resources/img/logo.png +0 -0
  211. package/keycloak/themes/nodefony/account/theme.properties +14 -0
  212. package/keycloak/themes/nodefony/admin/resources/css/nodefony.css +66 -0
  213. package/keycloak/themes/nodefony/admin/resources/img/favicon.ico +0 -0
  214. package/keycloak/themes/nodefony/admin/resources/img/logo.png +0 -0
  215. package/keycloak/themes/nodefony/admin/theme.properties +12 -0
  216. package/keycloak/themes/nodefony/email/html/template.ftl +73 -0
  217. package/keycloak/themes/nodefony/email/messages/messages_en.properties +2 -0
  218. package/keycloak/themes/nodefony/email/messages/messages_fr.properties +3 -0
  219. package/keycloak/themes/nodefony/email/resources/img/logo.png +0 -0
  220. package/keycloak/themes/nodefony/email/theme.properties +8 -0
  221. package/keycloak/themes/nodefony/login/messages/messages_en.properties +22 -0
  222. package/keycloak/themes/nodefony/login/messages/messages_fr.properties +25 -0
  223. package/keycloak/themes/nodefony/login/resources/css/login.css +1141 -0
  224. package/keycloak/themes/nodefony/login/resources/css/nodefony.css +688 -0
  225. package/keycloak/themes/nodefony/login/resources/css/skins/blueprint.css +70 -0
  226. package/keycloak/themes/nodefony/login/resources/css/skins/console.css +57 -0
  227. package/keycloak/themes/nodefony/login/resources/css/skins/dots.css +67 -0
  228. package/keycloak/themes/nodefony/login/resources/css/skins/enterprise.css +84 -0
  229. package/keycloak/themes/nodefony/login/resources/css/skins/horizon.css +158 -0
  230. package/keycloak/themes/nodefony/login/resources/css/skins/ledger.css +224 -0
  231. package/keycloak/themes/nodefony/login/resources/css/skins/minimal.css +63 -0
  232. package/keycloak/themes/nodefony/login/resources/css/skins/photo-card.css +72 -0
  233. package/keycloak/themes/nodefony/login/resources/img/favicon.ico +0 -0
  234. package/keycloak/themes/nodefony/login/resources/img/logo.png +0 -0
  235. package/keycloak/themes/nodefony/login/template.ftl +423 -0
  236. package/keycloak/themes/nodefony/login/theme.properties +17 -0
  237. package/keycloak/themes/nodefony/welcome/resources/css/nodefony.css +32 -0
  238. package/keycloak/themes/nodefony/welcome/resources/img/logo.png +0 -0
  239. package/keycloak/themes/nodefony/welcome/theme.properties +11 -0
  240. package/keycloak/themes/nodefony-blueprint/login/theme.properties +12 -0
  241. package/keycloak/themes/nodefony-console/login/theme.properties +12 -0
  242. package/keycloak/themes/nodefony-dots/login/theme.properties +12 -0
  243. package/keycloak/themes/nodefony-enterprise/login/theme.properties +12 -0
  244. package/keycloak/themes/nodefony-horizon/login/theme.properties +12 -0
  245. package/keycloak/themes/nodefony-ledger/login/theme.properties +12 -0
  246. package/keycloak/themes/nodefony-minimal/login/theme.properties +12 -0
  247. package/keycloak/themes/nodefony-photo-card/login/theme.properties +12 -0
  248. package/package.json +19 -17
  249. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
@@ -0,0 +1,109 @@
1
+ import { ADMIN_SCOPE_READ, ADMIN_SCOPE_WRITE, refusedAdminScopes } from "nodefony";
2
+ // nodefony/src/token/scopeGrants.ts
3
+ const SCOPE_TOKEN = /^[\x21\x23-\x5B\x5D-\x7E]+$/u;
4
+ const WILDCARD_SUFFIX = ":*";
5
+ /**
6
+ * Clés que `grantableScopes` refuse : le plan d'administration est jugé par le
7
+ * framework, et une déclaration ici serait soit sans effet, soit un
8
+ * assouplissement — deux pièges que rien ne signalerait.
9
+ */
10
+ const RESERVED_SCOPE_GRANT_KEYS = [
11
+ ADMIN_SCOPE_READ,
12
+ ADMIN_SCOPE_WRITE,
13
+ "admin:*"
14
+ ];
15
+ /**
16
+ * Dit pourquoi une clé de `grantableScopes` est invalide, ou `null` si elle
17
+ * est acceptable.
18
+ *
19
+ * Une clé est un `scope-token` (RFC 6749 §3.3) ; un `*` n'y est admis qu'en
20
+ * dernière position, juste après `:` (une famille `préfixe:*`).
21
+ *
22
+ * @param key - la clé déclarée.
23
+ * @returns le motif du refus, ou `null`.
24
+ */
25
+ function scopeGrantKeyProblem(key) {
26
+ if (!SCOPE_TOKEN.test(key)) return "n'est pas un scope valide (RFC 6749 §3.3 : ni espace, ni guillemet, ni barre oblique inverse)";
27
+ const star = key.indexOf("*");
28
+ if (star !== -1 && (star !== key.length - 1 || !key.endsWith(WILDCARD_SUFFIX) || key.length === 2)) return "porte un `*` ailleurs qu'en fin de famille (`préfixe:*`)";
29
+ if (RESERVED_SCOPE_GRANT_KEYS.includes(key)) return "est réservé au plan d'administration du framework (les scopes `admin:read`/`admin:write` sont accordés au seul rôle ROLE_NODEFONY_ADMIN)";
30
+ return null;
31
+ }
32
+ /**
33
+ * Politique d'attribution des scopes d'application, compilée une fois au boot.
34
+ *
35
+ * Résolution : une clé EXACTE l'emporte sur une famille, et la famille la plus
36
+ * longue sur une plus courte (`shop:admin:*` > `shop:*`). Le résultat ne dépend
37
+ * donc pas de l'ordre des clés. Un scope qu'aucune clé ne couvre reste accordé
38
+ * à tout porteur (comportement historique).
39
+ */
40
+ var ScopeGrantPolicy = class {
41
+ #exact = /* @__PURE__ */ new Map();
42
+ #families = [];
43
+ /**
44
+ * @param grants - la section `grantableScopes`, déjà validée par le schéma.
45
+ */
46
+ constructor(grants = {}) {
47
+ for (const [key, roles] of Object.entries(grants)) if (key.endsWith(WILDCARD_SUFFIX)) this.#families.push([key.slice(0, -1), roles]);
48
+ else this.#exact.set(key, roles);
49
+ this.#families.sort((a, b) => b[0].length - a[0].length);
50
+ }
51
+ /** `true` si aucune clé n'est déclarée — tout scope d'application est accordé. */
52
+ get isEmpty() {
53
+ return this.#exact.size === 0 && this.#families.length === 0;
54
+ }
55
+ /**
56
+ * Les rôles dont UN suffit pour obtenir ce scope.
57
+ *
58
+ * @param scope - un scope demandé.
59
+ * @returns les rôles de la clé la plus spécifique, ou `null` si aucune clé ne
60
+ * le couvre (scope libre).
61
+ */
62
+ rolesFor(scope) {
63
+ const exact = this.#exact.get(scope);
64
+ if (exact !== void 0) return exact;
65
+ for (const [prefix, roles] of this.#families) if (scope.startsWith(prefix)) return roles;
66
+ return null;
67
+ }
68
+ };
69
+ /**
70
+ * Les scopes demandés que ce porteur ne peut pas obtenir, avec la règle qui
71
+ * refuse chacun.
72
+ *
73
+ * Seul juge des DEUX familles, appelé par chaque émetteur de l'application
74
+ * (jeton d'accès, clé API) : la règle d'administration du framework
75
+ * ({@link refusedAdminScopes}, jugée sur les rôles PLATS du compte, comme le
76
+ * plan d'administration) et la politique de l'application
77
+ * ({@link ScopeGrantPolicy}, jugée hiérarchie de rôles comprise, comme un
78
+ * `@IsGranted`).
79
+ *
80
+ * @param requested - scopes demandés (RFC 6749 §3.3).
81
+ * @param roles - rôles du porteur, tels que son compte les porte.
82
+ * @param policy - politique compilée de `grantableScopes`.
83
+ * @param hierarchy - hiérarchie de rôles ; absente, seuls les rôles plats comptent.
84
+ * @returns les refus, dans l'ordre de la demande — vide si tout est accordé.
85
+ */
86
+ function refusedScopes(requested, roles, policy, hierarchy) {
87
+ const admin = refusedAdminScopes(requested, roles);
88
+ if (admin.length === 0 && policy.isEmpty) return [];
89
+ let reachable = null;
90
+ const out = [];
91
+ for (const scope of requested) {
92
+ if (admin.includes(scope)) {
93
+ out.push({
94
+ scope,
95
+ rule: "admin"
96
+ });
97
+ continue;
98
+ }
99
+ const allowed = policy.rolesFor(scope);
100
+ if (allowed === null) continue;
101
+ reachable ??= hierarchy ? hierarchy.reachableRoles(roles) : new Set(roles);
102
+ if (!allowed.some((role) => reachable?.has(role))) out.push({
103
+ scope,
104
+ rule: "application"
105
+ });
106
+ }
107
+ return out;
108
+ }
109
+ export { RESERVED_SCOPE_GRANT_KEYS, ScopeGrantPolicy, refusedScopes, scopeGrantKeyProblem };
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
+ import { randomUUID } from "node:crypto";
4
+ import { link, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
4
5
  import process from "node:process";
5
- //#region nodefony/src/token/secretFile.ts
6
+ // nodefony/src/token/secretFile.ts
6
7
  /**
7
8
  * Écrire un SECRET sur disque — la seule implémentation du dépôt.
8
9
  *
@@ -113,6 +114,62 @@ async function writeSecret(file, content) {
113
114
  throw e;
114
115
  }
115
116
  }
117
+ /**
118
+ * Codes d'un système de fichiers qui ne sait pas poser de lien dur (FAT, exFAT,
119
+ * certains montages réseau) — et non d'une cible déjà présente.
120
+ */
121
+ const NO_HARD_LINK = /* @__PURE__ */ new Set([
122
+ "EPERM",
123
+ "ENOTSUP",
124
+ "ENOSYS",
125
+ "EOPNOTSUPP"
126
+ ]);
127
+ /**
128
+ * Crée un secret SEULEMENT s'il n'existe pas encore : 0600, atomique, et un
129
+ * seul gagnant quand plusieurs process le tentent en même temps.
130
+ *
131
+ * ⭐ **Pourquoi pas {@link writeSecret}.** `rename` REMPLACE la cible : deux
132
+ * workers de `nodefony cluster` qui génèrent chacun une clé au premier boot
133
+ * l'écrivent tous les deux, le dernier gagne sur le disque — et l'autre garde
134
+ * en mémoire une clé que plus personne ne connaît. Ici, le temporaire est
135
+ * publié par un LIEN DUR, que le système refuse (`EEXIST`) si le nom existe :
136
+ * le perdant le sait, et relit la clé du gagnant. Le lecteur ne voit jamais un
137
+ * fichier à demi écrit, puisque le lien n'apparaît qu'une fois le contenu complet.
138
+ *
139
+ * ⚠️ Un système de fichiers sans liens durs (FAT, exFAT) retombe sur une
140
+ * création exclusive directe (`wx`) : toujours un seul gagnant, mais un lecteur
141
+ * concurrent peut lire le fichier pendant son écriture — il lève alors, au lieu
142
+ * de signer avec une clé divergente.
143
+ *
144
+ * @param file - chemin du secret.
145
+ * @param content - contenu à écrire.
146
+ * @returns `true` si ce process l'a créé, `false` s'il existait déjà.
147
+ */
148
+ async function createSecretExclusive(file, content) {
149
+ await mkdir(path.dirname(file), { recursive: true });
150
+ const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
151
+ await writeFile(tmp, content, { mode: 384 });
152
+ try {
153
+ await link(tmp, file);
154
+ return true;
155
+ } catch (e) {
156
+ const code = e.code ?? "";
157
+ if (code === "EEXIST") return false;
158
+ if (!NO_HARD_LINK.has(code)) throw e;
159
+ try {
160
+ await writeFile(file, content, {
161
+ mode: 384,
162
+ flag: "wx"
163
+ });
164
+ return true;
165
+ } catch (fallback) {
166
+ if (fallback.code === "EEXIST") return false;
167
+ throw fallback;
168
+ }
169
+ } finally {
170
+ await rm(tmp, { force: true }).catch(() => {});
171
+ }
172
+ }
116
173
  /** Forme synchrone de {@link writeSecret}. */
117
174
  function writeSecretSync(file, content) {
118
175
  mkdirSync(path.dirname(file), { recursive: true });
@@ -130,5 +187,4 @@ function writeSecretSync(file, content) {
130
187
  chmodSync(file, 384);
131
188
  } catch {}
132
189
  }
133
- //#endregion
134
- export { MODE_SECRET, messageNonRestreint, modeNonRestreint, modeNonRestreintAsync, readIfPresent, readIfPresentSync, writeSecret, writeSecretSync };
190
+ export { MODE_SECRET, createSecretExclusive, messageNonRestreint, modeNonRestreint, modeNonRestreintAsync, readIfPresent, readIfPresentSync, writeSecret, writeSecretSync };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/token/tokenCriteria.ts
1
+ // nodefony/src/token/tokenCriteria.ts
2
2
  /**
3
3
  * Fragment de critère **portable** exprimant l'état de vie d'un jeton — la
4
4
  * traduction que les adapters SQL et Mongo partagent au lieu de la réécrire.
@@ -32,5 +32,4 @@ function tokenStatusCriteria(status, now) {
32
32
  default: return {};
33
33
  }
34
34
  }
35
- //#endregion
36
35
  export { tokenStatusCriteria };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/token/tokenFilters.ts
1
+ // nodefony/src/token/tokenFilters.ts
2
2
  /**
3
3
  * **Le vocabulaire de filtre des jetons**, en noms PUBLICS — ceux qu'un client
4
4
  * écrit dans l'URL (`?status=revoked`), jamais des noms de colonne.
@@ -68,5 +68,4 @@ const TOKEN_FACETS = {
68
68
  * Un test verrouille l'accord entre cette liste et {@link TOKEN_FACETS}.
69
69
  */
70
70
  const TOKEN_STATS_FILTERS = { subjectId: "string" };
71
- //#endregion
72
71
  export { TOKEN_FACETS, TOKEN_FILTERS, TOKEN_STATS_FILTERS };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/token/tokenSort.ts
1
+ // nodefony/src/token/tokenSort.ts
2
2
  /**
3
3
  * **Le vocabulaire de tri des jetons**, en noms PUBLICS — ceux qu'un client écrit
4
4
  * dans l'URL (`?order=createdAt:DESC`), jamais des noms de colonne.
@@ -36,5 +36,4 @@ const TOKEN_SORTABLE_FIELDS = [
36
36
  * ligne entre deux pages).
37
37
  */
38
38
  const TOKEN_DEFAULT_ORDER = [["createdAt", "DESC"], ["id", "DESC"]];
39
- //#endregion
40
39
  export { TOKEN_DEFAULT_ORDER, TOKEN_SORTABLE_FIELDS };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/token/tokenStatus.ts
1
+ // nodefony/src/token/tokenStatus.ts
2
2
  /**
3
3
  * **La** définition de l'état d'un jeton — un seul exemplaire, pour les backends
4
4
  * qui évaluent en mémoire (store mémoire, filtrage inline d'un batch `SCAN`).
@@ -31,5 +31,4 @@ function tokenStatusOf(token, now) {
31
31
  function matchesTokenStatus(token, status, now) {
32
32
  return status === void 0 || tokenStatusOf(token, now) === status;
33
33
  }
34
- //#endregion
35
34
  export { matchesTokenStatus, tokenStatusOf };
@@ -1,5 +1,5 @@
1
1
  import { MemoryTokenStore } from "./MemoryTokenStore.js";
2
- //#region nodefony/src/token/tokenStoreRegistry.ts
2
+ // nodefony/src/token/tokenStoreRegistry.ts
3
3
  const factories = /* @__PURE__ */ new Map();
4
4
  /**
5
5
  * Enregistre (ou remplace) la fabrique d'un store de jetons. Appelée par le
@@ -21,5 +21,4 @@ registerTokenStore("memory", (ctx) => {
21
21
  const days = ctx?.config?.tokenStore?.retentionRevokedDays;
22
22
  return typeof days === "number" ? new MemoryTokenStore(Date.now, days * 864e5) : new MemoryTokenStore();
23
23
  });
24
- //#endregion
25
24
  export { getTokenStoreFactory, listTokenStores, registerTokenStore };
@@ -1,5 +1,5 @@
1
1
  import { assertPageQuery } from "nodefony";
2
- //#region nodefony/src/totp/MemoryTotpSecretStore.ts
2
+ // nodefony/src/totp/MemoryTotpSecretStore.ts
3
3
  /**
4
4
  * Projette un secret en vue d'introspection — **le seul endroit** où l'on
5
5
  * décide ce qui sort d'un store TOTP en mémoire. `secretEnc` et les condensats
@@ -105,5 +105,4 @@ var MemoryTotpSecretStore = class {
105
105
  for (const secret of snapshot.secrets) this.#byUser.set(secret.userId, secret);
106
106
  }
107
107
  };
108
- //#endregion
109
108
  export { MemoryTotpSecretStore, MemoryTotpSecretStore as default, matchesTotpQuery, toTotpEnrollment };
@@ -1,6 +1,6 @@
1
1
  import { decryptSecret, deriveKey, encryptSecret, generateEphemeralKey } from "../crypto/secretCipher.js";
2
2
  import { Buffer } from "node:buffer";
3
- //#region nodefony/src/totp/totpCipher.ts
3
+ // nodefony/src/totp/totpCipher.ts
4
4
  /**
5
5
  * Chiffrement réversible du secret TOTP au repos — façade de domaine au-dessus
6
6
  * de la brique générique {@link ../crypto/secretCipher}.
@@ -26,5 +26,4 @@ const TOTP_DERIVATION = {
26
26
  function deriveTotpKey(material) {
27
27
  return deriveKey(material, TOTP_DERIVATION);
28
28
  }
29
- //#endregion
30
29
  export { decryptSecret, deriveTotpKey, encryptSecret, generateEphemeralKey };
@@ -1,5 +1,5 @@
1
1
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
- //#region nodefony/src/totp/totpCrypto.ts
2
+ // nodefony/src/totp/totpCrypto.ts
3
3
  /** Paramètres TOTP — défauts interopérables (Google Authenticator). */
4
4
  const TOTP_DEFAULTS = {
5
5
  /** Taille du secret en octets (RFC 4226 R6 : 160 bits recommandés). */
@@ -222,5 +222,4 @@ function matchRecoveryCode(presented, hashes) {
222
222
  }
223
223
  return found;
224
224
  }
225
- //#endregion
226
225
  export { TOTP_DEFAULTS, base32Decode, base32Encode, buildOtpauthUri, generateRecoveryCodes, generateTotpSecret, hashRecoveryCode, hotp, matchRecoveryCode, totpCode, totpCounter, unbiasedIndices, verifyTotp };
@@ -1,7 +1,7 @@
1
1
  import { decryptSecret, encryptSecret } from "../crypto/secretCipher.js";
2
2
  import "./totpCipher.js";
3
3
  import { base32Encode, buildOtpauthUri, generateRecoveryCodes, generateTotpSecret, hashRecoveryCode, matchRecoveryCode, verifyTotp } from "./totpCrypto.js";
4
- //#region nodefony/src/totp/totpOperations.ts
4
+ // nodefony/src/totp/totpOperations.ts
5
5
  /**
6
6
  * Démarre l'enrôlement : génère un secret aléatoire, le **chiffre** au repos et
7
7
  * l'enregistre en attente de confirmation (`confirmedAt: null`). Retourne le
@@ -125,5 +125,4 @@ async function totpStatus(deps, userId) {
125
125
  recoveryCodesRemaining: record.recoveryCodes.length
126
126
  };
127
127
  }
128
- //#endregion
129
128
  export { beginTotpEnrollment, confirmTotpEnrollment, disableTotp, totpStatus, verifyTotpLogin };
@@ -1,5 +1,5 @@
1
1
  import { MemoryTotpSecretStore } from "./MemoryTotpSecretStore.js";
2
- //#region nodefony/src/totp/totpSecretStoreRegistry.ts
2
+ // nodefony/src/totp/totpSecretStoreRegistry.ts
3
3
  const factories = /* @__PURE__ */ new Map();
4
4
  /** Enregistre (ou remplace) la fabrique d'un store de secrets TOTP. */
5
5
  function registerTotpStore(name, factory) {
@@ -14,5 +14,4 @@ function listTotpStores() {
14
14
  return [...factories.keys()];
15
15
  }
16
16
  registerTotpStore("memory", () => new MemoryTotpSecretStore());
17
- //#endregion
18
17
  export { getTotpStoreFactory, listTotpStores, registerTotpStore };
@@ -0,0 +1,50 @@
1
+ import { InsecureTransportError } from "../../errors/InsecureTransportError.js";
2
+ // nodefony/src/transport/CredentialTransportPolicy.ts
3
+ /**
4
+ * Dit si un secret de connexion se refuse : en production, sans échappement
5
+ * explicite. Seule source de la décision — le firewall la compose au boot.
6
+ *
7
+ * @param environment - environnement normalisé du kernel.
8
+ * @param allowInsecureCredentials - échappement `security.allowInsecureCredentials`.
9
+ */
10
+ function shouldEnforceCredentialTransport(environment, allowInsecureCredentials) {
11
+ return environment === "production" && !allowInsecureCredentials;
12
+ }
13
+ /**
14
+ * Refuse, avant toute vérification, un mot de passe ou un code de second
15
+ * facteur reçu en clair. Une instance partagée au conteneur (`credentialTransport`)
16
+ * sert TOUTES les portes qui reçoivent un secret : formulaire de session, second
17
+ * facteur, Basic, émission de jeton — une porte oubliée serait le canal clair.
18
+ *
19
+ * La décision se prend sur le scheme EFFECTIF (`context.scheme`) : derrière un
20
+ * proxy qui termine TLS et déclaré de confiance, il vaut `https` alors que le
21
+ * pod écoute en HTTP. C'est le cas de la plupart des déploiements ; c'est donc
22
+ * `trustProxy` que nomme le journal, pas « passez en HTTPS ».
23
+ */
24
+ var CredentialTransportPolicy = class {
25
+ enforce;
26
+ #log;
27
+ #warned = false;
28
+ constructor(options) {
29
+ this.enforce = options.enforce;
30
+ this.#log = options.log ?? null;
31
+ }
32
+ /**
33
+ * Laisse passer un secret reçu sur TLS, ou n'importe lequel hors production.
34
+ *
35
+ * @param context - contexte de la requête qui porte le secret.
36
+ * @param door - porte qui le reçoit, nommée dans le journal.
37
+ * @throws InsecureTransportError (403) — secret reçu en clair en production.
38
+ */
39
+ assert(context, door) {
40
+ if (!this.enforce) return;
41
+ const scheme = context.scheme;
42
+ if (scheme === "https" || scheme === "wss") return;
43
+ if (!this.#warned && this.#log !== null) {
44
+ this.#warned = true;
45
+ this.#log(`Secret de connexion REFUSÉ (${door}) : la requête est arrivée en ${scheme ?? "scheme inconnu"} en production. Derrière un proxy qui termine TLS, déclarez-le de confiance (\`trustProxy\`, config @nodefony/http) pour que \`X-Forwarded-Proto\` soit lu ; sinon servez l'application en HTTPS. Échappement explicite : \`security.allowInsecureCredentials\`. Les refus suivants ne sont plus journalisés ici (voir l'audit).`);
46
+ }
47
+ throw new InsecureTransportError();
48
+ }
49
+ };
50
+ export { CredentialTransportPolicy, shouldEnforceCredentialTransport };
@@ -1,6 +1,6 @@
1
1
  import { RoleHierarchyWalker } from "../RoleHierarchyWalker.js";
2
2
  import "../../contracts/IAccessVoter.js";
3
- //#region nodefony/src/voter/RoleVoter.ts
3
+ // nodefony/src/voter/RoleVoter.ts
4
4
  /**
5
5
  * Voter built-in **niveau A** — résout les attributs `ROLE_*` via la hiérarchie
6
6
  * de rôles ({@link RoleHierarchyWalker}).
@@ -28,5 +28,4 @@ var RoleVoter = class {
28
28
  return Promise.resolve(vote);
29
29
  }
30
30
  };
31
- //#endregion
32
31
  export { RoleVoter, RoleVoter as default };
@@ -1,5 +1,5 @@
1
1
  import "../../contracts/IAccessVoter.js";
2
- //#region nodefony/src/voter/ScopeVoter.ts
2
+ // nodefony/src/voter/ScopeVoter.ts
3
3
  /**
4
4
  * Types de jetons **non scopables** — identité humaine interactive (session BFF
5
5
  * web/Studio/WS, login mot de passe) ou visiteur anonyme. Un scope `api:action`
@@ -48,5 +48,4 @@ var ScopeVoter = class {
48
48
  return Promise.resolve(token.getScopes().includes(attribute) ? "GRANT" : "ABSTAIN");
49
49
  }
50
50
  };
51
- //#endregion
52
51
  export { ScopeVoter, ScopeVoter as default };
@@ -1,6 +1,6 @@
1
1
  import { RoleVoter } from "./RoleVoter.js";
2
2
  import { ScopeVoter } from "./ScopeVoter.js";
3
- //#region nodefony/src/voter/voterRegistry.ts
3
+ // nodefony/src/voter/voterRegistry.ts
4
4
  const factories = /* @__PURE__ */ new Map();
5
5
  /**
6
6
  * Enregistre (ou remplace) la fabrique d'un voter. Appelé par les builtins au
@@ -16,5 +16,4 @@ function listVoterFactories() {
16
16
  }
17
17
  registerVoterFactory("role", ({ container }) => new RoleVoter(container));
18
18
  registerVoterFactory("scope", () => new ScopeVoter());
19
- //#endregion
20
19
  export { listVoterFactories, registerVoterFactory };
@@ -1,5 +1,5 @@
1
1
  import { assertPageQuery } from "nodefony";
2
- //#region nodefony/src/webauthn/MemoryWebAuthnCredentialStore.ts
2
+ // nodefony/src/webauthn/MemoryWebAuthnCredentialStore.ts
3
3
  /**
4
4
  * Projection contractuelle : credential complet → vue admin **sans `publicKey`**.
5
5
  * Partagée par les backends qui matérialisent des credentials en mémoire.
@@ -135,5 +135,4 @@ var MemoryWebAuthnCredentialStore = class {
135
135
  }
136
136
  }
137
137
  };
138
- //#endregion
139
138
  export { MemoryWebAuthnCredentialStore, MemoryWebAuthnCredentialStore as default, toWebAuthnSummary };
@@ -1,5 +1,5 @@
1
1
  import { MemoryWebAuthnCredentialStore } from "./MemoryWebAuthnCredentialStore.js";
2
- //#region nodefony/src/webauthn/webAuthnCredentialStoreRegistry.ts
2
+ // nodefony/src/webauthn/webAuthnCredentialStoreRegistry.ts
3
3
  const factories = /* @__PURE__ */ new Map();
4
4
  /** Enregistre (ou remplace) la fabrique d'un store de credentials WebAuthn. */
5
5
  function registerWebAuthnStore(name, factory) {
@@ -14,5 +14,4 @@ function listWebAuthnStores() {
14
14
  return [...factories.keys()];
15
15
  }
16
16
  registerWebAuthnStore("memory", () => new MemoryWebAuthnCredentialStore());
17
- //#endregion
18
17
  export { getWebAuthnStoreFactory, listWebAuthnStores, registerWebAuthnStore };
@@ -1,6 +1,6 @@
1
1
  import { WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS } from "./webhookSort.js";
2
2
  import { assertPageQuery, compareByOrder, pickOrder } from "nodefony";
3
- //#region nodefony/src/webhook/MemoryWebhookStore.ts
3
+ // nodefony/src/webhook/MemoryWebhookStore.ts
4
4
  /**
5
5
  * Applique les filtres d'{@link IWebhookListQuery} à un endpoint — sémantique de
6
6
  * RÉFÉRENCE du contrat, partagée par les backends qui filtrent en mémoire.
@@ -91,5 +91,4 @@ var MemoryWebhookStore = class {
91
91
  return n;
92
92
  }
93
93
  };
94
- //#endregion
95
94
  export { MemoryWebhookStore, matchesWebhookQuery };
@@ -1,5 +1,5 @@
1
1
  import { webhookSignatureHeaders } from "./webhookSignature.js";
2
- //#region nodefony/src/webhook/WebhookDispatcher.ts
2
+ // nodefony/src/webhook/WebhookDispatcher.ts
3
3
  /**
4
4
  * Dispatcher de webhooks sortants — abonné au journal d'audit, il filtre les
5
5
  * événements par souscription, signe (Standard Webhooks v1) et livre, **sans
@@ -205,5 +205,4 @@ var WebhookDispatcher = class {
205
205
  this.#queue = null;
206
206
  }
207
207
  };
208
- //#endregion
209
208
  export { WebhookDispatcher, backoffMs, classifyDelivery, matchesSubscription };
@@ -1,6 +1,6 @@
1
1
  import { decryptSecret, deriveKey, encryptSecret, generateEphemeralKey } from "../crypto/secretCipher.js";
2
2
  import { Buffer } from "node:buffer";
3
- //#region nodefony/src/webhook/webhookCipher.ts
3
+ // nodefony/src/webhook/webhookCipher.ts
4
4
  /**
5
5
  * Chiffrement réversible du secret de signature webhook au repos — façade de
6
6
  * domaine au-dessus de {@link ../crypto/secretCipher} (calque {@link ../totp/totpCipher}).
@@ -23,5 +23,4 @@ const WEBHOOK_DERIVATION = {
23
23
  function deriveWebhookKey(material) {
24
24
  return deriveKey(material, WEBHOOK_DERIVATION);
25
25
  }
26
- //#endregion
27
26
  export { decryptSecret, deriveWebhookKey, encryptSecret, generateEphemeralKey };
@@ -2,7 +2,7 @@ import { Buffer } from "node:buffer";
2
2
  import { isIP } from "node:net";
3
3
  import { request } from "node:https";
4
4
  import { request as request$1 } from "node:http";
5
- //#region nodefony/src/webhook/webhookDelivery.ts
5
+ // nodefony/src/webhook/webhookDelivery.ts
6
6
  /**
7
7
  * Émetteur HTTP d'une livraison webhook — `node:http(s)` natif (0 dép). Trois
8
8
  * propriétés de sécurité par rapport à `fetch` :
@@ -98,5 +98,4 @@ function deliverWebhook(url, body, headers, opts) {
98
98
  req.end();
99
99
  });
100
100
  }
101
- //#endregion
102
101
  export { deliverWebhook };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/webhook/webhookFilters.ts
1
+ // nodefony/src/webhook/webhookFilters.ts
2
2
  /**
3
3
  * **Le vocabulaire de filtre des endpoints webhook**, en noms PUBLICS — ceux
4
4
  * qu'un client écrit dans l'URL (`?enabled=false&event=user.created`).
@@ -52,5 +52,4 @@ const WEBHOOK_FACETS = {
52
52
  * Un test verrouille l'accord entre cette liste et {@link WEBHOOK_FACETS}.
53
53
  */
54
54
  const WEBHOOK_STATS_FILTERS = { event: "string" };
55
- //#endregion
56
55
  export { WEBHOOK_FACETS, WEBHOOK_FILTERS, WEBHOOK_STATS_FILTERS };
@@ -1,6 +1,6 @@
1
1
  import { createHmac } from "node:crypto";
2
2
  import { Buffer } from "node:buffer";
3
- //#region nodefony/src/webhook/webhookSignature.ts
3
+ // nodefony/src/webhook/webhookSignature.ts
4
4
  /**
5
5
  * Signature des webhooks sortants — schéma **Standard Webhooks v1**
6
6
  * (standardwebhooks.com), choisi plutôt que RFC 9421 (trop lourd pour un webhook,
@@ -47,5 +47,4 @@ function webhookSignatureHeaders(secret, id, timestampS, body) {
47
47
  "webhook-signature": signStandardWebhook(secret, id, timestampS, body)
48
48
  };
49
49
  }
50
- //#endregion
51
50
  export { buildSignatureBase, parseWebhookSecret, signStandardWebhook, webhookSignatureHeaders };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/webhook/webhookSort.ts
1
+ // nodefony/src/webhook/webhookSort.ts
2
2
  /**
3
3
  * **Le vocabulaire de tri des endpoints webhook**, en noms PUBLICS — ceux qu'un
4
4
  * client écrit dans l'URL (`?order=failureCount:DESC`), jamais des noms de
@@ -44,5 +44,4 @@ const WEBHOOK_SORTABLE_FIELDS = [
44
44
  * des deux ne jamais apparaître.
45
45
  */
46
46
  const WEBHOOK_DEFAULT_ORDER = [["createdAt", "DESC"], ["id", "ASC"]];
47
- //#endregion
48
47
  export { WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS };
@@ -1,5 +1,5 @@
1
1
  import { MemoryWebhookStore } from "./MemoryWebhookStore.js";
2
- //#region nodefony/src/webhook/webhookStoreRegistry.ts
2
+ // nodefony/src/webhook/webhookStoreRegistry.ts
3
3
  const factories = /* @__PURE__ */ new Map();
4
4
  /** Enregistre (ou remplace) la fabrique d'un store d'endpoints webhook. */
5
5
  function registerWebhookStore(name, factory) {
@@ -14,5 +14,4 @@ function listWebhookStores() {
14
14
  return [...factories.keys()];
15
15
  }
16
16
  registerWebhookStore("memory", () => new MemoryWebhookStore());
17
- //#endregion
18
17
  export { getWebhookStoreFactory, listWebhookStores, registerWebhookStore };
@@ -10,6 +10,7 @@ import ApiKeyService from "./nodefony/service/apiKeys.js";
10
10
  import AuditService from "./nodefony/service/auditService.js";
11
11
  import TotpService from "./nodefony/service/totp.js";
12
12
  import WebhookService from "./nodefony/service/webhooks.js";
13
+ import KeycloakService from "./nodefony/service/keycloak.js";
13
14
  import type { ISecurityConfigInput } from "./nodefony/config/defineModuleConfig.js";
14
15
  declare module "nodefony" {
15
16
  interface NodefonyModuleConfig {
@@ -25,7 +26,7 @@ declare module "nodefony" {
25
26
  * + Zod validée au boot (fail-closed si invalide). Consomme `@nodefony/user`
26
27
  * (IUser/IUserProvider/IPasswordVerifier) — jamais l'inverse.
27
28
  *
28
- * Livré : firewall (zones, mode first|all, challenge RFC 7235), authenticators
29
+ * Livré : firewall (zones, mode first|all, challenge RFC 9110 §11), authenticators
29
30
  * `anonymous`/`userpassword` (Basic RFC 7617)/`session` (BFF, J3), flux
30
31
  * login/logout/me (`AuthFlow`, anti-fixation + throttling partagé).
31
32
  * Jwt/oauth2/mtls/apikey, CORS, CSRF, autorisation par décorateurs et data
@@ -48,6 +49,14 @@ declare class Security extends Module {
48
49
  * No-op si le broker est absent (Studio non chargé) ou déjà enregistré.
49
50
  */
50
51
  onKernelBoot(): Promise<this>;
52
+ /**
53
+ * Déclare au bilan de démarrage les routes métier laissées PUBLIQUES.
54
+ *
55
+ * `onReady` et pas `onBoot` : les zones sont compilées par le pare-feu à son
56
+ * propre `onBoot`, posé APRÈS celui du module — à `onBoot` la liste est encore
57
+ * vide. `onReady` précède l'écriture du bilan (`var/last-boot.json`).
58
+ */
59
+ onKernelReady(): Promise<this>;
51
60
  }
52
61
  export default Security;
53
62
  export { Firewall };
@@ -63,8 +72,11 @@ export { OAuth2Service };
63
72
  export { AuditService };
64
73
  export { TotpService };
65
74
  export { WebhookService };
75
+ export { KeycloakService };
76
+ export type { IKeycloakRealmPlan, IKeycloakRealmRequest, } from "./nodefony/service/keycloak.js";
66
77
  export type { IWebhookRegisterInput, IWebhookSecretReveal, IWebhookDeliveryPolicy, } from "./nodefony/service/webhooks.js";
67
78
  export type { ISafeUser, ILoginOutcome } from "./nodefony/service/authFlow.js";
79
+ export { amrForFactor } from "./nodefony/src/sessionAuthentication.js";
68
80
  export type { ITokenResponse } from "./nodefony/service/tokenService.js";
69
81
  export type { IApiKeyView, IApiKeyCreated, IApiKeyCapabilities, ICreateApiKeyOptions, } from "./nodefony/contracts/IApiKey.js";
70
82
  export { ApiKeyError } from "./nodefony/errors/ApiKeyError.js";
@@ -114,14 +126,20 @@ export type { IWebAuthnUser, IWebAuthnAssertionResult, } from "./nodefony/servic
114
126
  export type { IWebAuthnCredential } from "./nodefony/contracts/IWebAuthnCredential.js";
115
127
  export type { IWebAuthnCredentialStore, IWebAuthnCredentialSummary, IWebAuthnListQuery, WebAuthnAuthUpdate, } from "./nodefony/contracts/IWebAuthnCredentialStore.js";
116
128
  export type { IOAuthAuthorization } from "./nodefony/service/oauth2.js";
117
- export type { IOAuthProvider, IIssuerPolicy, } from "./nodefony/contracts/IOAuthProvider.js";
118
- export { registerOAuthProvider, getOAuthProviderFactory, listOAuthProviders, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
119
- export type { OAuthProviderFactory, IOAuthProviderContext, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
129
+ export type { IOAuthProvider, IIssuerPolicy, ILogoutRequest, ILogoutTokenClaims, } from "./nodefony/contracts/IOAuthProvider.js";
130
+ export { registerOAuthProvider, getOAuthProviderFactory, listOAuthProviders, oauthProviderRequiresIssuer, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
131
+ export type { OAuthProviderFactory, IOAuthProviderContext, IOAuthProviderRegistration, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
120
132
  export { OAuth2Client, OAuth2Tokens, OAuth2RequestError, generateState, generateCodeVerifier, createCodeChallenge, } from "./nodefony/src/oauth/oauth2Client.js";
121
133
  export type { IOAuth2ClientOptions } from "./nodefony/src/oauth/oauth2Client.js";
122
134
  export { discoverAuthorizationServer } from "./nodefony/src/oauth/metadata.js";
135
+ export { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
136
+ export type { IClientCredentialsRequest } from "./nodefony/src/oauth/clientCredentials.js";
137
+ export { diagnoseOAuthProvider } from "./nodefony/src/oauth/providerDiagnosis.js";
138
+ export type { IOAuthDiagnosis, IOAuthDiagnosisInput, IOAuthCheck, OAuthCheckName, OAuthCheckStatus, OAuthFailureKind, } from "./nodefony/src/oauth/providerDiagnosis.js";
139
+ export { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme, } from "./nodefony/keycloak/keycloakAdmin.js";
140
+ export type { IKeycloakAdminClientOptions, IKeycloakAdminCredentials, IKeycloakAdminTarget, IKeycloakLoginThemeResult, KeycloakLoginThemeOutcome, } from "./nodefony/keycloak/keycloakAdmin.js";
123
141
  export type { IDiscoveredAuthorizationServer, IDiscoveryOptions, } from "./nodefony/src/oauth/metadata.js";
124
- export { createOidcProvider, createDiscoveredOidcProvider, } from "./nodefony/src/oauth/providers/oidc.js";
142
+ export { createOidcProvider, createDiscoveredOidcProvider, readLogoutTokenClaims, } from "./nodefony/src/oauth/providers/oidc.js";
125
143
  export type { IOidcPkceClient, IOidcProviderOptions, } from "./nodefony/src/oauth/providers/oidc.js";
126
144
  export { createGithubProvider } from "./nodefony/src/oauth/providers/github.js";
127
145
  export { MemoryAuditStore } from "./nodefony/src/audit/MemoryAuditStore.js";
@@ -159,6 +177,8 @@ export { defineSecurityConfig, securityConfigJsonSchema, } from "./nodefony/conf
159
177
  export type { ISecurityConfig, ISecurityConfigInput, ISecurityAreaConfig, } from "./nodefony/config/defineModuleConfig.js";
160
178
  export { LoginThrottler } from "./nodefony/src/throttle/LoginThrottler.js";
161
179
  export type { ILoginThrottleOptions } from "./nodefony/src/throttle/LoginThrottler.js";
162
- export { AuthenticationError, AccessDeniedError, ThrottledError, UnverifiableTokenError, InvalidTargetError, CsrfError, SsrfError, } from "./nodefony/errors/index.js";
180
+ export { CredentialTransportPolicy, shouldEnforceCredentialTransport, } from "./nodefony/src/transport/CredentialTransportPolicy.js";
181
+ export type { ICredentialTransportContext, ICredentialTransportPolicyOptions, } from "./nodefony/src/transport/CredentialTransportPolicy.js";
182
+ export { AuthenticationError, AccessDeniedError, ThrottledError, UnverifiableTokenError, InvalidTargetError, CsrfError, SsrfError, InsecureTransportError, } from "./nodefony/errors/index.js";
163
183
  export { VoterVote } from "./nodefony/contracts/index.js";
164
184
  export type { IToken, IAuthenticator, ISecuredArea, IFirewall, IAccessVoter, IAuthorizationService, IAccessTokenRecord, ITokenStore, ITokenListQuery, ITokenUsage, IResourcePermission, TokenRevokeReason, TokenStatus, IJwtKeystore, IJwtSigningKey, } from "./nodefony/contracts/index.js";