@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,138 @@
1
+ /**
2
+ * Le realm Keycloak d'une application — construit, jamais recopié.
3
+ *
4
+ * Deux usages, UNE construction : la contribution du paquet à une application
5
+ * neuve (`nodefony/keycloak/scaffold.ts`, jouée par `create app` après
6
+ * l'installation) écrit son realm d'import, et `security:keycloak:realm` le
7
+ * réécrit depuis la configuration EFFECTIVE d'une application qui tourne. Le
8
+ * cœur n'en sait rien : Keycloak est l'affaire de ce paquet.
9
+ *
10
+ * Avant lui, deux realms écrits à la main — celui du dépôt et le gabarit de
11
+ * l'application générée — répétaient ce que disait la configuration
12
+ * (identifiant de client, URL de retour, audience). Ils avaient divergé : le
13
+ * realm généré était né sans audience, et tout jeton y était refusé.
14
+ *
15
+ * Fonctions PURES : aucune lecture de disque, aucun environnement.
16
+ *
17
+ * @module
18
+ */
19
+ /** Une valeur JSON — ce qu'un realm d'import peut contenir. */
20
+ export type TKeycloakJson = string | number | boolean | null | TKeycloakJson[] | {
21
+ [key: string]: TKeycloakJson;
22
+ };
23
+ /** Un objet JSON du realm (client, mapper, utilisateur…). */
24
+ export type TKeycloakObject = {
25
+ [key: string]: TKeycloakJson;
26
+ };
27
+ /** Un rôle à déclarer dans le realm. */
28
+ export interface IKeycloakRoleInput {
29
+ /** Nom du rôle CÔTÉ KEYCLOAK (la clé de `roleMapping`). */
30
+ readonly name: string;
31
+ /** Texte affiché dans la console Keycloak. */
32
+ readonly description?: string;
33
+ }
34
+ /** Un compte de démonstration semé dans le realm de développement. */
35
+ export interface IKeycloakUserInput {
36
+ /** Identifiant FIXÉ : laissé à Keycloak, il change à chaque réimport. */
37
+ readonly id: string;
38
+ readonly username: string;
39
+ readonly email: string;
40
+ readonly firstName: string;
41
+ readonly lastName: string;
42
+ /** Mot de passe de DÉVELOPPEMENT, public par nature. */
43
+ readonly password: string;
44
+ /** Rôles du client navigateur accordés à ce compte. */
45
+ readonly clientRoles?: readonly string[];
46
+ }
47
+ /** Le client « machine » (compte de service, `client_credentials`). */
48
+ export interface IKeycloakMachineInput {
49
+ readonly clientId: string;
50
+ /** Secret de développement ; `null` = laissé à Keycloak (production). */
51
+ readonly secret: string | null;
52
+ readonly name?: string;
53
+ readonly description?: string;
54
+ }
55
+ /** Tout ce que le realm dit de l'application. */
56
+ export interface IKeycloakRealmInput {
57
+ /** Nom du realm — le dernier segment de l'émetteur (`…/realms/<nom>`). */
58
+ readonly realm: string;
59
+ /** Titre affiché sur la page de connexion. */
60
+ readonly displayName?: string;
61
+ /** Identifiant du client navigateur (`oauth2.providers.<p>.clientId`). */
62
+ readonly clientId: string;
63
+ /** Secret du client ; `null` = laissé à Keycloak (production). */
64
+ readonly clientSecret: string | null;
65
+ readonly clientName?: string;
66
+ readonly clientDescription?: string;
67
+ /** URL de retour EXACTES (RFC 9700 : comparaison au caractère près). */
68
+ readonly redirectUris: readonly string[];
69
+ /** Où Keycloak a le droit de renvoyer le navigateur après déconnexion. */
70
+ readonly postLogoutRedirectUris: readonly string[];
71
+ /** Point de déconnexion par canal arrière ; `null` = aucun. */
72
+ readonly backchannelLogoutUrl: string | null;
73
+ /**
74
+ * Ressources pour lesquelles Keycloak émet des jetons à cette application :
75
+ * un mapper d'audience par entrée, sur chaque client. Vide = aucun mapper,
76
+ * et le jeton porte alors `aud: "account"`, que toute zone refuse.
77
+ */
78
+ readonly audiences: readonly string[];
79
+ /** Rôles du client navigateur (`rolesSource: ["client"]`). */
80
+ readonly clientRoles: readonly IKeycloakRoleInput[];
81
+ /** Rôles du realm (`rolesSource: ["realm"]`). */
82
+ readonly realmRoles: readonly IKeycloakRoleInput[];
83
+ /** Client machine ; `null` = aucun. */
84
+ readonly machine: IKeycloakMachineInput | null;
85
+ /** Thèmes à déclarer ; absent = ceux de Keycloak. */
86
+ readonly themes?: {
87
+ readonly login?: string;
88
+ readonly account?: string;
89
+ readonly email?: string;
90
+ readonly admin?: string;
91
+ };
92
+ /** Comptes de démonstration (développement seulement). */
93
+ readonly users?: readonly IKeycloakUserInput[];
94
+ }
95
+ /**
96
+ * Le rôle composite que Keycloak donne à tout compte CRÉÉ dans le realm —
97
+ * `manage-account`, `view-profile`, `offline_access`, `uma_authorization`.
98
+ *
99
+ * ⚠️ Un compte IMPORTÉ ne le reçoit pas : l'import n'accorde que les rôles
100
+ * qu'il liste. Sans lui, la console du compte rend 401 sur le profil même de
101
+ * l'utilisateur.
102
+ *
103
+ * @param realm - nom du realm
104
+ * @returns le nom du rôle par défaut
105
+ */
106
+ export declare function keycloakDefaultRole(realm: string): string;
107
+ /**
108
+ * Construit le realm d'import complet d'une application.
109
+ *
110
+ * @param input - ce que la configuration de l'application dit du realm
111
+ * @returns le realm, prêt à être sérialisé par {@link renderKeycloakRealm}
112
+ */
113
+ export declare function buildKeycloakRealm(input: IKeycloakRealmInput): TKeycloakObject;
114
+ /**
115
+ * Fond un realm dérivé de la config dans le realm d'import existant.
116
+ *
117
+ * Ce que la configuration DIT est réécrit : nom du realm, thèmes déclarés,
118
+ * client navigateur (flux, URL de retour, audience, déconnexion), client
119
+ * machine (flux, audience). Ce qu'un humain a écrit SURVIT : comptes,
120
+ * titre, noms et textes des clients, secret machine, rôles déjà déclarés,
121
+ * mappers qui ne sont pas d'audience, clients et clés inconnus. Régénérer
122
+ * ne détruit donc rien de ce que la console a ajouté.
123
+ *
124
+ * @param existing - le realm lu sur disque (JSON quelconque)
125
+ * @param derived - le realm construit par {@link buildKeycloakRealm}
126
+ * @returns le realm fusionné
127
+ * @throws Si `existing` n'est pas un objet JSON
128
+ */
129
+ export declare function mergeKeycloakRealm(existing: TKeycloakJson, derived: TKeycloakObject): TKeycloakObject;
130
+ /**
131
+ * Sérialise un realm comme le dépôt l'écrit : deux espaces, tableaux de
132
+ * valeurs simples sur une ligne quand ils tiennent en 80 colonnes, fin de
133
+ * ligne — la forme que prettier donne au fichier versionné.
134
+ *
135
+ * @param realm - le realm à écrire
136
+ * @returns le texte du fichier d'import
137
+ */
138
+ export declare function renderKeycloakRealm(realm: TKeycloakObject): string;
@@ -0,0 +1,92 @@
1
+ import type { IKeycloakMachineInput, IKeycloakRealmInput } from "./keycloakRealm.js";
2
+ import { type LoginPageSkin } from "nodefony";
3
+ import type { ISecurityConfig } from "../config/config.js";
4
+ /**
5
+ * Les ports de l'application, lus dans `servers` de sa configuration.
6
+ *
7
+ * L'HÔTE n'en fait pas partie : le domaine de la config est une adresse
8
+ * d'ÉCOUTE (`127.0.0.1`, `0.0.0.0`), pas celle que tape le navigateur. Celle-ci
9
+ * est l'hôte de `redirectUri`, que Keycloak compare au caractère près.
10
+ */
11
+ export interface IAppServers {
12
+ /** Port HTTP, `null` si le serveur en clair est coupé. */
13
+ readonly httpPort: number | null;
14
+ /** Port HTTPS, `null` si le serveur TLS est coupé. */
15
+ readonly httpsPort: number | null;
16
+ }
17
+ /** Ce qu'il faut savoir de l'application pour dériver son realm. */
18
+ export interface IKeycloakRealmContext {
19
+ /** Nom du fournisseur dans `oauth2.providers` (défaut `keycloak`). */
20
+ readonly providerName: string;
21
+ /** La configuration de sécurité VALIDÉE. */
22
+ readonly config: ISecurityConfig;
23
+ readonly servers: IAppServers;
24
+ /**
25
+ * `true` en production : aucun secret écrit (Keycloak le génère, l'exploitant
26
+ * le copie dans son gestionnaire), aucune adresse locale de développement.
27
+ */
28
+ readonly production: boolean;
29
+ /** Client machine à tenir à jour ; `null` = aucun. */
30
+ readonly machine: IKeycloakMachineInput | null;
31
+ /**
32
+ * Origine que Keycloak utilise pour JOINDRE l'application (canal arrière).
33
+ * Défaut développement : `http://host.docker.internal:<port http>` — le
34
+ * Keycloak du compose vit dans un conteneur, `localhost` y désigne le sien.
35
+ */
36
+ readonly backchannelOrigin?: string;
37
+ /**
38
+ * `loginTheme` du realm déjà écrit, `null` s'il n'en déclare pas (ou s'il
39
+ * n'existe pas encore). Seul un realm déjà habillé par Nodefony change
40
+ * d'habillage : un thème propre à l'application n'est jamais remplacé.
41
+ */
42
+ readonly currentLoginTheme?: string | null;
43
+ }
44
+ /** Une dérivation, et ce qu'elle a remarqué en chemin. */
45
+ export interface IKeycloakRealmDerivation {
46
+ readonly input: IKeycloakRealmInput;
47
+ /** Constats à DIRE à l'utilisateur (le realm reste écrit). */
48
+ readonly warnings: string[];
49
+ }
50
+ /** Erreur de dérivation : la config ne permet pas d'écrire un realm. */
51
+ export declare class KeycloakRealmError extends Error {
52
+ readonly name = "KeycloakRealmError";
53
+ }
54
+ /**
55
+ * Le nom du realm, lu dans l'émetteur (`https://kc.example/realms/<nom>`).
56
+ *
57
+ * @throws {KeycloakRealmError} Si l'émetteur n'a pas la forme Keycloak
58
+ */
59
+ export declare function realmNameFromIssuer(issuer: string | undefined): string;
60
+ /**
61
+ * Dérive, depuis la configuration EFFECTIVE de l'application, ce que son realm
62
+ * Keycloak doit dire.
63
+ *
64
+ * - **Client** : `clientId`, et son secret hors production.
65
+ * - **URL de retour** : `redirectUri` du fournisseur, plus — hors production —
66
+ * le même chemin sur chaque serveur local (http ET https), parce que
67
+ * l'application répond sur les deux et que Keycloak compare au caractère près.
68
+ * - **Audience** : `audiences` du fournisseur, sinon la `resource` de chaque
69
+ * zone `external-jwt`. Une audience qu'aucune zone ne porte est DITE : le
70
+ * jeton serait refusé partout.
71
+ * - **Rôles** : les clés de `roleMapping`, rangées selon `rolesSource`.
72
+ *
73
+ * @param ctx - la configuration et le décor de l'application
74
+ * @returns l'entrée du constructeur de realm et les constats
75
+ * @throws {KeycloakRealmError} Si le fournisseur est absent ou sans émetteur
76
+ */
77
+ /**
78
+ * Le thème Keycloak de connexion qui porte un habillage de la page `/login`.
79
+ *
80
+ * @param skin - habillage (`loginPage.skin`)
81
+ * @returns `nodefony` pour l'habillage par défaut, `nodefony-<habillage>` sinon
82
+ * (thème enfant livré sous `keycloak/themes/`)
83
+ */
84
+ export declare function keycloakLoginTheme(skin: LoginPageSkin): string;
85
+ /**
86
+ * Dit si un thème Keycloak est l'un de ceux que livre Nodefony.
87
+ *
88
+ * @param theme - nom du thème
89
+ * @returns `true` pour `nodefony` et `nodefony-<habillage>`
90
+ */
91
+ export declare function isNodefonyKeycloakTheme(theme: string): boolean;
92
+ export declare function deriveKeycloakRealmInput(ctx: IKeycloakRealmContext): IKeycloakRealmDerivation;
@@ -0,0 +1,98 @@
1
+ import type { IAppContributionContext } from "nodefony";
2
+ import { type IKeycloakRealmInput } from "./keycloakRealm.js";
3
+ /** Le nom du thème, tel que le realm le déclare et que Keycloak le monte. */
4
+ export declare const KEYCLOAK_THEME_NAME = "nodefony";
5
+ /** Où l'application reçoit le décor Keycloak (chemins qui voyagent : `/`). */
6
+ export declare const APP_KEYCLOAK_REALM_FILE = "docker/keycloak/import/realm.json";
7
+ export declare const APP_KEYCLOAK_THEMES_DIR = "docker/keycloak/themes";
8
+ export declare const APP_KEYCLOAK_THEME_DIR = "docker/keycloak/themes/nodefony";
9
+ /**
10
+ * Ressource de l'API partenaire d'une application neuve en développement :
11
+ * ce que rend `partnerResource()` du gabarit `complete` sans `NF_JWT_ISSUER`
12
+ * ni `NF_PORT_HTTPS` — le port HTTPS par défaut, comme les URL de retour du
13
+ * realm. Deux paquets, une valeur : le test de contribution compare le
14
+ * gabarit rendu à cette constante.
15
+ */
16
+ export declare const APP_PARTNER_DEV_RESOURCE = "https://localhost:5152/api/partner";
17
+ /** Les types de thème que le realm sait déclarer, et la clé qui les porte. */
18
+ declare const THEME_TYPES: readonly ["login", "account", "email", "admin"];
19
+ type TThemeType = (typeof THEME_TYPES)[number];
20
+ /**
21
+ * Le dossier du thème Keycloak livré par le paquet.
22
+ *
23
+ * @returns chemin natif absolu (`<paquet>/keycloak/themes/nodefony`)
24
+ */
25
+ export declare function keycloakThemeSource(): string;
26
+ /**
27
+ * Le dossier de TOUS les thèmes Keycloak livrés : `nodefony` et ses thèmes
28
+ * enfants `nodefony-<habillage>`, un par habillage de la page `/login`.
29
+ * Keycloak les monte côte à côte (`/opt/keycloak/themes/`).
30
+ *
31
+ * @returns chemin natif absolu (`<paquet>/keycloak/themes`)
32
+ */
33
+ export declare function keycloakThemesSource(): string;
34
+ /**
35
+ * Les types que le thème livré couvre — un dossier par type. Le realm ne
36
+ * déclare QUE ceux-là : déclarer un type absent ferait retomber Keycloak sur
37
+ * son thème par défaut, avec un avertissement à chaque page.
38
+ *
39
+ * @param themeDir - dossier du thème
40
+ * @returns les types présents, dans l'ordre de {@link THEME_TYPES}
41
+ */
42
+ export declare function deliveredThemeTypes(themeDir: string): TThemeType[];
43
+ /**
44
+ * Le secret du client navigateur dans le décor de développement d'une app.
45
+ *
46
+ * ⚠️ Même règle que le gabarit `.env` du cœur (`keycloak.clientSecret` dans
47
+ * `engine.ts`) : la frontière de paquets empêche de l'appeler, un test de ce
48
+ * paquet compare donc les deux sorties.
49
+ *
50
+ * @param appName - nom de l'application
51
+ * @returns le secret, PUBLIC par nature (écrit dans le realm versionné)
52
+ */
53
+ export declare function devClientSecret(appName: string): string;
54
+ /**
55
+ * Le secret de l'appelant machine (`<app>-machine`) dans le décor de
56
+ * développement d'une app.
57
+ *
58
+ * ⚠️ Même règle que le gabarit `.env` du cœur (`keycloak.machineSecret` dans
59
+ * `engine.ts`), comparée par le même test que {@link devClientSecret}.
60
+ *
61
+ * @param appName - nom de l'application
62
+ * @returns le secret, PUBLIC par nature (écrit dans le realm versionné)
63
+ */
64
+ export declare function devMachineSecret(appName: string): string;
65
+ /**
66
+ * Ce que dit la configuration rendue par le gabarit `security.ts` d'une app
67
+ * neuve : client `<app>`, URL de retour sur les deux ports de développement,
68
+ * rôle client `admin` (`roleMapping: { admin: "ROLE_ADMIN" }`), et les
69
+ * audiences des zones `external-jwt` que la configuration rendue déclare.
70
+ *
71
+ * @param appName - nom de l'application (= realm = identifiant de client)
72
+ * @param themes - types de thème à déclarer
73
+ * @param ids - `sub` des comptes de démonstration, FIXÉS dans le fichier :
74
+ * laissés à Keycloak, ils changent à chaque réimport et la connexion
75
+ * suivante est refusée (identifiant déjà lié à un autre `sub`)
76
+ * @param audiences - ressources que les jetons du realm doivent porter
77
+ * (mapper sur les deux clients) ; vide = aucune zone ne les accepte
78
+ * @returns l'entrée du constructeur de realm
79
+ */
80
+ export declare function appDevRealmInput(appName: string, themes: readonly TThemeType[], ids: {
81
+ readonly user: string;
82
+ readonly admin: string;
83
+ }, audiences?: readonly string[]): IKeycloakRealmInput;
84
+ /**
85
+ * Recopie les thèmes livrés dans l'application — `nodefony` et ses thèmes
86
+ * d'habillage —, fichier par fichier, sans jamais remplacer un fichier présent.
87
+ *
88
+ * @param context - contexte de contribution
89
+ */
90
+ export declare function copyKeycloakTheme(context: IAppContributionContext): void;
91
+ /**
92
+ * Pose le realm d'import et le thème Keycloak, si l'application monte un
93
+ * décor Keycloak.
94
+ *
95
+ * @param context - contexte fourni par le cœur (`runAppContributions`)
96
+ */
97
+ export declare function contributeKeycloak(context: IAppContributionContext): void;
98
+ export {};
@@ -0,0 +1,43 @@
1
+ import { CliKernel, Command } from "nodefony";
2
+ /**
3
+ * `nodefony security:keycloak:realm` — le realm Keycloak que la configuration
4
+ * de l'application implique.
5
+ *
6
+ * Le realm d'import répète ce que la config dit déjà : identifiant du client,
7
+ * URL de retour, audience de chaque zone, rôles de `roleMapping`. Écrit à la
8
+ * main, il diverge — et le symptôme est un 401 ou une URL de retour refusée,
9
+ * jamais un message qui nomme la cause. Cette commande le DÉRIVE, par le
10
+ * service `keycloak` — qui porte toutes les actions Keycloak ; elle n'en
11
+ * garde que l'affichage et les codes de sortie.
12
+ *
13
+ * Elle FUSIONNE dans le fichier existant au lieu de l'écraser : comptes de
14
+ * démonstration, titres, secret machine, mappers posés dans la console
15
+ * survivent (`mergeKeycloakRealm`, cœur — le même constructeur que
16
+ * `create app`).
17
+ *
18
+ * - sans option : affiche le realm fusionné ;
19
+ * - `--write` : l'écrit (le fichier par défaut est l'unique `.json` de
20
+ * `docker/keycloak/import/`) ;
21
+ * - `--check` : sort en 1 si le fichier n'est plus à jour — la garde de CI ;
22
+ * - `--apply` : pose sur le realm EXISTANT le thème de connexion qui suit
23
+ * `loginPage.skin`, par l'API d'administration (service `keycloak`) —
24
+ * développement seulement, compte `oauth2.providers.<nom>.admin` requis.
25
+ *
26
+ * ⚠️ Keycloak n'importe un realm qu'à sa CRÉATION : un realm déjà présent
27
+ * n'est pas relu. Après `--write`, le porter par un import PARTIEL (clients en
28
+ * « Overwrite » : comptes et seconds facteurs survivent) ; retirer le volume
29
+ * ne se fait que sur un realm qui ne porte rien à garder.
30
+ */
31
+ declare class SecurityKeycloakRealm extends Command {
32
+ #private;
33
+ constructor(cli: CliKernel);
34
+ generate(opts: {
35
+ write?: string | boolean;
36
+ check?: string | boolean;
37
+ apply?: boolean;
38
+ provider?: string;
39
+ machine?: string;
40
+ backchannelOrigin?: string;
41
+ }): Promise<this>;
42
+ }
43
+ export default SecurityKeycloakRealm;
@@ -0,0 +1,15 @@
1
+ import type { IAppContributionContext } from "nodefony";
2
+ /**
3
+ * Le point de contribution de `@nodefony/security` : ce que le paquet livre à
4
+ * une application, déclaré dans son `package.json` (`nodefony.contribute`) et
5
+ * joué par le cœur, qui n'en connaît rien.
6
+ *
7
+ * @module
8
+ */
9
+ /**
10
+ * La contribution du paquet — aujourd'hui le seul décor Keycloak de
11
+ * développement (`nodefony/keycloak/`).
12
+ *
13
+ * @param context - contexte fourni par le cœur (`runAppContributions`)
14
+ */
15
+ export declare function contribute(context: IAppContributionContext): void;
@@ -1,29 +1,50 @@
1
1
  import { Service, Module } from "nodefony";
