@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,115 @@
1
+ import { Service, Module } from "nodefony";
2
+ import type { LoginPageSkin } from "nodefony";
3
+ import type { ISecurityConfig } from "../config/config.js";
4
+ import { KeycloakAdminClient, type IKeycloakLoginThemeResult } from "../keycloak/keycloakAdmin.js";
5
+ /** Ce qu'on demande au realm dérivé. */
6
+ export interface IKeycloakRealmRequest {
7
+ /** Nom dans `oauth2.providers` (défaut `keycloak`). */
8
+ readonly provider?: string | undefined;
9
+ /** Fichier d'import, relatif à la racine de l'application ou absolu. */
10
+ readonly file?: string | undefined;
11
+ /** Client machine dont l'audience suit la config. */
12
+ readonly machine?: string | undefined;
13
+ /** Origine par laquelle Keycloak joint l'application (canal arrière). */
14
+ readonly backchannelOrigin?: string | undefined;
15
+ }
16
+ /** Le realm que la configuration implique, confronté au fichier d'import. */
17
+ export interface IKeycloakRealmPlan {
18
+ /** Fichier d'import visé (chemin natif absolu). */
19
+ readonly file: string;
20
+ /** Contenu actuel du fichier, `null` s'il n'existe pas. */
21
+ readonly current: string | null;
22
+ /** Le realm fusionné, tel qu'il serait écrit. */
23
+ readonly text: string;
24
+ /** Constats à DIRE (le realm reste écrivable). */
25
+ readonly warnings: readonly string[];
26
+ /** `true` si le fichier porte déjà exactement {@link text}. */
27
+ readonly upToDate: boolean;
28
+ }
29
+ /**
30
+ * Service `keycloak` — toutes les actions de Nodefony sur un serveur Keycloak,
31
+ * en un point du conteneur.
32
+ *
33
+ * - **le realm** que la configuration implique : dérivé, fusionné avec le
34
+ * fichier d'import existant, comparé, écrit ({@link realmPlan},
35
+ * {@link writeRealm}) ;
36
+ * - **l'API d'administration** d'un realm vivant ({@link admin}), et ce qui s'y
37
+ * fait : le thème de connexion qui suit `loginPage.skin`
38
+ * ({@link applyLoginTheme}).
39
+ *
40
+ * Les commandes `security:keycloak:*` et les routes de développement
41
+ * l'appellent ; elles ne gardent que l'affichage. Seule la contribution de
42
+ * scaffold (`create app`, `scaffold:sync`) appelle les fonctions pures en
43
+ * direct : elle tourne SANS démarrer l'application, donc sans conteneur.
44
+ *
45
+ * Rien n'est construit au démarrage : la configuration est lue à l'appel, et
46
+ * les clients d'administration (un par fournisseur, jeton gardé) au premier
47
+ * usage.
48
+ */
49
+ declare class KeycloakService extends Service {
50
+ #private;
51
+ module: Module;
52
+ constructor(module: Module);
53
+ /**
54
+ * La configuration de sécurité VALIDÉE, relue à chaque appel (chemin froid).
55
+ *
56
+ * @returns la configuration
57
+ */
58
+ config(): ISecurityConfig;
59
+ /**
60
+ * Le realm que la configuration implique, fusionné avec le fichier d'import.
61
+ *
62
+ * Le realm d'import répète ce que la config dit déjà (client, URL de retour,
63
+ * audiences, rôles de `roleMapping`) : écrit à la main, il diverge. Ce plan
64
+ * le DÉRIVE, puis le fusionne pour que ce qu'un humain a ajouté survive
65
+ * (comptes, titres, secret machine, mappers posés dans la console).
66
+ *
67
+ * @param request - fournisseur, fichier, client machine, canal arrière
68
+ * @returns le fichier, son contenu, le realm à écrire et les constats
69
+ * @throws {KeycloakRealmError} Fournisseur absent ou mal décrit, fichier qui
70
+ * n'est pas un realm
71
+ */
72
+ realmPlan(request?: IKeycloakRealmRequest): IKeycloakRealmPlan;
73
+ /**
74
+ * Écrit le realm d'un plan — rien si le fichier est déjà à jour.
75
+ *
76
+ * ⚠️ Keycloak n'importe un realm qu'à sa CRÉATION : un realm déjà présent ne
77
+ * relit pas ce fichier ({@link applyLoginTheme} pour le thème, import
78
+ * partiel dans la console pour le reste).
79
+ *
80
+ * @param plan - le rendu de {@link realmPlan}
81
+ * @returns `true` si le fichier a été écrit
82
+ */
83
+ writeRealm(plan: IKeycloakRealmPlan): boolean;
84
+ /**
85
+ * Le client de l'API d'administration d'un fournisseur Keycloak.
86
+ *
87
+ * @param providerName - nom dans `oauth2.providers` (défaut `keycloak`)
88
+ * @returns le client, construit au premier appel puis gardé (jeton réutilisé)
89
+ * @throws {KeycloakAdminError} Hors développement, fournisseur absent, sans
90
+ * émetteur ou sans compte d'administration
91
+ */
92
+ admin(providerName?: string): KeycloakAdminClient;
93
+ /**
94
+ * Le thème de connexion qu'un habillage demande à Keycloak.
95
+ *
96
+ * @param skin - habillage ; défaut : `loginPage.skin` de la configuration
97
+ * @returns `nodefony` ou `nodefony-<habillage>`
98
+ */
99
+ expectedLoginTheme(skin?: LoginPageSkin): string;
100
+ /**
101
+ * Pose sur le realm VIVANT le thème de connexion d'un habillage.
102
+ *
103
+ * @param options - fournisseur (défaut `keycloak`) et habillage (défaut :
104
+ * `loginPage.skin` ; un aperçu de `/login` passe le sien)
105
+ * @returns le thème d'avant, celui demandé, et ce qui a été fait
106
+ * @throws {KeycloakAdminError} Hors développement, compte absent ou refusé,
107
+ * Keycloak injoignable
108
+ */
109
+ applyLoginTheme(options?: {
110
+ provider?: string;
111
+ skin?: LoginPageSkin;
112
+ }): Promise<IKeycloakLoginThemeResult>;
113
+ }
114
+ export { KeycloakService };
115
+ export default KeycloakService;
@@ -1,4 +1,31 @@
1
1
  import { Service, Module } from "nodefony";
