@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,117 @@
1
+ import type { IOAuthProvider } from "../../contracts/IOAuthProvider.js";
2
+ import { type OAuth2ClientAuthMethod } from "./oauth2Client.js";
3
+ /**
4
+ * **Diagnostic d'un fournisseur OAuth sans connexion humaine** — dit POURQUOI un
5
+ * branchement échoue, là où le journal ne montre qu'un 401 ou une page d'erreur
6
+ * du fournisseur.
7
+ *
8
+ * Trois sondes, dans l'ordre où l'une éclaire l'autre :
9
+ *
10
+ * 1. **découverte** — le fournisseur se construit comme au premier login
11
+ * (métadonnées, émetteur identique RFC 8414 §3.3, PKCE S256 annoncé) ;
12
+ * 2. **point d'autorisation** — la requête que le bouton enverrait, sans la
13
+ * suivre : le serveur accepte-t-il ce client et cette URL de retour ?
14
+ * 3. **point de jeton** — un code INVENTÉ présenté avec le vrai secret :
15
+ * `invalid_grant` prouve que le client s'est authentifié (seul le code est
16
+ * refusé), un refus d'authentification prouve le contraire (RFC 6749 §5.2).
17
+ *
18
+ * 🔴 **Rien n'est réécrit ici.** Les sondes passent par le fournisseur que la
19
+ * fabrique construit pour le login — mêmes points d'entrée, même méthode
20
+ * d'authentification, même URL de retour. Un diagnostic qui reconstruirait sa
21
+ * propre requête prouverait que SA requête passe, pas celle du login.
22
+ *
23
+ * @remarks Chemin froid (une commande, un écran d'administration). La sonde
24
+ * d'autorisation ouvre côté fournisseur une session de connexion vide, qui
25
+ * expire seule ; aucun compte n'est touché, aucun jeton n'est émis.
26
+ */
27
+ /**
28
+ * Les sondes, dans leur ordre. `theme` n'existe que si l'appelant fournit un
29
+ * {@link IOAuthDiagnosisInput.pageCheck} ET que la page d'autorisation s'y
30
+ * prête (Keycloak).
31
+ */
32
+ export type OAuthCheckName = "discovery" | "authorization" | "token" | "theme";
33
+ /**
34
+ * Verdict d'une sonde. `inconclusive` = le serveur a répondu quelque chose
35
+ * qu'on ne sait pas lire — ni un quitus, ni un manquement ; `skipped` = une
36
+ * sonde précédente a rendu celle-ci impossible.
37
+ */
38
+ export type OAuthCheckStatus = "ok" | "failed" | "inconclusive" | "skipped";
39
+ /** La cause d'un échec, lisible par une machine (rapport, console, tests). */
40
+ export type OAuthFailureKind =
41
+ /** Le document de métadonnées se déclare d'un autre émetteur (RFC 8414 §3.3). */
42
+ "issuer-mismatch"
43
+ /** Émetteur injoignable, document absent ou incomplet, PKCE S256 non annoncé. */
44
+ | "discovery-failed"
45
+ /** Le point d'autorisation refuse l'URL de retour (non enregistrée). */
46
+ | "redirect-uri-rejected"
47
+ /** Le point d'autorisation refuse la demande pour une autre raison. */
48
+ | "authorization-refused"
49
+ /** Le point d'autorisation n'a pas répondu. */
50
+ | "authorization-unreachable"
51
+ /** Le client existe (le point d'autorisation l'accepte), son secret est refusé. */
52
+ | "secret-rejected"
53
+ /** Authentification du client refusée : client inconnu, ou secret refusé. */
54
+ | "client-rejected"
55
+ /** Le point de jeton refuse pour une autre raison (flux non autorisé…). */
56
+ | "token-refused"
57
+ /** Le point de jeton n'a pas répondu. */
58
+ | "token-unreachable"
59
+ /** La page du fournisseur ne porte pas l'habillage de `/login`. */
60
+ | "login-theme-mismatch";
61
+ /** Le résultat d'une sonde. */
62
+ export interface IOAuthCheck {
63
+ readonly name: OAuthCheckName;
64
+ readonly status: OAuthCheckStatus;
65
+ /** Présent si et seulement si `status === "failed"`. */
66
+ readonly kind?: OAuthFailureKind;
67
+ /** Phrase destinée à l'opérateur : le constat, puis le geste. */
68
+ readonly message: string;
69
+ }
70
+ /** Le diagnostic d'un fournisseur. */
71
+ export interface IOAuthDiagnosis {
72
+ /** Nom du fournisseur (`oauth2.providers.<name>`). */
73
+ readonly provider: string;
74
+ /** `true` si aucune sonde n'a échoué. Une sonde non concluante ne l'empêche pas. */
75
+ readonly ok: boolean;
76
+ readonly checks: readonly IOAuthCheck[];
77
+ }
78
+ /** Ce que le diagnostic d'un fournisseur doit savoir. */
79
+ export interface IOAuthDiagnosisInput {
80
+ /** Nom du fournisseur. */
81
+ readonly name: string;
82
+ /** Construit le fournisseur comme pour un login — découverte comprise. */
83
+ readonly build: () => IOAuthProvider | Promise<IOAuthProvider>;
84
+ /** Identifiant du client, tel que configuré. */
85
+ readonly clientId: string;
86
+ /** URL de retour configurée — celle que le fournisseur doit connaître. */
87
+ readonly redirectUri: string;
88
+ /** Méthode d'authentification déclarée ; `"none"` = client public. */
89
+ readonly clientAuthMethod?: OAuth2ClientAuthMethod | undefined;
90
+ /** Portées configurées ; vides = celles du fournisseur. */
91
+ readonly scopes?: readonly string[] | undefined;
92
+ /**
93
+ * Transport de la sonde d'autorisation. Celle du point de jeton passe par le
94
+ * fournisseur, donc par le transport que SA fabrique a reçu.
95
+ */
96
+ readonly fetch?: typeof globalThis.fetch | undefined;
97
+ /** Délai d'attente de la sonde d'autorisation, en millisecondes. */
98
+ readonly timeoutMs?: number | undefined;
99
+ /**
100
+ * Juge la page de connexion que le point d'autorisation sert (ses premiers
101
+ * octets). Rend une sonde de plus, ou `null` quand la page ne s'y prête
102
+ * pas. Le diagnostic reste générique : c'est le kit Keycloak qui sait lire
103
+ * le thème d'une page de Keycloak.
104
+ */
105
+ readonly pageCheck?: ((html: string) => IOAuthCheck | null) | undefined;
106
+ }
107
+ /**
108
+ * Diagnostique un fournisseur OAuth sans connexion humaine.
109
+ *
110
+ * Ne lève jamais : un serveur muet ou hostile devient une sonde en échec, et
111
+ * les sondes que l'échec rend impossibles sont DITES sautées — un silence ne
112
+ * vaut pas quitus.
113
+ *
114
+ * @param input - fournisseur à construire et configuration à confronter.
115
+ * @returns les trois sondes et leur verdict.
116
+ */
117
+ export declare function diagnoseOAuthProvider(input: IOAuthDiagnosisInput): Promise<IOAuthDiagnosis>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Traduction des rôles d'un fournisseur d'identité (Keycloak, tout serveur
3
+ * OpenID Connect) en rôles de l'application.
4
+ *
5
+ * Logique PURE, sans réseau ni base : la même fonction sert la connexion par
6
+ * navigateur (claims du jeton reçu du point de jeton) et l'API (claims d'un
7
+ * jeton dont la signature vient d'être vérifiée). Deux implémentations
8
+ * divergeraient — un rôle retiré dans l'annuaire disparaîtrait d'un côté et
9
+ * survivrait de l'autre.
10
+ */
11
+ /** Où lire les rôles dans les claims du fournisseur. */
12
+ export type ProviderRoleSource = "client" | "realm" | "groups";
13
+ /** Source par défaut : les rôles du CLIENT — ceux que l'annuaire attribue à CETTE application. */
14
+ export declare const DEFAULT_ROLE_SOURCES: readonly ProviderRoleSource[];
15
+ /**
16
+ * Préfixe des rôles de PLATEFORME (console d'administration, plan
17
+ * d'administration) — jamais accordés par un annuaire externe.
18
+ */
19
+ export declare const PLATFORM_ROLE_PREFIX = "ROLE_NODEFONY_";
20
+ /** `true` si le rôle relève de la plateforme (`ROLE_NODEFONY_*`). */
21
+ export declare function isPlatformRole(role: string): boolean;
22
+ /** Table de correspondance compilée d'UN fournisseur. */
23
+ export interface IProviderRoleMapping {
24
+ /** Client dont on lit les rôles (`resource_access.<clientId>.roles`). */
25
+ readonly clientId: string;
26
+ /** Sources lues, dans l'ordre. */
27
+ readonly sources: readonly ProviderRoleSource[];
28
+ /** Rôle du fournisseur → rôle de l'application (objet sans prototype). */
29
+ readonly table: Readonly<Record<string, string>>;
30
+ }
31
+ /** Ce que la configuration d'un fournisseur apporte à la correspondance. */
32
+ export interface IProviderRoleConfig {
33
+ readonly clientId: string;
34
+ readonly roleMapping?: Readonly<Record<string, string>> | undefined;
35
+ readonly rolesSource?: readonly ProviderRoleSource[] | undefined;
36
+ }
37
+ /**
38
+ * Compile la table d'un fournisseur — `null` quand il n'en déclare aucune, ce
39
+ * qui laisse les rôles du compte entièrement locaux (comportement historique).
40
+ *
41
+ * @param config - configuration du fournisseur.
42
+ * @returns la table compilée, ou `null`.
43
+ */
44
+ export declare function compileProviderRoleMapping(config: IProviderRoleConfig): IProviderRoleMapping | null;
45
+ /**
46
+ * Rôles de l'application accordés par le fournisseur, d'après ses claims.
47
+ *
48
+ * Un rôle du fournisseur absent de la table est IGNORÉ — jamais recopié tel
49
+ * quel : l'annuaire ne fabrique pas de rôle que l'application n'a pas déclaré.
50
+ * Plusieurs jeux de claims (jeton d'accès, jeton d'identité) se cumulent : ils
51
+ * viennent du même émetteur, chacun peut porter une partie des rôles selon
52
+ * les « mappers » configurés chez lui.
53
+ *
54
+ * @param mapping - table compilée du fournisseur.
55
+ * @param claimSets - claims à lire ; `null`/`undefined` ignorés.
56
+ * @returns rôles de l'application, triés et sans doublon.
57
+ */
58
+ export declare function mapProviderRoles(mapping: IProviderRoleMapping, ...claimSets: (Readonly<Record<string, unknown>> | null | undefined)[]): string[];
@@ -1,4 +1,4 @@
1
- import type { IOAuthProvider } from "../../../contracts/IOAuthProvider.js";
1
+ import type { ILogoutTokenClaims, IOAuthProvider } from "../../../contracts/IOAuthProvider.js";
2
2
  import type { IOAuthProviderContext } from "../oauthProviderRegistry.js";