2
- import { RemoteJwtVerifier } from "../src/token/RemoteJwtVerifier.js";
2
+ import type { IAccessTokenVerifier } from "nodefony";
3
3
  /**
4
- * Pose (ou non) le vérificateur de jetons d'accès TIERS dans le conteneur.
4
+ * Pose (ou non) le vérificateur de jetons d'accès dans le conteneur — celui
5
+ * qu'une ressource protégée (porte MCP, zone `external-jwt`) interroge.
5
6
  *
6
- * Ce service est une **décision de câblage**, pas de la cryptographie : toute la
7
- * mécanique vit dans {@link RemoteJwtVerifier}, et la doctrine de refus dans le
8
- * cœur (`nodefony/src/oauth/`). Ici, on lit la configuration et on tranche une
9
- * seule question — cette application accepte-t-elle des jetons émis ailleurs ?
7
+ * Ce service est une **décision de câblage**, pas de la cryptographie : la
8
+ * vérification d'un jeton TIERS vit dans {@link RemoteJwtVerifier}, celle d'un
9
+ * jeton de CETTE application dans {@link JwtAuthenticator}, et la doctrine de
10
+ * refus dans le cœur (`nodefony/src/oauth/`). Ici, on tranche deux questions :
11
+ * l'application signe-t-elle ses jetons, et en accepte-t-elle d'ailleurs ?
10
12
  *
11
- * **Aucun émetteur déclaré = rien n'est posé**, et c'est le comportement voulu :
12
- * une porte protégée qui ne trouve pas de vérificateur refuse de servir en le
13
- * disant (503 + CRITIC), là où un vérificateur présent mais vide refuserait
13
+ * ⭐ **Un jeton signé par cette application est TOUJOURS vérifiable**, dès que
14
+ * sa capacité JWT est active (`security.jwt.enabled`, le défaut) — sans rien
15
+ * déclarer dans `resourceServer.issuers`. Une application qui émet un jeton
16
+ * pour sa propre porte (`nodefony security:mcp:token --resource …`) doit pouvoir
17
+ * le lire : sans cette règle, la porte refusait un jeton parfaitement valide,
18
+ * et l'application était un serveur d'autorisation dont aucune ressource ne
19
+ * reconnaissait la signature. Rien de nouveau ne devient valide : l'audience
20
+ * reste exigée, et seules celles de `jwt.audiences` sont émises.
21
+ *
22
+ * 🔴 **Il est vérifié par les règles de l'authentificateur `jwt`, pas par son
23
+ * jeu de clés seul** : révocation ciblée et en masse, compte encore actif. Lu
24
+ * comme un jeton tiers, un jeton RÉVOQUÉ — ou celui d'un compte désactivé —
25
+ * ouvrait encore la porte jusqu'à son expiration.
26
+ *
27
+ * L'aiguillage se fait sur l'émetteur revendiqué (`peekIssuer`), comme dans le
28
+ * pare-feu : l'émetteur de l'application va à la vérification locale, tout
29
+ * autre à la liste fermée des émetteurs tiers. Un émetteur inconnu est refusé
30
+ * sans la moindre requête sortante.
31
+ *
32
+ * **Rien à vérifier = rien n'est posé** (capacité JWT coupée, aucun émetteur
33
+ * tiers) : une porte protégée qui ne trouve pas de vérificateur refuse alors de
34
+ * servir en le disant (503 + CRITIC), là où un vérificateur vide refuserait
14
35
  * chaque jeton un par un — même résultat pour l'appelant, diagnostic beaucoup
15
36
  * plus difficile pour l'exploitant.
16
37
  *
17
- * Le coût est nul quand la capacité n'est pas utilisée : rien n'est instancié,
18
- * aucune requête n'est faite au démarrage. La découverte des clés n'a lieu qu'au
19
- * PREMIER jeton réellement présenté, et une seule fois par émetteur.
38
+ * Le coût est nul tant qu'aucun jeton n'est présenté : aucune requête au
39
+ * démarrage, clés locales lues au premier jeton, découverte d'un émetteur tiers
40
+ * au premier jeton qui s'en réclame.
20
41
  */