2
+ import { type IOAuthDiagnosis } from "../src/oauth/providerDiagnosis.js";
3
+ /**
4
+ * Ce que la session retient d'un login fédéré pour pouvoir fermer AUSSI la
5
+ * session du fournisseur (RP-Initiated Logout). Stocké côté serveur ; l'ID
6
+ * token ne traverse le navigateur qu'une fois, dans l'adresse de déconnexion
7
+ * (`id_token_hint`), la session locale déjà détruite.
8
+ *
9
+ * @remarks L'ID token est une exception assumée à « les jetons du fournisseur
10
+ * ne sont pas conservés » : il n'ouvre aucun accès (ni API, ni rafraîchissement),
11
+ * il désigne la session à fermer. Jetons d'accès et de rafraîchissement restent
12
+ * jetés.
13
+ */
14
+ export interface IFederatedLogoutHint {
15
+ /** Fournisseur qui a ouvert la session. */
16
+ readonly provider: string;
17
+ /** ID token reçu au login, rejoué en `id_token_hint`. */
18
+ readonly idToken: string;
19
+ /** Identifiant de session du fournisseur (claim `sid`), ou `null`. */
20
+ readonly sid: string | null;
21
+ }
22
+ /** Vue minimale d'une session — celle que porte le contexte. */
23
+ interface IHintSession {
24
+ get(key: string): unknown;
25
+ set(key: string, value: unknown): unknown;
26
+ /** Métadonnée énumérable — absente d'une session réduite (tests). */
27
+ setMetaBag?(key: string, value: unknown): unknown;
28
+ }
2
29
  /**
3
30
  * Libellé affichable d'un fournisseur, quand sa configuration n'en donne pas.
4
31
  *
@@ -20,6 +47,8 @@ export interface IOAuthDisplayProvider {
20
47
  readonly name: string;
21
48
  /** Libellé du bouton : celui de la config, sinon dérivé du nom. */
22
49
  readonly label: string;
50
+ /** Image du bouton (`icon` de la config) — absente = icône du framework. */
51
+ readonly icon?: string;
23
52
  }
24
53
  /** Données à porter en session entre `authorize` et `callback` (anti-replay). */