3
3
  import { type IAuthorizationRequest, type ITokenRequest, type OAuth2Tokens } from "../oauth2Client.js";
4
4
  import { type IDiscoveryOptions } from "../metadata.js";
@@ -30,7 +30,40 @@ export interface IOidcProviderOptions {
30
30
  readonly decodeIdToken: (idToken: string) => object | Promise<object>;
31
31
  /** Scopes par défaut si la config n'en précise aucun. */
32
32
  readonly defaultScopes?: string[];
33
+ /**
34
+ * Point de déconnexion du fournisseur (`end_session_endpoint`). Absent ou
35
+ * `null` : le fournisseur n'offre pas `createLogoutURL`.
36
+ */
37
+ readonly endSessionEndpoint?: string | null;
38
+ /**
39
+ * Vérifie la SIGNATURE d'un jeton de déconnexion (émetteur, audience,
40
+ * algorithme, `exp`/`iat`) et rend ses claims, ou `null` s'il est refusé ;
41
+ * lève sur une panne. Absente : le fournisseur n'offre pas
42
+ * `verifyLogoutToken`. Le sens du jeton (§2.4) est contrôlé ensuite par
43
+ * {@link readLogoutTokenClaims}, jamais par cette fonction.
44
+ */
45
+ readonly verifyLogoutTokenSignature?: (logoutToken: string) => Promise<{
46
+ issuer: string;
47
+ payload: Record<string, unknown>;
48
+ } | null>;
33
49
  }