21
42
  declare class AccessTokenVerifierService extends Service {
22
43
  #private;
23
44
  module: Module;
24
45
  constructor(module: Module);
25
- /** Le vérificateur, ou `null` si aucun émetteur n'est déclaré. */
26
- get verifier(): RemoteJwtVerifier | null;
46
+ /** Le vérificateur posé, ou `null` si rien n'est vérifiable. */
47
+ get verifier(): IAccessTokenVerifier | null;
27
48
  }
28
49
  export default AccessTokenVerifierService;
29
50
  export { AccessTokenVerifierService };
@@ -40,11 +40,22 @@ declare class ApiKeyService extends Service {
40
40
  * Émet une nouvelle clé API pour un porteur — renvoie sa vue publique **+ le
41
41
  * token en clair** (affiché une seule fois).
42
42
  *
43
+ * Un scope que le porteur ne peut pas OBTENIR — réservé par
44
+ * `grantableScopes` à des rôles qu'il n'a pas, ou `admin:*` sans le rôle
45
+ * d'administration — refuse la création entière : une clé émise avec moins
46
+ * que demandé surprendrait sans qu'aucune réponse ne le dise. Même juge que
47
+ * l'émission des jetons ({@link refusedScopes}).
48
+ *
49
+ * @param roles - rôles du porteur tels que son compte les porte ; omis =
50
+ * aucun rôle, donc tout scope réservé est refusé (fail-closed).
43
51
  * @throws ApiKeyError 400 — nom vide/trop long, scope hors catalogue, expiry invalide.
52
+ * @throws ApiKeyError 403 — scope réservé à des rôles que le porteur n'a pas.
44
53
  * @throws ApiKeyError 409 — plafond `maxPerSubject` atteint.
45
- * @throws ApiKeyError 503 — store indisponible.
54
+ * @throws ApiKeyError 403 — aucun compte sous cet identifiant (la clé est liée
55
+ * à l'`id` immuable du compte, lu dans l'annuaire).
56
+ * @throws ApiKeyError 503 — store ou annuaire `users` indisponible.
46
57
  */
47
- createForSubject(subjectId: string, subjectType: "user" | "service", opts: ICreateApiKeyOptions): Promise<IApiKeyCreated>;
58
+ createForSubject(subjectId: string, subjectType: "user" | "service", opts: ICreateApiKeyOptions, roles?: readonly string[]): Promise<IApiKeyCreated>;
48
59
  /** Liste les clés (PAT) d'un porteur — vue publique, sans secret, récentes d'abord. */
49
60
  listForSubject(subjectId: string): Promise<IApiKeyView[]>;
50
61
  /**
@@ -1,4 +1,5 @@
1
1
  import { Service, Module } from "nodefony";
2
+ import type { ILoginPageDescription } from "nodefony";
2
3
  import type { ContextType, ISession } from "@nodefony/http";
3
4
  /**
4
5
  * Projection PUBLIQUE de l'utilisateur — ce qui sort en JSON vers le client.
@@ -7,7 +8,7 @@ import type { ContextType, ISession } from "@nodefony/http";
7
8
  */
8
9
  export interface ISafeUser {
9
10
  id: string;
10
- username: string;
11
+ identifier: string;
11
12
  roles: string[];
12
13
  }
13
14
  /**
@@ -19,7 +20,7 @@ export type ILoginOutcome = {
19
20
  user: ISafeUser;
20
21
  } | {
21
22
  status: "mfa_required";
22
- methods: ["totp"];
23
+ methods: readonly string[];
23
24
  };
24
25
  /**
25
26
  * Flux de session BFF — login/logout/me côté serveur (P6 J3).
@@ -56,6 +57,8 @@ declare class AuthFlow extends Service {
56
57
  * @param identifier - identifiant saisi (body JSON, non typé à la frontière).
57
58
  * @param password - mot de passe saisi.
58
59
  * @returns `authenticated` (identité établie) ou `mfa_required` (2ᵉ facteur requis).
60
+ * @throws InsecureTransportError (403) — mot de passe reçu en clair en
61
+ * production (refusé AVANT le throttle et le verifier).
59
62
  * @throws ThrottledError (429 + `Retry-After`) — backoff actif.
60
63
  * @throws AuthenticationError (401, message uniforme) — credential absent ou
61
64
  * invalide.
@@ -86,6 +89,7 @@ declare class AuthFlow extends Service {
86
89
  * @param context - contexte HTTP (porte la session PENDING).
87
90
  * @param code - code présenté (TOTP ou code de récupération).
88
91
  * @returns la projection publique de l'utilisateur authentifié.
92
+ * @throws InsecureTransportError (403) — code reçu en clair en production.
89
93
  * @throws ThrottledError (429) — trop de tentatives.
90
94
  * @throws AuthenticationError (401, uniforme) — aucun défi en cours, code absent
91
95
  * ou invalide (la session N'est PAS ouverte ; le défi reste pour un retry).
@@ -110,14 +114,42 @@ declare class AuthFlow extends Service {
110
114
  * @returns `true` si une session a réellement été détruite.
111
115
  */
112
116
  logout(context: ContextType): Promise<boolean>;
117
+ /**
118
+ * Déconnexion COMPLÈTE : détruit la session locale et rend, si la session
119
+ * vient d'un fournisseur qui publie un point de déconnexion (Keycloak…),
120
+ * l'adresse où envoyer le navigateur pour fermer AUSSI sa session à lui —
121
+ * sans quoi le clic suivant sur « Continuer avec … » reconnecte sans mot de
122
+ * passe (OpenID Connect RP-Initiated Logout 1.0).
123
+ *
124
+ * @returns `destroyed` comme {@link logout} ; `logoutUrl` à suivre, ou `null`.
125
+ */
126
+ logoutWithProvider(context: ContextType): Promise<{
127
+ destroyed: boolean;
128
+ logoutUrl: string | null;
129
+ }>;
113
130
  /**
114
131
  * Identité portée par la session courante, re-résolue auprès du provider
115
132
  * (mêmes contrôles que le `SessionAuthenticator` — source unique).
116
133
  *
117
134
  * @returns la projection publique, ou `null` (pas de session, session
118
- * orpheline, compte verrouillé/désactivé) — le handler répond 401.
135
+ * orpheline, compte verrouillé/désactivé) — le handler répond `200`
136
+ * `{ user: null }`.
119
137
  */
120
138
  me(context: ContextType): Promise<ISafeUser | null>;
139
+ /**
140
+ * Décrit la page de connexion par défaut : ce qu'elle propose et comment
141
+ * elle s'habille, depuis `security.loginPage` et les fournisseurs
142
+ * opérationnels.
143
+ *
144
+ * Lue par le contrôleur de `@nodefony/framework`, qui n'importe pas ce
145
+ * paquet. Les fournisseurs sont relus à chaque appel : l'un d'eux peut
146
+ * tomber ou revenir pendant que le serveur tourne.
147
+ *
148
+ * @returns la description, ou `null` quand la page par défaut est
149
+ * désactivée (`loginPage.enabled: false`) ou la configuration invalide —
150
+ * le contrôleur répond alors 404.
151
+ */
152
+ describeLoginPage(): ILoginPageDescription | null;
121
153
  }