25
54
  export interface IOAuthAuthorization {
@@ -30,20 +59,60 @@ export interface IOAuthAuthorization {
30
59
  /** `code_verifier` PKCE à stocker en session, ou `null` (fournisseur sans PKCE). */
31
60
  readonly codeVerifier: string | null;
32
61
  }
62
+ /**
63
+ * Confronte l'émetteur configuré d'un fournisseur à ce que sa fabrique exige.
64
+ *
65
+ * Une URL qui ne peut pas être un émetteur est refusée QUEL QUE SOIT le
66
+ * fournisseur : aucune valeur mal formée n'est une configuration voulue. Une
67
+ * absence ne l'est que si la fabrique a déclaré l'émetteur requis.
68
+ *
69
+ * @param name - nom du fournisseur (`oauth2.providers.<name>`).
70
+ * @param issuer - émetteur lu dans sa configuration.
71
+ * @throws BootConfigurationError - émetteur requis absent, ou mal formé.
72
+ */
73
+ export declare function checkProviderIssuer(name: string, issuer: string | undefined): void;
74
+ /**
75
+ * Annonce qu'un annuaire accorde des rôles de PLATEFORME, à la sévérité de
76
+ * l'environnement — ou `null` quand il n'y a rien à dire.
77
+ *
78
+ * L'ouverture est ÉCRITE (`allowPlatformRoles`), donc voulue : l'annonce ne
79
+ * la conteste pas, elle la rappelle. Le risque — un annuaire compromis
80
+ * administre l'instance — n'est réel qu'en production : là, `WARNING` ;
81
+ * ailleurs le même fait est dit en `INFO`, sans sonner une alarme à chaque
82
+ * démarrage d'un décor de développement.
83
+ *
84
+ * @param name - nom du fournisseur (`oauth2.providers.<name>`).
85
+ * @param allowed - `allowPlatformRoles` tel que déclaré.
86
+ * @param granted - rôles de l'application que la table accorde.
87
+ * @param production - l'application tourne en production.
88
+ * @returns le message et sa sévérité, ou `null`.
89
+ */
90
+ export declare function platformRolesNotice(name: string, allowed: boolean | undefined, granted: readonly string[], production: boolean): {
91
+ message: string;
92
+ severity: "WARNING" | "INFO";
93
+ } | null;
33
94
  /**
34
95
  * **Social login OAuth 2.0** (P6 J9) — orchestrateur du flux *Authorization Code*.
35
96
  *
36
97
  * Posture OAuth 2.1 (RFC 9700) : Authorization Code uniquement (jamais implicit /
37
98
  * ROPC), **PKCE S256** quand le fournisseur le supporte (RFC 7636), **state**
38
- * anti-CSRF, **iss** anti-mix-up (RFC 9207) ; aucun jeton n'atteint le navigateur
39
- * (le login produit une **session BFF**, gérée hors de ce service par le
40
- * controller + `AuthFlow`).
99
+ * anti-CSRF, **iss** anti-mix-up (RFC 9207) ; aucun jeton d'accès n'atteint le
100
+ * navigateur (le login produit une **session BFF**, gérée hors de ce service par
101
+ * le controller + `AuthFlow`) — seul l'ID token y passe, une fois, à la
102
+ * déconnexion ({@link IFederatedLogoutHint}).
103
+ *
104
+ * Au boot (si `oauth2.enabled`), la configuration de chaque fournisseur est
105
+ * confrontée à ce que sa fabrique exige : un émetteur requis absent, ou qui n'est
106
+ * pas une URL d'émetteur valide, INTERROMPT le démarrage en nommant la clé — une
107
+ * faute d'écriture ne doit pas attendre le premier clic pour se montrer. Un nom
108
+ * inconnu du registre reste un WARNING.
41
109
  *
42
- * Les fournisseurs sont construits **au premier login** (cold path — jamais au boot
43
- * ni par requête) puis mémoïsés : c'est là que les points d'entrée d'un émetteur
44
- * OIDC sont découverts, une seule fois par processus. Au boot (si `oauth2.enabled`)
45
- * : seule la config est validée et les fournisseurs configurés sont confrontés au
46
- * registre (un nom inconnu = WARNING, pas fatal).
110
+ * Les fournisseurs sont construits une fois puis mémoïsés : c'est là que les
111
+ * points d'entrée d'un émetteur OIDC sont découverts. Un run qui sert (ports
112
+ * ouverts) les construit DÈS le boot, sans l'attendre : un émetteur injoignable
113
+ * n'est pas une faute de configuration, il se journalise et son bouton quitte
114
+ * l'écran de connexion jusqu'à ce qu'une nouvelle tentative réussisse. Un run
115
+ * console ne touche jamais le réseau.
47
116
  *
48
117
  * Le service ne touche **ni HTTP ni session** : il rend à l'appelant les éléments
49
118
  * (URL, state, verifier) que le controller persiste en session — testable sans
@@ -78,6 +147,9 @@ declare class OAuth2Service extends Service {
78
147
  /**
79
148
  * Redirections post-login (succès / échec) — lues par le controller.
80
149
  * Surcharge PAR FOURNISSEUR si fournie, sinon valeur globale, sinon défaut.
150
+ *
151
+ * @remarks L'échec mène par défaut à la page de connexion (`loginPage.path`) :
152
+ * déplacer la page déplace la redirection, les deux ne peuvent pas diverger.
81
153
  */
82
154
  getRedirects(provider?: string): {
83
155
  success: string;
@@ -87,9 +159,13 @@ declare class OAuth2Service extends Service {
87
159
  * Étape 1 — prépare l'URL d'autorisation + les éléments anti-replay à stocker
88
160
  * en session (`state`, et `code_verifier` si PKCE).
89
161
  *
162
+ * @param options.theme - thème rendu par l'application, transmis au seul
163
+ * fournisseur qui sait l'afficher (Keycloak) ; les autres l'ignorent
90
164
  * @throws AuthenticationError — fournisseur non configuré / inconnu du registre.
91
165
  */
92
- createAuthorization(provider: string): Promise<IOAuthAuthorization>;
166
+ createAuthorization(provider: string, options?: {
167
+ theme?: "light" | "dark";
168
+ }): Promise<IOAuthAuthorization>;
93
169
  /**
94
170
  * Étape 2 — valide la réponse, échange le `code`, lit le profil et provisionne
95
171
  * l'utilisateur local (Shadow User). Retourne l'identifiant à ouvrir en session.
@@ -100,7 +176,67 @@ declare class OAuth2Service extends Service {
100
176
  */
101
177
  exchangeAndProvision(provider: string, code: string, codeVerifier: string | null, returnedIss: string | null): Promise<{
102
178
  identifier: string;
179
+ logoutHint: IFederatedLogoutHint | null;
103
180
  }>;
181
+ /**
182
+ * Retient en session l'indice de déconnexion d'un login fédéré — à appeler
183
+ * APRÈS l'ouverture de la session authentifiée (l'ID de session est
184
+ * régénéré à l'ouverture).
185
+ *
186
+ * @param session - session authentifiée de la requête.
187
+ * @param hint - indice rendu par {@link exchangeAndProvision}, ou `null`.
188
+ */
189
+ rememberLogout(session: IHintSession, hint: IFederatedLogoutHint | null): void;
190
+ /**
191
+ * Adresse de déconnexion chez le fournisseur qui a ouvert la session, ou
192
+ * `null` : session locale, fournisseur sans point de déconnexion, ou
193
+ * fournisseur devenu indisponible. À lire AVANT de détruire la session.
194
+ *
195
+ * @param session - session courante, ou `null`.
196
+ * @returns l'URL vers laquelle envoyer le navigateur, ou `null`.
197
+ */
198
+ logoutUrlFor(session: IHintSession | null): Promise<string | null>;
199
+ /**
200
+ * Déconnexion demandée PAR le fournisseur sur le canal arrière (OpenID
201
+ * Connect Back-Channel Logout 1.0) : vérifie le jeton, refuse un rejeu, puis
202
+ * détruit les sessions locales qu'il désigne.
203
+ *
204
+ * Une session est désignée si elle a été ouverte par CE fournisseur (index
205
+ * fédéré posé au login par {@link rememberLogout}) et, selon le jeton : porte son `sid` ; ou,
206
+ * sans `sid`, appartient à l'utilisateur lié à son `sub`. Une session ouverte
207
+ * par mot de passe ou par un autre fournisseur n'est jamais touchée — le
208
+ * fournisseur ne ferme que ce qu'il a ouvert.
209
+ *
210
+ * @remarks Les connexions WebSocket portées par une session détruite se
211
+ * ferment au tick de revalidation du hub temps réel (code `4001`).
212
+ *
213
+ * @param provider - fournisseur désigné par l'adresse appelée.
214
+ * @param logoutToken - paramètre `logout_token` reçu, brut.
215
+ * @returns `unsupported` si le fournisseur ne sait pas émettre de jeton de
216
+ * déconnexion ; `refused` si le jeton est invalide ou rejoué ; sinon le
217
+ * nombre de sessions détruites (0 : déjà déconnecté, c'est un succès §2.7).
218
+ * @throws Error - vérification impossible (jeu de clés injoignable) ou
219
+ * sessions impossibles à parcourir : la déconnexion a ÉCHOUÉ.
220
+ */
221
+ backchannelLogout(provider: string, logoutToken: string): Promise<{
222
+ readonly outcome: "unsupported" | "refused";
223
+ } | {
224
+ readonly outcome: "done";
225
+ readonly destroyed: number;
226
+ }>;
227
+ /**
228
+ * Diagnostique les fournisseurs CONFIGURÉS sans connexion humaine : découverte
229
+ * et émetteur, URL de retour acceptée, secret accepté (cf
230
+ * `diagnoseOAuthProvider`).
231
+ *
232
+ * Le fournisseur est construit à NEUF, hors de la mémoïsation du login : un
233
+ * diagnostic doit voir le serveur tel qu'il est maintenant, et son échec ne
234
+ * doit pas retirer un bouton de l'écran de connexion.
235
+ *
236
+ * @param name - un seul fournisseur ; à défaut, tous ceux de la configuration.
237
+ * @returns un diagnostic par fournisseur ; vide si le social login est éteint.
238
+ */
239
+ diagnose(name?: string): Promise<IOAuthDiagnosis[]>;
104
240
  }
105
241
  export default OAuth2Service;
106
242
  export { OAuth2Service };
@@ -1,6 +1,7 @@
1
1
  import { Service, Module } from "nodefony";
2
2
  import type * as Jose from "jose";
3
3
  import type { IUser } from "@nodefony/user";
4
+ import type { ICredentialTransportContext } from "../src/transport/CredentialTransportPolicy.js";
4
5
  /**
5
6
  * Réponse d'émission de jetons — nommage RFC 6749 §5.1 (snake_case, JSON).
6
7
  * Le JWT part en `Authorization: Bearer`, JAMAIS en cookie ni en URL.
@@ -76,10 +77,15 @@ declare class TokenService extends Service {
76
77
  * Émet un couple access/refresh après vérification d'un credential
77
78
  * identifiant/mot de passe (grant M2M/CLI). Throttling NIST partagé si activé.
78
79
  *
80
+ * @param context - requête HTTP qui porte le credential. Présente, elle
81
+ * soumet l'appel au refus du clair en production ; absente (CLI, appel
82
+ * programmatique), il n'y a pas de transport à juger. Un endpoint HTTP DOIT
83
+ * la passer.
84
+ * @throws InsecureTransportError (403) — credential reçu en clair en production.
79
85
  * @throws ThrottledError (429) — backoff actif.
80
86
  * @throws AuthenticationError (401, message uniforme) — credential invalide.
81
87
  */
82
- issueForCredentials(identifier: unknown, password: unknown, requestedScopes?: string[], resource?: unknown): Promise<ITokenResponse>;
88
+ issueForCredentials(identifier: unknown, password: unknown, requestedScopes?: string[], resource?: unknown, context?: ICredentialTransportContext): Promise<ITokenResponse>;
83
89
  issueTokens(user: IUser, requestedScopes?: string[], resource?: unknown, accessTtlS?: number): Promise<ITokenResponse>;
84
90
  /**
85
91
  * Rotation d'un refresh token (RFC 9700 §4.14) : valide le refresh présenté,
@@ -27,12 +27,36 @@ declare class TotpService extends Service {
27
27
  isEnabled(): boolean;
28
28
  /** Démarre l'enrôlement (secret + QR affichés 1×). */
29
29
  beginEnrollment(userId: string, account: string): Promise<ITotpEnrollment>;
30
- /** Confirme l'enrôlement par un 1ᵉʳ code → active + codes de récupération clairs. */
31
- confirmEnrollment(userId: string, code: string): Promise<ITotpActivation>;
30
+ /**
31
+ * Confirme l'enrôlement par un 1ᵉʳ code → active + codes de récupération clairs.
32
+ *
33
+ * Activer un second facteur est un geste de SÉCURITÉ : il est audité ici
34
+ * (`user.totp_enabled`), avec la requête qui l'a porté — sans trace, un facteur
35
+ * posé à l'insu du titulaire ne se retrouve pas.
36
+ *
37
+ * @param userId - compte dont on active le 2FA (le porteur de la session).
38
+ * @param code - premier code de l'application d'authentification.
39
+ * @param context - contexte de la requête (IP, navigateur, identifiant de
40
+ * requête) recopié dans l'événement d'audit ; absent, l'événement part sans.
41
+ * @returns les codes de récupération, affichés une seule fois.
42
+ * @throws quand le code est faux, l'enrôlement absent ou déjà confirmé.
43
+ */
44
+ confirmEnrollment(userId: string, code: string, context?: unknown): Promise<ITotpActivation>;
32
45
  /** Vérifie un second facteur au login (code TOTP ou code de récupération). */
33
46
  verifyLogin(userId: string, code: string): Promise<ITotpLoginResult>;
34
- /** Désactive le 2FA d'un utilisateur. */
35
- disable(userId: string): Promise<void>;
47
+ /**
48
+ * Désactive le 2FA d'un utilisateur.
49
+ *
50
+ * Avec un `context`, c'est le TITULAIRE qui retire son facteur (porte
51
+ * libre-service) : le geste est audité ici (`user.totp_disabled`). Sans
52
+ * contexte, l'appelant porte lui-même l'audit — le plan d'administration le
53
+ * fait avec l'identité de l'administrateur (`viaAdmin`), qu'ici on ignore.
54
+ * Un seul événement par geste, jamais deux.
55
+ *
56
+ * @param userId - compte dont on retire le 2FA.
57
+ * @param context - contexte de la requête du titulaire (audit libre-service).
58
+ */
59
+ disable(userId: string, context?: unknown): Promise<void>;
36
60
  /** État 2FA d'un utilisateur (absent / pending / activé + codes restants). */
37
61
  status(userId: string): Promise<ITotpStatus>;
38
62
  /**
@@ -5,13 +5,35 @@ import type { IWebAuthnCredential } from "../contracts/IWebAuthnCredential.js";
5
5
  import type { IWebAuthnCredentialSummary, IWebAuthnListQuery } from "../contracts/IWebAuthnCredentialStore.js";
6
6
  /** Sujet d'une cérémonie d'enregistrement — l'utilisateur qui crée un passkey. */
7
7
  export interface IWebAuthnUser {
8
- /** Identifiant applicatif stable (sub / username) — devient le `userHandle`. */
8
+ /**
9
+ * Clé de propriétaire = `IUser.identifier` — range les credentials
10
+ * (`findByUser`), cf `IWebAuthnCredentialStore`. N'est PAS le `userHandle`.
11
+ */
9
12
  readonly id: string;
13
+ /**
14
+ * Source du `userHandle` WebAuthn : l'`IUser.id` (UUID) du compte. WebAuthn
15
+ * L3 (§4, « User Handle ») l'exige opaque, d'au plus 64 octets, et « MUST NOT
16
+ * contain personally identifying information » — l'identifiant de connexion,
17
+ * souvent un email, est donc exclu. Omis (ou trop long), le handle est le
18
+ * condensat SHA-256 de {@link IWebAuthnUser.id} : 32 octets, jamais l'identifiant
19
+ * en clair. Le handle n'est jamais relu par le serveur — l'authentification
20
+ * retrouve le compte par l'id du credential stocké.
21
+ */
22
+ readonly handle?: string;
10
23
  /** Nom de compte affiché par l'OS (email, identifiant…). */
11
24
  readonly name: string;
12
25
  /** Nom complet optionnel (UX de l'invite système). */
13
26
  readonly displayName?: string;
14
27
  }
28
+ /**
29
+ * Octets du `userHandle` d'une cérémonie d'enregistrement — opaques, bornés, sans
30
+ * donnée personnelle (cf {@link IWebAuthnUser.handle}).
31
+ *
32
+ * @param user - sujet de la cérémonie.
33
+ * @returns `handle` encodé s'il est fourni et tient en 64 octets, sinon le
34
+ * condensat SHA-256 de l'`id`.
35
+ */
36
+ export declare function userHandleBytes(user: IWebAuthnUser): Uint8Array<ArrayBuffer>;
15
37
  /** Résultat d'une authentification WebAuthn vérifiée. */
16
38
  export interface IWebAuthnAssertionResult {
17
39
  /** Le credential résolu (état déjà mis à jour : compteur, sauvegarde, usage). */
@@ -9,6 +9,7 @@ import type { ISecurityAreaConfig } from "../config/defineModuleConfig.js";
9
9
  * spécificité au boot.
10
10
  */
11
11
  export declare class SecuredArea implements ISecuredArea {
12
+ #private;
12
13
  readonly name: string;
13
14
  readonly pattern: RegExp;
14
15
  readonly security: boolean;
@@ -18,6 +19,9 @@ export declare class SecuredArea implements ISecuredArea {
18
19
  readonly host?: string | undefined;
19
20
  readonly realtime: boolean;
20
21
  readonly resource?: string | undefined;
22
+ readonly entryPoint?: string | undefined;
23
+ readonly subjectPolicy?: "require" | "ephemeral" | undefined;
24
+ readonly ephemeralRoles?: readonly string[] | undefined;
21
25
  /**
22
26
  * Rôles exigés par défaut dans la zone, ou `null` quand elle n'en exige
23
27
  * aucun. `null` plutôt qu'un tableau vide : le hot path teste une référence,
@@ -0,0 +1,82 @@
1
+ import type { IUser, IUserProvider } from "@nodefony/user";
2
+ /**
3
+ * Claim d'un jeton d'accès émis par l'application qui porte l'identifiant
4
+ * IMMUABLE du compte (`IUser.id`) — `sub` porte l'identifiant fonctionnel.
5
+ */
6
+ export declare const ACCOUNT_ID_CLAIM = "uid";
7
+ /**
8
+ * Clé, dans `metadata` d'un enregistrement de jeton (refresh, clé d'API) comme
9
+ * dans le `metaBag` d'une session, qui porte l'identifiant immuable du compte.
10
+ */
11
+ export declare const ACCOUNT_ID_KEY = "accountId";
12
+ /**
13
+ * Claim d'un jeton d'accès émis par l'application qui porte la version des
14
+ * identifiants du compte à l'émission (`IUser.credentialsVersion`).
15
+ */
16
+ export declare const CREDENTIALS_VERSION_CLAIM = "cv";
17
+ /**
18
+ * Clé, dans `metadata` d'un enregistrement de jeton comme dans le `metaBag`
19
+ * d'une session, qui porte la version des identifiants lue à l'émission.
20
+ */
21
+ export declare const CREDENTIALS_VERSION_KEY = "credentialsVersion";
22
+ /**
23
+ * La version des identifiants à inscrire dans un artefact qu'on émet pour ce
24
+ * compte — 0 quand le compte n'en porte pas encore.
25
+ *
26
+ * @param user - compte pour lequel l'artefact est émis.
27
+ * @returns la version courante, jamais `null`.
28
+ */
29
+ export declare function credentialsVersionOf(user: IUser): number;
30
+ /**
31
+ * Dit si un artefact d'accès (jeton, session, clé) appartient bien au compte
32
+ * qu'on vient de recharger par son identifiant fonctionnel, ET s'il a été émis
33
+ * depuis le dernier changement de mot de passe.
34
+ *
35
+ * Les artefacts sont rangés sous l'`identifier`, qu'un compte RECRÉÉ peut
36
+ * reprendre : supprimé hors du framework (`DELETE` SQL, restauration, import)
37
+ * puis revenu sous le même nom, l'identité se rechargerait et l'artefact de
38
+ * l'ancien titulaire ouvrirait le compte du nouveau. L'identifiant immuable
39
+ * lève l'ambiguïté sans dépendre d'aucun événement ni d'aucun stockage : deux
40
+ * comptes successifs portent deux `id` différents. SEULE règle de
41
+ * comparaison — chaque authentificateur l'appelle, aucun ne la recopie.
42
+ *
43
+ * Un artefact SANS liaison est refusé : il a été émis avant que la règle
44
+ * existe, et rien ne dit à qui il appartient.
45
+ *
46
+ * La version des identifiants fait de la coupure au changement de mot de passe
47
+ * un ÉTAT du compte, et non une suppression : si la cascade de révocation
48
+ * échoue, ou si une requête en vol sur un autre processus réécrit la session
49
+ * qu'on vient d'effacer, l'artefact porte toujours l'ancienne version, et il
50
+ * tombe ici. Un artefact émis avant cette règle n'en porte pas : il vaut 0,
51
+ * comme un compte qui n'a jamais changé de mot de passe — le déploiement ne
52
+ * déconnecte personne. Une valeur d'un autre type est refusée.
53
+ *
54
+ * ⚠️ Chemin chaud — appelée sur CHAQUE requête authentifiée : deux comparaisons
55
+ * de chaîne et d'entier, aucune allocation.
56
+ *
57
+ * @param user - compte rechargé par l'identifiant que porte l'artefact.
58
+ * @param boundAccountId - identifiant immuable inscrit dans l'artefact.
59
+ * @param boundCredentialsVersion - version des identifiants inscrite dans
60
+ * l'artefact ({@link CREDENTIALS_VERSION_KEY}), `undefined` si absente.
61
+ * Obligatoire dans la signature pour qu'aucune porte ne l'oublie.
62
+ * @returns `true` si l'artefact a été émis pour CE compte, à sa version courante.
63
+ */
64
+ export declare function isBoundToAccount(user: IUser, boundAccountId: unknown, boundCredentialsVersion: unknown): boolean;
65
+ /**
66
+ * Recharge le compte qu'un jeton d'accès de CETTE application désigne, ou
67
+ * `null` : sujet inconnu, compte inactif ou verrouillé, jeton émis pour un
68
+ * autre compte du même nom ou avant un changement de mot de passe.
69
+ *
70
+ * SEULE implémentation de la règle « ce jeton ouvre-t-il encore ce compte ? »
71
+ * pour un jeton maison : la zone `jwt` (`JwtAuthenticator`) et le résolveur de
72
+ * compte d'une ressource protégée l'appellent, aucun ne la recopie. Un refus
73
+ * est `null`, jamais une exception : chaque appelant le traduit dans son
74
+ * propre contrat (401 uniforme, outil en `isError`).
75
+ *
76
+ * @param provider - annuaire des comptes de l'application.
77
+ * @param identifier - identifiant fonctionnel porté par le jeton (`sub`).
78
+ * @param boundAccountId - identifiant immuable inscrit dans le jeton ({@link ACCOUNT_ID_CLAIM}).
79
+ * @param boundCredentialsVersion - version des identifiants inscrite ({@link CREDENTIALS_VERSION_CLAIM}).
80
+ * @returns le compte, ou `null` s'il n'est pas (ou plus) celui du jeton.
81
+ */
82
+ export declare function loadBoundAccount(provider: IUserProvider, identifier: string, boundAccountId: unknown, boundCredentialsVersion: unknown): Promise<IUser | null>;
@@ -1,31 +1,91 @@
1
- import type { Container } from "nodefony";
1
+ import type { Container, IBootNotice } from "nodefony";
2
2
  import { type IUserRevokedEvent } from "@nodefony/user";
3
+ /** Résolution d'un service par nom — tout ce que la purge demande au container. */
4
+ interface ServiceLookupLike {
5
+ get(name: string): unknown;
6
+ }
3
7
  /** Bus d'événements minimal (kernel) capable d'abonner un handler. */
4
8
  interface KernelListenerLike {
5
- on(event: string, handler: (...args: unknown[]) => void): unknown;
9
+ on(event: string, handler: (...args: unknown[]) => unknown): unknown;
6
10
  }
7
11
  /**
8
12
  * Cascade de révocation déclenchée par {@link USER_REVOKED_EVENT} : éjecte
9
13
  * **immédiatement** les artefacts d'accès du porteur — ses **sessions** (http,
10
14
  * `destroyByUser`) et ses **jetons/PAT** (`tokenStore.revokeAllForSubject`,
11
- * seuil `invalidBefore`). Best-effort par brique (une indispo n'empêche pas
12
- * l'autre) — l'accès était DÉJÀ neutralisé par le re-fetch des authenticators,
13
- * cette cascade est de la **propreté + défense en profondeur**, jamais l'unique
14
- * rempart. `tenantId` du payload est réservé (scoping non câblé en mono-tenant).
15
+ * seuil `invalidBefore`, qui couvre aussi les clés d'API). Best-effort par
16
+ * brique (une indispo n'empêche pas l'autre) — l'accès était DÉJÀ neutralisé par
17
+ * le re-fetch des authenticators. `tenantId` du payload est réservé (scoping non
18
+ * câblé en mono-tenant).
19
+ *
20
+ * **Suppression du compte** (`reason: "deleted"`) : retire AUSSI ses facteurs —
21
+ * secret TOTP (codes de récupération compris) et passkeys. Ils sont rangés sous
22
+ * l'`identifier`, qu'un compte recréé peut reprendre : laissés en place, le
23
+ * nouveau titulaire hériterait du second facteur de l'ancien. Un compte
24
+ * seulement désactivé ou verrouillé les GARDE — il les retrouve à sa
25
+ * réactivation.
15
26
  *
16
- * @param container - container du kernel (résolution lazy de `sessions`/`tokenStore`).
27
+ * @param container - container du kernel (résolution lazy de `sessions`, `tokenStore`, `totp`, `webauthn`).
17
28
  * @param event - charge utile de l'événement (porteur + raison).
18
29
  * @param now - horloge (epoch ms) injectable pour les tests.
19
30
  */
20
31
  export declare function cascadeUserRevocation(container: Container, event: IUserRevokedEvent, now?: number): Promise<void>;
21
32
  /**
22
- * Abonne la cascade au bus kernel. À appeler au `onKernelBoot` d'un module
23
- * bootable (ici `@nodefony/security`). **Extensible** : tout autre module
24
- * (webhooks…) peut s'abonner au MÊME `USER_REVOKED_EVENT` pour ses propres
25
- * artefacts, sans toucher à ce fichier.
33
+ * Purge, à la NAISSANCE d'un compte, tout ce qu'un ancien titulaire du même
34
+ * identifiant a laissé derrière lui : sessions, jetons et facteurs. Un compte
35
+ * qui vient de naître n'a encore rien émis — tout artefact déjà rangé sous son
36
+ * identifiant appartient donc à quelqu'un d'autre. C'est ce qui ferme le cas
37
+ * d'un compte supprimé HORS du framework (`DELETE` SQL, aucun événement), dont
38
+ * l'accès redeviendrait valide dès qu'un compte renaît sous son nom.
39
+ *
40
+ * Le seuil est tronqué à la SECONDE : l'`iat` d'un JWT l'est aussi (RFC 7519
41
+ * §2, NumericDate), et un seuil à la milliseconde rejetterait le jeton du
42
+ * nouveau titulaire émis dans la même seconde que sa création.
43
+ *
44
+ * @param container - container du kernel.
45
+ * @param identifier - `IUser.identifier` du compte créé.
46
+ * @param now - horloge (epoch ms) injectable pour les tests.
47
+ */
48
+ export declare function purgeInheritedAccess(container: ServiceLookupLike, identifier: string, now?: number): Promise<void>;
49
+ /**
50
+ * Retire les facteurs rangés sous un identifiant — secret TOTP (codes de
51
+ * récupération compris) et passkeys. SEULE implémentation du geste, appelée à
52
+ * la suppression d'un compte ET à la création d'un compte sous cet identifiant.
53
+ * Best-effort par brique : un TOTP indisponible n'empêche pas le retrait des
54
+ * passkeys.
55
+ *
56
+ * @param container - container du kernel (services `totp`, `webauthn`).
57
+ * @param identifier - `IUser.identifier` du compte.
58
+ */
59
+ export declare function removeAccountFactors(container: ServiceLookupLike, identifier: string): Promise<void>;
60
+ /**
61
+ * Abonne la cascade au bus kernel, UNE fois, au `onKernelBoot` de
62
+ * `@nodefony/security` — avant qu'aucun compte ne puisse être créé ou révoqué
63
+ * (les comptes semés au démarrage compris). Deux abonnements :
64
+ * - {@link USER_REVOKED_EVENT} → {@link cascadeUserRevocation} ;
65
+ * - {@link USER_CREATED_EVENT} → {@link purgeInheritedAccess} : sessions,
66
+ * jetons et facteurs HÉRITÉS. Un compte qui vient de naître n'a encore rien
67
+ * émis ni enrôlé, donc tout artefact déjà rangé sous son identifiant
68
+ * appartient à un ancien titulaire. Règle exacte, aucune date à comparer.
69
+ * ⚠️ Un import qui chargerait les facteurs AVANT les comptes les verrait
70
+ * retirés : importer les comptes d'abord.
71
+ *
72
+ * Les deux sont émis par `UserService` seul, et attendus : chaque handler rend
73
+ * sa promesse. **Extensible** : tout autre module (webhooks…) s'abonne aux
74
+ * MÊMES événements pour ses propres artefacts, sans toucher à ce fichier.
26
75
  *
27
76
  * @param kernel - bus d'événements (kernel) exposant `on`.
28
- * @param container - container capturé par le handler.
77
+ * @param container - container capturé par les handlers.
29
78
  */
30
79
  export declare function registerUserRevocationCascade(kernel: KernelListenerLike, container: Container): void;
80
+ /**
81
+ * Avis de démarrage quand le service `users` n'émet pas sur le bus du kernel —
82
+ * construit sans container (`new UserService(repo, encoder)`). Ses événements
83
+ * partent alors sur son propre bus, où personne n'écoute : supprimer un compte
84
+ * laisserait ses sessions, ses jetons et ses facteurs en place, SANS UN MOT.
85
+ *
86
+ * @param container - container du kernel.
87
+ * @param kernel - le kernel attendu comme bus des événements.
88
+ * @returns l'avis à déclarer, ou `null` si `users` est absent ou bien câblé.
89
+ */
90
+ export declare function usersEventBusNotice(container: ServiceLookupLike, kernel: object): IBootNotice | null;
31
91
  export {};