50
+ /**
51
+ * Lit ce qu'un jeton de déconnexion DÉSIGNE, une fois sa signature vérifiée —
52
+ * les points 5 à 7 de la validation (OpenID Connect Back-Channel Logout 1.0
53
+ * §2.6) : `sub` ou `sid` présent, `events` qui le déclare jeton de
54
+ * déconnexion, AUCUN `nonce`.
55
+ *
56
+ * @remarks Ces gardes ferment la confusion entre jetons (§4.1) : un ID token
57
+ * du même émetteur, pour le même client, porte une signature tout aussi
58
+ * valide — c'est `events` qui le distingue, et l'absence de `nonce` qui
59
+ * empêche le chemin inverse. Fonction PURE, éprouvable sans clé ni réseau.
60
+ *
61
+ * @param issuer - émetteur canonique rendu par la vérification de signature.
62
+ * @param payload - claims vérifiés.
63
+ * @returns les claims utiles, ou `null` si le jeton n'est pas un jeton de
64
+ * déconnexion valide.
65
+ */
66
+ export declare function readLogoutTokenClaims(issuer: string, payload: Record<string, unknown>): ILogoutTokenClaims | null;
34
67
  /**
35
68
  * Fabrique un {@link IOAuthProvider} **générique OIDC** — couvre TOUT fournisseur
36
69
  * OpenID Connect sans code spécifique : le profil se lit toujours pareil (claims
@@ -0,0 +1,36 @@
1
+ import type { ISecurityConfig } from "../config/config.js";
2
+ /** Une zone que la connexion OAuth ouvre à quiconque obtient un compte. */
3
+ export interface IOpenToSignupZone {
4
+ /** Nom de la zone (`areas.<nom>`). */
5
+ zone: string;
6
+ /** Fournisseurs OAuth qui créent les comptes à la volée, triés. */
7
+ providers: readonly string[];
8
+ /** Le constat, en une phrase — la MÊME au démarrage, dans la console et dans `doctor`. */
9
+ message: string;
10
+ /** Les deux gestes qui le soldent, prêts à taper. */
11
+ action: string;
12
+ }
13
+ /**
14
+ * Les zones protégées qui n'exigent aucun rôle alors que la connexion OAuth
15
+ * crée les comptes à la volée — c'est-à-dire ouvertes à tout compte que le
16
+ * fournisseur délivre.
17
+ *
18
+ * Une zone sans `roles` laisse passer toute identité authentifiée : c'est un
19
+ * défaut voulu. Il devient un piège quand `oauth2.allowSignup` vaut `true`
20
+ * (le défaut) : avec Google ou GitHub, « authentifié » veut dire « n'importe
21
+ * qui sur Internet ». Une zone est donc retenue quand TOUT ceci est vrai :
22
+ * protégée, sans `anonymous`, sans `roles`, avec `session` (c'est par la
23
+ * session qu'un compte OAuth entre), et `oauth2` actif avec au moins un
24
+ * fournisseur et `allowSignup`.
25
+ *
26
+ * Le remède est une DÉCLARATION : `roles: ["ROLE_USER"]` ne change rien au
27
+ * comportement (un compte créé reçoit ce rôle) mais écrit l'intention.
28
+ *
29
+ * Calculée au démarrage et dans `describe()`, jamais par requête. C'est la
30
+ * SEULE implémentation de la règle : `doctor` lit son résultat par le plan
31
+ * d'administration, il ne la recalcule pas.
32
+ *
33
+ * @param config - la configuration de sécurité validée.
34
+ * @returns les zones concernées, dans l'ordre de déclaration ; vide sinon.
35
+ */
36
+ export declare function findZonesOpenToSignup(config: Pick<ISecurityConfig, "areas" | "oauth2">): IOpenToSignupZone[];
@@ -31,8 +31,9 @@ export declare class UserRealtimeToken implements IRealtimeToken {
31
31
  * @param revalidate - preuve de vie de l'identité, adaptée à son mode.
32
32
  * @param type - mode d'authentification réel (défaut `"session"`).
33
33
  * @param scopes - scopes délégués du jeton d'origine (défaut aucun).
34
+ * @param confirmAccount - relecture du compte, payée sur `api.request` seul.
34
35
  */
35
- constructor(user: IUser, revalidate?: ((nowMs?: number) => Promise<boolean>) | null, type?: string, scopes?: readonly string[]);
36
+ constructor(user: IUser, revalidate?: ((nowMs?: number) => Promise<boolean>) | null, type?: string, scopes?: readonly string[], confirmAccount?: (() => Promise<boolean>) | null);
36
37
  /**
37
38
  * Zero Trust : l'identité qui a ouvert cette socket est-elle TOUJOURS vivante ?
38
39
  *
@@ -46,6 +47,11 @@ export declare class UserRealtimeToken implements IRealtimeToken {
46
47
  * sans dépendre de l'horloge de la machine.
47
48
  */
48
49
  isValid(nowMs?: number): Promise<boolean>;
50
+ /**
51
+ * Le compte qui a ouvert la socket existe-t-il toujours, actif, et sous le
52
+ * même `id` immuable ? `true` si aucune confirmation n'est câblée.
53
+ */
54
+ confirmAccount(): Promise<boolean>;
49
55
  getUserIdentifier(): string;
50
56
  isAuthenticated(): boolean;
51
57
  getRoles(): string[];
@@ -32,6 +32,12 @@ export interface IRealtimeToken {
32
32
  * (absent = valide). Appelé par le pont `api.request` avant l'action data plane.
33
33
  */
34
34
  isValid?(nowMs?: number): boolean | Promise<boolean>;
35
+ /**
36
+ * Le compte de la socket existe-t-il toujours, actif, sous le même `id` ?
37
+ * (miroir de `IRealtimeToken.confirmAccount` realtime). Optionnel (absent =
38
+ * confirmé). Relit l'annuaire : payé sur `api.request` seul, jamais au tick.
39
+ */
40
+ confirmAccount?(): Promise<boolean>;
35
41
  }
36
42
  /**
37
43
  * Données de handshake WS (miroir de `IRealtimeHandshake`) — DTO neutre passé à
@@ -0,0 +1,27 @@
1
+ import type { IRolesWithin } from "nodefony";
2
+ import type { IUser } from "@nodefony/user";
3
+ import type { RoleHierarchyWalker } from "./RoleHierarchyWalker.js";
4
+ /**
5
+ * Vue de l'annuaire PLUS permissive que `IUserProvider` (qui promet un
6
+ * `IUser`) : un fournisseur d'application peut rendre `null` ou `undefined`
7
+ * au lieu de lever — sans garde, zéro rôle vaudrait accord. Tout
8
+ * `IUserProvider` la satisfait.
9
+ */
10
+ export interface IAccountDirectory {
11
+ loadUserByIdentifier(identifier: string): Promise<IUser | null | undefined>;
12
+ }
13
+ /**
14
+ * La règle « jouer un autre compte » : les rôles de `target`, hiérarchie
15
+ * comprise, sont-ils TOUS atteints par ceux de `owner` ? Jouer un compte ne
16
+ * monte jamais en droits.
17
+ *
18
+ * Fermée en cas de doute : un annuaire absent, un compte introuvable — y
19
+ * compris un sujet externe éphémère, qui n'a pas de compte local — rendent un
20
+ * refus, jamais un accord. L'annuaire est résolu à l'appel : le service `users`
21
+ * peut être posé après le pare-feu.
22
+ *
23
+ * @param resolveUsers - rend l'annuaire des comptes (`users`), ou `null`
24
+ * @param hierarchy - la hiérarchie de rôles du pare-feu
25
+ * @returns la règle, à poser au conteneur sous `ROLES_WITHIN`
26
+ */
27
+ export declare function rolesWithin(resolveUsers: () => IAccountDirectory | null, hierarchy: RoleHierarchyWalker): IRolesWithin;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Facteurs d'authentification de Nodefony → méthodes RFC 8176, posées dans la
3
+ * session à son ouverture. Les clés et leur lecteur sont au cœur
4
+ * (`readSessionAuthentication`, `nodefony`) ; seule la POLITIQUE de
5
+ * correspondance vit ici.
6
+ */
7
+ /**
8
+ * Valeurs RFC 8176 d'un facteur nommé par un chemin d'authentification.
9
+ *
10
+ * @param factor - le facteur tel que l'audit le nomme (`password`, `totp`,
11
+ * `recovery`, `webauthn`, `oauth`…)
12
+ * @returns les méthodes vérifiées localement ; vide pour un facteur délégué
13
+ */
14
+ export declare function amrForFactor(factor: string): readonly string[];
@@ -18,3 +18,25 @@ import type { IUser, IUserProvider } from "@nodefony/user";
18
18
  * compte verrouillé ou désactivé.
19
19
  */
20
20
  export declare function resolveSessionIdentity(provider: IUserProvider, identifier: string): Promise<IUser>;
21
+ /**
22
+ * Résout l'identité d'une session DÉJÀ ouverte, en exigeant qu'elle ait été
23
+ * ouverte pour CE compte — {@link resolveSessionIdentity} plus la liaison à
24
+ * l'identifiant immuable (`isBoundToAccount`).
25
+ *
26
+ * La session range son principal sous l'`identifier`, qu'un compte recréé
27
+ * (suppression hors framework, restauration, import) peut reprendre : sans la
28
+ * liaison, la session de l'ancien titulaire ouvrirait le compte du nouveau.
29
+ * Une session sans liaison est refusée au même titre.
30
+ *
31
+ * @param provider - source d'identité (`UserService` via le container).
32
+ * @param identifier - identifiant fonctionnel stocké en session.
33
+ * @param boundAccountId - identifiant immuable inscrit à l'ouverture
34
+ * (`metaBag`, clé `ACCOUNT_ID_KEY`).
35
+ * @param boundCredentialsVersion - version des identifiants inscrite à
36
+ * l'ouverture (`metaBag`, clé `CREDENTIALS_VERSION_KEY`).
37
+ * @returns l'utilisateur actif, titulaire de la session.
38
+ * @throws AuthenticationError (401, message uniforme) — identifiant inconnu,
39
+ * compte verrouillé ou désactivé, session ouverte pour un autre compte ou
40
+ * avant le dernier changement de mot de passe.
41
+ */
42
+ export declare function resolveBoundSessionIdentity(provider: IUserProvider, identifier: string, boundAccountId: unknown, boundCredentialsVersion: unknown): Promise<IUser>;
@@ -1,4 +1,4 @@
1
- import type { JSONWebKeySet } from "jose";
1
+ import type { JSONWebKeySet, JWK } from "jose";
2
2
  import type { IJwtKeystore, IJwtSigningKey } from "../../contracts/IJwtKeystore.js";
3
3
  /** Journalisation injectée (le keystore n'est pas un Service — il reçoit un log). */
4
4
  type LogFn = (message: string, severity: string) => void;
@@ -29,6 +29,43 @@ interface KeystoreSource {
29
29
  * @returns le message à journaliser, ou `null` quand le dossier est nettoyé.
30
30
  */
31
31
  export declare function keystoreLeaksIntoImage(dir: string): string | null;
32
+ /** Clé telle que persistée : JWK privé (avec `d`) + métadonnées. */
33
+ export interface IStoredKey extends JWK {
34
+ kid: string;
35
+ alg: string;
36
+ use?: string;
37
+ createdAt?: number;
38
+ }
39
+ /** Forme du fichier `keyset.json` / de la variable d'env `keySetJson`. */
40
+ export interface IStoredKeySet {
41
+ /** `kid` de la clé qui signe (les autres ne servent qu'à vérifier). */
42
+ active: string;
43
+ keys: IStoredKey[];
44
+ }
45
+ /**
46
+ * Lit un jeu de clés sérialisé et en vérifie la FORME — la seule lecture du
47
+ * dépôt, appelée par le keystore ET par la validation de configuration.
48
+ *
49
+ * Ne vérifie que la structure (`keys` non vide, un `kid` par clé) : importer
50
+ * les clés exige `jose`, chargé paresseusement au premier jeton. Les messages
51
+ * ne recopient JAMAIS la valeur reçue — elle porte une clé privée.
52
+ *
53
+ * @param json - le jeu de clés, tel que `NF_JWT_KEYSET` ou `keyset.json` le porte.
54
+ * @returns le jeu de clés lu.
55
+ * @throws Error si le JSON est illisible ou la structure incomplète.
56
+ */
57
+ export declare function parseKeySet(json: string): IStoredKeySet;
58
+ /**
59
+ * Génère un jeu de clés de signature à partager entre tous les process d'une
60
+ * application — la valeur de `NF_JWT_KEYSET`.
61
+ *
62
+ * Même générateur que celui du keystore (une seule implémentation), sérialisé
63
+ * en JSON COMPACT : une ligne, sans quote simple, qu'un `.env` porte entre
64
+ * quotes simples et qu'un gestionnaire de secrets stocke tel quel.
65
+ *
66
+ * @returns le jeu de clés sérialisé — il contient la clé PRIVÉE.
67
+ */
68
+ export declare function generateKeySet(): Promise<string>;
32
69
  /**
33
70
  * Keystore Ed25519 — implémentation de référence d'{@link IJwtKeystore}.
34
71
  *
@@ -49,10 +86,15 @@ export declare function keystoreLeaksIntoImage(dir: string): string | null;
49
86
  * usage ; le boot ne paie rien si le JWT n'est jamais sollicité. Le chargement
50
87
  * est mémoïsé (une seule résolution concurrente).
51
88
  *
52
- * @remarks Race au 1ᵉʳ boot d'un **cluster** sans clé pré-provisionnée : deux
53
- * workers peuvent générer puis écrire des clés différentes (le dernier `rename`
54
- * gagne). En prod, provisionner la clé hors-bande (`keySetJson`/SecretProvider
55
- * P16) élimine ce cas — c'est précisément la source recommandée.
89
+ * @remarks Plusieurs process qui servent la MÊME application doivent signer
90
+ * avec la même clé, sinon un jeton signé par l'un est refusé par l'autre :
91
+ * - **pods** (aucun disque partagé) → source `env` obligatoire, la même valeur
92
+ * injectée partout (`NF_JWT_KEYSET`, générée par `security:secrets`) ;
93
+ * - **workers de `nodefony cluster`** → la source `env` est héritée au fork ; à
94
+ * défaut, la source `fichier` est sûre : la création de `keyset.json` est
95
+ * EXCLUSIVE ({@link createSecretExclusive}), un seul worker la génère et les
96
+ * autres relisent la sienne ;
97
+ * - source `mémoire` → une clé par process, jamais cohérente.
56
98
  */
57
99
  export declare class JwtKeystore implements IJwtKeystore {
58
100
  #private;
@@ -21,6 +21,13 @@ export interface ITrustedIssuer {
21
21
  * découvert.
22
22
  */
23
23
  jwksUri?: string | undefined;
24
+ /**
25
+ * Adresse où lire les métadonnées quand elle diffère de l'émetteur (service
26
+ * interne d'un cluster). Le document doit toujours se déclarer de `issuer` ;
27
+ * le `jwks_uri` peut alors être en `http` sur l'origine exacte de cette
28
+ * adresse (`isBackChannelTransportAllowed`, cœur).
29
+ */
30
+ discoveryUrl?: string | undefined;
24
31
  /**
25
32
  * Jeu de clés fourni LOCALEMENT — aucune requête, aucune découverte.
26
33
  *
@@ -123,8 +130,8 @@ export declare class RemoteJwtVerifier {
123
130
  #private;
124
131
  /**
125
132
  * @param options - émetteurs de confiance et réglages réseau
126
- * @throws Error si un émetteur est invalide, dupliqué, ou déclare un
127
- * algorithme à secret partagé
133
+ * @throws Error si un émetteur est invalide, dupliqué, déclare un
134
+ * algorithme à secret partagé ou une `discoveryUrl` invalide
128
135
  */
129
136
  constructor(options: IRemoteJwtVerifierOptions);
130
137
  /** Nombre d'émetteurs de confiance — pour l'introspection et les journaux. */
@@ -145,5 +152,25 @@ export declare class RemoteJwtVerifier {
145
152
  * « jeton invalide »
146
153
  */
147
154
  verify(token: string, audience: string): Promise<IAccessPrincipal | null>;
155
+ /**
156
+ * Vérifie un jeton et rend ses claims BRUTS — pour un jeton qui n'est pas un
157
+ * jeton d'accès (jeton de déconnexion OpenID Connect, par exemple), dont le
158
+ * sens se lit dans des claims que {@link verify} ne remonte pas.
159
+ *
160
+ * Mêmes garanties et même contrat d'erreur que {@link verify} : signature,
161
+ * émetteur de la liste fermée, algorithme imposé, audience obligatoire,
162
+ * claims exigés par l'émetteur ; un refus est un `null`, une panne lève.
163
+ *
164
+ * @param token - le jeton brut, tel que présenté
165
+ * @param audience - audience que le jeton DOIT porter dans `aud`
166
+ * @returns l'émetteur canonique et les claims vérifiés, ou `null` si le jeton
167
+ * est refusé
168
+ * @throws Error si l'émetteur ne peut pas être joint ou publie un jeu de clés
169
+ * inutilisable
170
+ */
171
+ verifyClaims(token: string, audience: string): Promise<{
172
+ issuer: string;
173
+ payload: Jose.JWTPayload;
174
+ } | null>;
148
175
  }
149
176
  export default RemoteJwtVerifier;
@@ -0,0 +1,77 @@
1
+ import type { RoleHierarchyWalker } from "../RoleHierarchyWalker.js";
2
+ /**
3
+ * Qui peut OBTENIR un scope d'application : clé = un scope exact ou une
4
+ * famille `préfixe:*`, valeur = les rôles dont UN suffit.
5
+ */
6
+ export type IScopeGrants = Readonly<Record<string, readonly string[]>>;
7
+ /**
8
+ * Un scope demandé et refusé, avec la règle qui l'a refusé.
9
+ *
10
+ * `admin` = réservé au plan d'administration du framework
11
+ * ({@link refusedAdminScopes}) ; `application` = déclaré dans
12
+ * `grantableScopes` pour des rôles que le porteur n'a pas.
13
+ */
14
+ export interface IScopeRefusal {
15
+ scope: string;
16
+ rule: "admin" | "application";
17
+ }
18
+ /**
19
+ * Clés que `grantableScopes` refuse : le plan d'administration est jugé par le
20
+ * framework, et une déclaration ici serait soit sans effet, soit un
21
+ * assouplissement — deux pièges que rien ne signalerait.
22
+ */
23
+ export declare const RESERVED_SCOPE_GRANT_KEYS: readonly string[];
24
+ /**
25
+ * Dit pourquoi une clé de `grantableScopes` est invalide, ou `null` si elle
26
+ * est acceptable.
27
+ *
28
+ * Une clé est un `scope-token` (RFC 6749 §3.3) ; un `*` n'y est admis qu'en
29
+ * dernière position, juste après `:` (une famille `préfixe:*`).
30
+ *
31
+ * @param key - la clé déclarée.
32
+ * @returns le motif du refus, ou `null`.
33
+ */
34
+ export declare function scopeGrantKeyProblem(key: string): string | null;
35
+ /**
36
+ * Politique d'attribution des scopes d'application, compilée une fois au boot.
37
+ *
38
+ * Résolution : une clé EXACTE l'emporte sur une famille, et la famille la plus
39
+ * longue sur une plus courte (`shop:admin:*` > `shop:*`). Le résultat ne dépend
40
+ * donc pas de l'ordre des clés. Un scope qu'aucune clé ne couvre reste accordé
41
+ * à tout porteur (comportement historique).
42
+ */
43
+ export declare class ScopeGrantPolicy {
44
+ #private;
45
+ /**
46
+ * @param grants - la section `grantableScopes`, déjà validée par le schéma.
47
+ */
48
+ constructor(grants?: IScopeGrants);
49
+ /** `true` si aucune clé n'est déclarée — tout scope d'application est accordé. */
50
+ get isEmpty(): boolean;
51
+ /**
52
+ * Les rôles dont UN suffit pour obtenir ce scope.
53
+ *
54
+ * @param scope - un scope demandé.
55
+ * @returns les rôles de la clé la plus spécifique, ou `null` si aucune clé ne
56
+ * le couvre (scope libre).
57
+ */
58
+ rolesFor(scope: string): readonly string[] | null;
59
+ }
60
+ /**
61
+ * Les scopes demandés que ce porteur ne peut pas obtenir, avec la règle qui
62
+ * refuse chacun.
63
+ *
64
+ * Seul juge des DEUX familles, appelé par chaque émetteur de l'application
65
+ * (jeton d'accès, clé API) : la règle d'administration du framework
66
+ * ({@link refusedAdminScopes}, jugée sur les rôles PLATS du compte, comme le
67
+ * plan d'administration) et la politique de l'application
68
+ * ({@link ScopeGrantPolicy}, jugée hiérarchie de rôles comprise, comme un
69
+ * `@IsGranted`).
70
+ *
71
+ * @param requested - scopes demandés (RFC 6749 §3.3).
72
+ * @param roles - rôles du porteur, tels que son compte les porte.
73
+ * @param policy - politique compilée de `grantableScopes`.
74
+ * @param hierarchy - hiérarchie de rôles ; absente, seuls les rôles plats comptent.
75
+ * @returns les refus, dans l'ordre de la demande — vide si tout est accordé.
76
+ */
77
+ export declare function refusedScopes(requested: readonly string[], roles: readonly string[], policy: ScopeGrantPolicy, hierarchy: RoleHierarchyWalker | null): IScopeRefusal[];
@@ -66,5 +66,27 @@ export declare function messageNonRestreint(file: string, mode: number): string;
66
66
  * un fichier EXISTANT conserve, sur certains systèmes, le mode de la cible.
67
67
  */
68
68
  export declare function writeSecret(file: string, content: string): Promise<void>;
69
+ /**
70
+ * Crée un secret SEULEMENT s'il n'existe pas encore : 0600, atomique, et un
71
+ * seul gagnant quand plusieurs process le tentent en même temps.
72
+ *
73
+ * ⭐ **Pourquoi pas {@link writeSecret}.** `rename` REMPLACE la cible : deux
74
+ * workers de `nodefony cluster` qui génèrent chacun une clé au premier boot
75
+ * l'écrivent tous les deux, le dernier gagne sur le disque — et l'autre garde
76
+ * en mémoire une clé que plus personne ne connaît. Ici, le temporaire est
77
+ * publié par un LIEN DUR, que le système refuse (`EEXIST`) si le nom existe :
78
+ * le perdant le sait, et relit la clé du gagnant. Le lecteur ne voit jamais un
79
+ * fichier à demi écrit, puisque le lien n'apparaît qu'une fois le contenu complet.
80
+ *
81
+ * ⚠️ Un système de fichiers sans liens durs (FAT, exFAT) retombe sur une
82
+ * création exclusive directe (`wx`) : toujours un seul gagnant, mais un lecteur
83
+ * concurrent peut lire le fichier pendant son écriture — il lève alors, au lieu
84
+ * de signer avec une clé divergente.
85
+ *
86
+ * @param file - chemin du secret.
87
+ * @param content - contenu à écrire.
88
+ * @returns `true` si ce process l'a créé, `false` s'il existait déjà.
89
+ */
90
+ export declare function createSecretExclusive(file: string, content: string): Promise<boolean>;
69
91
  /** Forme synchrone de {@link writeSecret}. */
70
92
  export declare function writeSecretSync(file: string, content: string): void;
@@ -0,0 +1,47 @@
1
+ /** Vue minimale du contexte : seul le scheme EFFECTIF compte. */
2
+ export interface ICredentialTransportContext {
3
+ /**
4
+ * Scheme vu par le client — `https` derrière un proxy de confiance qui
5
+ * termine TLS (`trustProxy` + `X-Forwarded-Proto`), jamais le transport brut.
6
+ */
7
+ readonly scheme?: string;
8
+ }
9
+ /** Réglages de la politique, résolus UNE fois au boot du firewall. */
10
+ export interface ICredentialTransportPolicyOptions {
11
+ /** `true` = un secret reçu hors TLS est refusé (production, sans échappement). */
12
+ readonly enforce: boolean;
13
+ /** Journal du premier refus (WARNING) — absent = silencieux. */
14
+ readonly log?: (message: string) => void;
15
+ }
16
+ /**
17
+ * Dit si un secret de connexion se refuse : en production, sans échappement
18
+ * explicite. Seule source de la décision — le firewall la compose au boot.
19
+ *
20
+ * @param environment - environnement normalisé du kernel.
21
+ * @param allowInsecureCredentials - échappement `security.allowInsecureCredentials`.
22
+ */
23
+ export declare function shouldEnforceCredentialTransport(environment: string | undefined, allowInsecureCredentials: boolean): boolean;
24
+ /**
25
+ * Refuse, avant toute vérification, un mot de passe ou un code de second
26
+ * facteur reçu en clair. Une instance partagée au conteneur (`credentialTransport`)
27
+ * sert TOUTES les portes qui reçoivent un secret : formulaire de session, second
28
+ * facteur, Basic, émission de jeton — une porte oubliée serait le canal clair.
29
+ *
30
+ * La décision se prend sur le scheme EFFECTIF (`context.scheme`) : derrière un
31
+ * proxy qui termine TLS et déclaré de confiance, il vaut `https` alors que le
32
+ * pod écoute en HTTP. C'est le cas de la plupart des déploiements ; c'est donc
33
+ * `trustProxy` que nomme le journal, pas « passez en HTTPS ».
34
+ */
35
+ export declare class CredentialTransportPolicy {
36
+ #private;
37
+ readonly enforce: boolean;
38
+ constructor(options: ICredentialTransportPolicyOptions);
39
+ /**
40
+ * Laisse passer un secret reçu sur TLS, ou n'importe lequel hors production.
41
+ *
42
+ * @param context - contexte de la requête qui porte le secret.
43
+ * @param door - porte qui le reçoit, nommée dans le journal.
44
+ * @throws InsecureTransportError (403) — secret reçu en clair en production.
45
+ */
46
+ assert(context: ICredentialTransportContext, door: string): void;
47
+ }