122
154
  export default AuthFlow;
123
155
  export { AuthFlow };
@@ -90,7 +90,7 @@ declare class Firewall extends Service implements IFirewall {
90
90
  isSecure(context: ContextType): boolean;
91
91
  /**
92
92
  * Pipeline complet de la zone : chaîne d'authenticators (selon `mode`) → ALS
93
- * → Zero Trust. Rejette (401, challenge RFC 7235 posé) ou résout.
93
+ * → Zero Trust. Rejette (401, challenge RFC 9110 §11 posé) ou résout.
94
94
  */
95
95
  handleSecurity(context: ContextType): Promise<ContextType>;
96
96
  /**
@@ -102,8 +102,13 @@ declare class Firewall extends Service implements IFirewall {
102
102
  * synchronizer token (`context.csrfToken`) — HttpContext pose ensuite le cookie
103
103
  * lisible `csrf-token`. Sinon, hot-path GET = retour immédiat (aucun en-tête lu).
104
104
  * - **Étape 1 (globale)** : sur une mutation, défense Fetch Metadata / Origin
105
- * (rejet cross-site même sur route publique). Skippée si `@CsrfExempt` (webhook,
106
- * auth par signature/clé) ou `bypassFirewall` (callbacks OAuth).
105
+ * (rejet cross-site même sur route publique). Skippée SEULEMENT par `@CsrfExempt`
106
+ * (webhook, auth par signature/clé). `bypassFirewall` ne l'en dispense PAS : il
107
+ * retire l'authentification, pas la provenance — les routes de connexion sont
108
+ * en bypass, et c'est précisément là qu'un formulaire tiers ouvrirait chez la
109
+ * victime une session sur le compte de l'attaquant (login CSRF, CWE-352). Un
110
+ * appel de serveur à serveur (canal arrière OIDC) ne porte ni `Sec-Fetch-Site`
111
+ * ni `Origin` : il passe toujours.
107
112
  * - **Étape 2 (opt-in)** : sur une mutation `@CsrfProtect`, exige EN PLUS le
108
113
  * synchronizer token (en-tête `x-csrf-token` ≡ cookie + HMAC valide).
109
114
  *
@@ -124,6 +129,15 @@ declare class Firewall extends Service implements IFirewall {
124
129
  * réponse), sinon `undefined` (la requête réelle suit le pipeline normal).
125
130
  */
126
131
  handleCors(context: ContextType): number | undefined;
132
+ /**
133
+ * Le revalidateur de l'identité de la requête courante, pour une connexion
134
+ * longue qui la gardera (flux SSE). Même règle, même store de jetons que les
135
+ * sockets WebSocket — un transport qui en aurait une autre serait celui
136
+ * qu'un attaquant prendrait pour survivre à sa révocation.
137
+ *
138
+ * @returns `null` pour un anonyme ; sinon la fonction qui re-valide.
139
+ */
140
+ currentRevalidator(): ((nowMs?: number) => Promise<boolean>) | null;
127
141
  /**
128
142
  * En-têtes de sécurité APPLICATIFS (P6 J5) — CSP, Referrer-Policy, isolation
129
143
  * cross-origin (COOP/COEP/CORP), Origin-Agent-Cluster, Permissions-Policy.