@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,688 @@
1
+ /*
2
+ * Thème de connexion Nodefony — l'écran Keycloak, jumeau de la page /login.
3
+ *
4
+ * Mise en page, panneau de marque (voile, courbe, arcs du logo) et TOUTES les
5
+ * couleurs viennent de `css/login.css`, copie conforme de `nodefony/login.css`
6
+ * (un test l'exige à l'octet). Cette feuille-ci ne fait que deux choses :
7
+ * 1. habiller les composants PatternFly que Keycloak génère (champs,
8
+ * boutons, cases) avec les variables `--nf-login-*` ;
9
+ * 2. mettre en forme le CONTENU propre à Keycloak (domaine, capacités du
10
+ * compte, retour à l'application).
11
+ *
12
+ * Une application change les couleurs par un thème enfant (`parent=nodefony`)
13
+ * qui ajoute sa feuille après celle-ci, et la mise en page par `nfLayout`.
14
+ */
15
+
16
+ /* Alias locaux : les règles PatternFly ci-dessous lisent `--nf-*`, la palette
17
+ vit dans `--nf-login-*`. Une seule source, des noms courts. */
18
+ .nf-kc {
19
+ --nf-heading: var(--nf-login-heading);
20
+ --nf-text: var(--nf-login-text);
21
+ --nf-muted: var(--nf-login-muted);
22
+ --nf-border: var(--nf-login-line-strong);
23
+ --nf-input: var(--nf-login-field);
24
+ --nf-secondary: var(--nf-login-surface);
25
+ --nf-secondary-hover: var(--nf-login-raised);
26
+ --nf-primary: var(--nf-login-brand);
27
+ --nf-primary-hover: var(--nf-login-brand-hover);
28
+ --nf-focus: var(--nf-login-accent);
29
+ --nf-radius: var(--nf-login-radius);
30
+
31
+ /* PatternFly reprend la police et les couleurs de la marque. Un LIEN est du
32
+ texte sur le fond, pas un aplat sous du blanc : il prend l'accent, lisible
33
+ dans les deux thèmes (le bleu de marque tombe à 2,70:1 sur fond sombre). */
34
+ --pf-v5-global--FontFamily--text: var(--nf-login-font);
35
+ --pf-v5-global--FontFamily--heading: var(--nf-login-font);
36
+ --pf-v5-global--link--Color: var(--nf-login-accent);
37
+ --pf-v5-global--link--Color--hover: var(--nf-login-heading);
38
+ --pf-v5-global--primary-color--100: var(--nf-login-brand);
39
+ --pf-v5-global--primary-color--200: var(--nf-login-brand-hover);
40
+ }
41
+
42
+ /* Le parent pose un fond « polygones » sur le body, ÉTIRÉ (`cover`) : il n'a
43
+ rien à faire ici. Les quatre propriétés se reposent, pas seulement l'image —
44
+ sans la taille, le motif d'un habillage (grille, points : une tuile de
45
+ quelques dizaines de pixels) était étiré à tout l'écran. */
46
+ .login-pf body.nf-kc {
47
+ background-color: var(--nf-login-bg);
48
+ background-image: var(--nf-login-backdrop);
49
+ background-size: var(--nf-login-backdrop-size);
50
+ background-position: var(--nf-login-backdrop-position);
51
+ background-repeat: repeat;
52
+ background-attachment: fixed;
53
+ }
54
+
55
+ /* ── Contenu du panneau de marque : le domaine et ce que le compte permet ── */
56
+
57
+ .nf-kc .nf-hero-brand .nf-logo {
58
+ height: 30px;
59
+ }
60
+
61
+ .nf-hero-body {
62
+ max-width: 470px;
63
+ }
64
+
65
+ /* Préfixés par `.nf-hero` : ce sont des <p>, et la règle `.nf-hero p` de
66
+ login.css (plus spécifique qu'une classe seule) les écraserait. */
67
+ .nf-hero .nf-hero-title {
68
+ max-width: none;
69
+ margin: 0 0 16px;
70
+ font-size: 44px;
71
+ font-weight: 650;
72
+ line-height: 1.05;
73
+ letter-spacing: -0.03em;
74
+ color: var(--nf-login-hero-fg);
75
+ }
76
+
77
+ .nf-hero .nf-hero-lead {
78
+ margin: 0 0 32px;
79
+ font-size: 18px;
80
+ line-height: 1.5;
81
+ color: var(--nf-login-hero-muted);
82
+ }
83
+
84
+ .nf-pill {
85
+ display: inline-block;
86
+ margin-bottom: 20px;
87
+ padding: 5px 12px;
88
+ border: 1px solid var(--nf-login-line-strong);
89
+ border-radius: 999px;
90
+ font: 600 11px/1.4 var(--nf-login-mono);
91
+ letter-spacing: 0.06em;
92
+ text-transform: uppercase;
93
+ color: var(--nf-login-hero-muted);
94
+ }
95
+
96
+ .nf-hero .nf-caps-title {
97
+ margin: 0 0 14px;
98
+ font: 600 11px/1.4 var(--nf-login-mono);
99
+ letter-spacing: 0.06em;
100
+ text-transform: uppercase;
101
+ color: var(--nf-login-hero-muted);
102
+ }
103
+
104
+ .nf-caps {
105
+ display: grid;
106
+ gap: 12px;
107
+ margin: 0;
108
+ padding: 0;
109
+ list-style: none;
110
+ }
111
+
112
+ .nf-caps li {
113
+ display: flex;
114
+ align-items: flex-start;
115
+ gap: 10px;
116
+ font-size: 15px;
117
+ line-height: 1.45;
118
+ }
119
+
120
+ .nf-kc .nf-caps .nf-icon {
121
+ flex: none;
122
+ width: 18px;
123
+ height: 18px;
124
+ margin-top: 2px;
125
+ color: var(--nf-login-ok);
126
+ }
127
+
128
+ .nf-hero-footer {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: space-between;
132
+ gap: 16px;
133
+ font: 600 11px/1.4 var(--nf-login-mono);
134
+ letter-spacing: 0.04em;
135
+ text-transform: uppercase;
136
+ color: var(--nf-login-hero-muted);
137
+ }
138
+
139
+ /* La hauteur se pose ICI, pas seulement dans l'attribut `height` du gabarit :
140
+ la feuille de PatternFly rend `height: auto` aux images, et le logo
141
+ s'afficherait alors à sa taille naturelle (256 px). */
142
+ .nf-kc .nf-logo {
143
+ display: block;
144
+ width: auto;
145
+ height: 30px;
146
+ }
147
+
148
+ .nf-kc .nf-icon {
149
+ width: 20px;
150
+ height: 20px;
151
+ stroke: currentColor;
152
+ }
153
+
154
+ /* ── Scène : la colonne du formulaire ───────────────────────────────────── */
155
+
156
+ .nf-kc .nf-stage {
157
+ gap: 28px;
158
+ }
159
+
160
+ .nf-kc .pf-v5-c-login {
161
+ min-height: auto;
162
+ padding: 0;
163
+ background: transparent;
164
+ }
165
+
166
+ .nf-kc .pf-v5-c-login__container {
167
+ grid-template-columns: min(400px, 100%);
168
+ grid-template-areas: "header" "main";
169
+ align-content: start;
170
+ row-gap: 0;
171
+ min-height: 0;
172
+ padding: 0;
173
+ }
174
+
175
+ /* `backdrop-filter` (même à 0 px) fait de la carte le bloc conteneur de ses
176
+ descendants positionnés : le sélecteur de langue s'y accrochait au lieu du
177
+ coin de la page. Le flou de carte n'a pas de sens sur cet écran. */
178
+ .nf-kc .nf-card {
179
+ backdrop-filter: none;
180
+ }
181
+
182
+ /* Mise en page « card » : le formulaire respire dans sa carte. */
183
+ .nf-kc[data-layout="card"] .pf-v5-c-login__container {
184
+ padding: 26px 28px;
185
+ }
186
+
187
+ /* Habillage `ledger` : sur la page /login, il dispose SA carte en deux
188
+ colonnes (titre et étapes | formulaire) et remonte le sous-titre au-dessus
189
+ du titre. La carte de Keycloak porte d'autres enfants : la grille y
190
+ écrasait le formulaire dans la colonne de 260 px. On garde ses couleurs et
191
+ sa mise en page nue, pas sa structure. */
192
+ .nf-kc[data-skin="ledger"] {
193
+ /* Une colonne : la largeur d'une carte ordinaire, pas celle des deux
194
+ volets de /login (dont le MINIMUM dépasserait ce maximum). */
195
+ --nf-login-card-min: 380px;
196
+ --nf-login-card-ratio: 34vw;
197
+ --nf-login-card-width: 460px;
198
+ }
199
+
200
+ .nf-kc[data-skin="ledger"] .nf-card {
201
+ grid-template-columns: 100%;
202
+ grid-template-rows: none;
203
+ }
204
+
205
+ .nf-kc[data-skin="ledger"] .nf-card h1,
206
+ .nf-kc[data-skin="ledger"] .nf-card .nf-sub {
207
+ order: 0;
208
+ }
209
+
210
+ /* Configuration du second facteur : consignes, code QR ET formulaire. Dans la
211
+ colonne de 400 px d'une connexion, la page dépassait 1 300 px de haut — un
212
+ écran de téléphone sur un bureau. Sur grand écran, la carte s'élargit en
213
+ deux colonnes : consignes et code QR à gauche, formulaire à droite ; le
214
+ reste (alerte, pied) occupe toute la largeur. Sur téléphone, rien ne change. */
215
+ @media (min-width: 960px) {
216
+ .nf-kc[data-page-id="login-login-config-totp"] {
217
+ /* Largeur FIXE de deux colonnes : minimum = maximum. */
218
+ --nf-login-card-min: 820px;
219
+ --nf-login-card-width: 820px;
220
+ }
221
+
222
+ .nf-kc[data-page-id="login-login-config-totp"] .pf-v5-c-login__container {
223
+ grid-template-columns: min(820px, 100%);
224
+ }
225
+
226
+ /* Le pied sous la carte suit sa largeur. */
227
+ .nf-kc[data-page-id="login-login-config-totp"] .nf-panel-footer {
228
+ width: min(820px, 100%);
229
+ }
230
+
231
+ .nf-kc[data-page-id="login-login-config-totp"] .pf-v5-c-login__main-body {
232
+ display: grid;
233
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
234
+ column-gap: 40px;
235
+ align-items: start;
236
+ }
237
+
238
+ .nf-kc[data-page-id="login-login-config-totp"]
239
+ .pf-v5-c-login__main-body
240
+ > :not(#kc-totp-settings, #kc-totp-settings-form) {
241
+ grid-column: 1 / -1;
242
+ }
243
+ }
244
+
245
+ /* Téléphone : la carte garde ses bords mais rend de la place, pour que le
246
+ bouton de validation reste dans l'écran sans défiler. */
247
+ @media (max-width: 520px) {
248
+ .nf-kc[data-layout="card"] .pf-v5-c-login__container {
249
+ padding: 20px 16px;
250
+ }
251
+ }
252
+
253
+ /* Logo compact et pied de périmètre SSO : seulement quand le panneau de marque
254
+ est absent — mises en page card et bare, et split sur petit écran. */
255
+ .nf-kc .pf-v5-c-login__header {
256
+ display: block;
257
+ /* PatternFly y pose 64 px de marge haute : un vide au-dessus du logo. */
258
+ margin: 0;
259
+ padding: 0 0 24px;
260
+ }
261
+
262
+ .nf-kc[data-layout="split"] .pf-v5-c-login__header,
263
+ .nf-kc[data-layout="split"] .nf-panel-footer {
264
+ display: none;
265
+ }
266
+
267
+ @media (max-width: 899px) {
268
+ .nf-kc[data-layout="split"] .pf-v5-c-login__header {
269
+ display: block;
270
+ }
271
+
272
+ .nf-kc[data-layout="split"] .nf-panel-footer {
273
+ display: grid;
274
+ }
275
+
276
+ /* Le haut est réservé au sélecteur de langue, posé dans le coin : sans
277
+ cette marge il chevauchait le bouton de retour. */
278
+ .nf-kc .nf-stage {
279
+ justify-content: flex-start;
280
+ padding-top: 72px;
281
+ }
282
+ }
283
+
284
+ /* `keycloak.v2` habille `#kc-header-wrapper` en capitales de 29 px, avec
285
+ `color: var(--pf-v5-global--Color--light-100) !important` — blanc sur blanc
286
+ en clair. Plutôt que surenchérir d'un `!important`, la variable qu'il lit
287
+ est redéfinie SUR l'élément ; l'identifiant donne la priorité au reste. */
288
+ .nf-kc #kc-header-wrapper.nf-compact-brand {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 10px;
292
+ font-size: 18px;
293
+ font-weight: 650;
294
+ letter-spacing: -0.01em;
295
+ line-height: 1.2;
296
+ text-transform: none;
297
+ --pf-v5-global--Color--light-100: var(--nf-heading);
298
+ }
299
+
300
+ .nf-kc .nf-compact-brand .nf-logo {
301
+ height: 26px;
302
+ }
303
+
304
+ .nf-kc .pf-v5-c-login__main {
305
+ background: transparent;
306
+ box-shadow: none;
307
+ }
308
+
309
+ .nf-kc .pf-v5-c-login__main-header {
310
+ display: block;
311
+ border-top: 0;
312
+ padding: 0 0 28px;
313
+ }
314
+
315
+ .nf-kc .pf-v5-c-login__main-body,
316
+ .nf-kc .pf-v5-c-login__main-footer {
317
+ padding-inline: 0;
318
+ }
319
+
320
+ .nf-kc #kc-page-title {
321
+ font-size: var(--nf-login-title-size);
322
+ font-weight: 650;
323
+ letter-spacing: -0.01em;
324
+ color: var(--nf-heading);
325
+ }
326
+
327
+ .nf-heading {
328
+ display: grid;
329
+ gap: 6px;
330
+ }
331
+
332
+ .nf-subtitle {
333
+ grid-column: 1 / -1;
334
+ margin: 0;
335
+ font-size: 14px;
336
+ line-height: 1.5;
337
+ color: var(--nf-muted);
338
+ }
339
+
340
+ /* Badge SSO : micro-libellé à chasse fixe, filet de l'accent. */
341
+ .nf-eyebrow {
342
+ display: inline-flex;
343
+ align-items: center;
344
+ gap: 6px;
345
+ justify-self: start;
346
+ margin-bottom: 10px;
347
+ padding: 4px 10px 4px 8px;
348
+ border: 1px solid color-mix(in srgb, var(--nf-focus) 45%, transparent);
349
+ border-radius: 999px;
350
+ color: var(--nf-focus);
351
+ font: 600 11px/1.4 var(--nf-login-mono);
352
+ letter-spacing: 0.04em;
353
+ text-transform: uppercase;
354
+ }
355
+
356
+ .nf-kc .nf-eyebrow .nf-icon {
357
+ width: 15px;
358
+ height: 15px;
359
+ }
360
+
361
+ /* ── Langue : repliée dans le coin, comme un réglage, pas comme un titre ── */
362
+
363
+ /* `absolute`, pas `fixed` : il défile avec la page au lieu de flotter
364
+ au-dessus du formulaire sur un petit écran. */
365
+ .nf-kc .pf-v5-c-login__main-header-utilities {
366
+ position: absolute;
367
+ top: 20px;
368
+ right: 24px;
369
+ z-index: 1;
370
+ }
371
+
372
+ .nf-kc .pf-v5-c-login__main-header-utilities .pf-v5-c-form-control {
373
+ min-width: 132px;
374
+ min-height: 34px;
375
+ font-size: 13px;
376
+ }
377
+
378
+ .nf-kc .pf-v5-c-login__main-header-utilities .pf-v5-c-form-control > select {
379
+ min-height: 32px;
380
+ padding-inline-start: 10px;
381
+ }
382
+
383
+ /* ── Formulaire : champs, boutons, cases ────────────────────────────────── */
384
+
385
+ .nf-kc .pf-v5-c-form {
386
+ gap: 18px;
387
+ }
388
+
389
+ .nf-kc .pf-v5-c-form__label-text {
390
+ font: var(--nf-login-label-font);
391
+ color: var(--nf-heading);
392
+ }
393
+
394
+ /* Champs : bordure pleine et coins arrondis. */
395
+ .nf-kc .pf-v5-c-form-control {
396
+ --pf-v5-c-form-control--BackgroundColor: var(--nf-input);
397
+ --pf-v5-c-form-control--Color: var(--nf-heading);
398
+ min-height: 44px;
399
+ border-radius: var(--nf-radius);
400
+ }
401
+
402
+ .nf-kc .pf-v5-c-form-control::before {
403
+ border: 1px solid var(--nf-border);
404
+ border-radius: var(--nf-radius);
405
+ }
406
+
407
+ .nf-kc .pf-v5-c-form-control > input,
408
+ .nf-kc .pf-v5-c-form-control > select {
409
+ min-height: 42px;
410
+ }
411
+
412
+ /* PatternFly trace son focus par un trait bas en ::after — la page /login, par
413
+ la bordure seule. Garder les deux donnait un double trait. */
414
+ .nf-kc .pf-v5-c-form-control::after {
415
+ display: none;
416
+ }
417
+
418
+ .nf-kc .pf-v5-c-form-control:focus-within::before {
419
+ border-color: var(--nf-focus);
420
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-focus) 28%, transparent);
421
+ }
422
+
423
+ /* Le focus se voit sur le CONTRÔLE (bordure + anneau) : le contour natif de
424
+ l'input s'y ajoutait en un second cadre, intérieur. */
425
+ .nf-kc .pf-v5-c-form-control > input:focus-visible {
426
+ outline: none;
427
+ }
428
+
429
+ .nf-kc .pf-v5-c-form-control.pf-m-error::before {
430
+ border-color: var(--nf-login-crit);
431
+ }
432
+
433
+ /* Champ mot de passe : le champ et le bouton « œil » forment UN contrôle.
434
+ C'est le GROUPE qui porte bordure, rayon, focus et erreur : bordés
435
+ séparément, un trait apparaissait entre les deux en sombre et pas en clair,
436
+ et l'anneau de focus coupait le contrôle en deux. */
437
+ .nf-kc .pf-v5-c-input-group:has(.pf-v5-c-button.pf-m-control) {
438
+ border: 1px solid var(--nf-border);
439
+ border-radius: var(--nf-radius);
440
+ background: var(--nf-input);
441
+ }
442
+
443
+ .nf-kc .pf-v5-c-input-group:has(.pf-v5-c-button.pf-m-control):focus-within {
444
+ border-color: var(--nf-focus);
445
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-focus) 28%, transparent);
446
+ }
447
+
448
+ .nf-kc .pf-v5-c-input-group:has(.pf-m-error) {
449
+ border-color: var(--nf-login-crit);
450
+ }
451
+
452
+ .nf-kc
453
+ .pf-v5-c-input-group:has(.pf-v5-c-button.pf-m-control)
454
+ .pf-v5-c-form-control {
455
+ min-height: 42px;
456
+ }
457
+
458
+ .nf-kc
459
+ .pf-v5-c-input-group:has(.pf-v5-c-button.pf-m-control)
460
+ .pf-v5-c-form-control::before,
461
+ .nf-kc
462
+ .pf-v5-c-input-group:has(.pf-v5-c-button.pf-m-control)
463
+ .pf-v5-c-form-control:focus-within::before {
464
+ border: 0;
465
+ box-shadow: none;
466
+ }
467
+
468
+ /* La feuille de `keycloak.v2` borde l'item du bouton à gauche : c'est le trait
469
+ qui coupait le contrôle en deux, visible en sombre seulement. */
470
+ .nf-kc
471
+ .pf-v5-c-input-group:has(.pf-v5-c-button.pf-m-control)
472
+ .pf-v5-c-input-group__item {
473
+ border: 0;
474
+ }
475
+
476
+ .nf-kc .pf-v5-c-input-group .pf-v5-c-button.pf-m-control {
477
+ min-height: 42px;
478
+ border: 0;
479
+ border-radius: 0 var(--nf-radius) var(--nf-radius) 0;
480
+ background: transparent;
481
+ color: var(--nf-muted);
482
+ }
483
+
484
+ .nf-kc .pf-v5-c-input-group .pf-v5-c-button.pf-m-control:hover {
485
+ color: var(--nf-heading);
486
+ }
487
+
488
+ .nf-kc .pf-v5-c-input-group .pf-v5-c-button.pf-m-control::after {
489
+ border: 0;
490
+ }
491
+
492
+ .nf-kc #username,
493
+ .nf-kc #password {
494
+ padding-inline-start: 40px;
495
+ background-repeat: no-repeat;
496
+ background-position: 12px center;
497
+ background-size: 18px 18px;
498
+ }
499
+
500
+ .nf-kc #username {
501
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a9099' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0'/%3E%3Cpath d='M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2'/%3E%3C/svg%3E");
502
+ }
503
+
504
+ .nf-kc #password {
505
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a9099' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6'/%3E%3Cpath d='M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0'/%3E%3Cpath d='M8 11v-4a4 4 0 1 1 8 0v4'/%3E%3C/svg%3E");
506
+ }
507
+
508
+ /* Boutons : plein bleu pour l'action, bordé pour les secondaires. */
509
+ .nf-kc .pf-v5-c-button.pf-m-primary {
510
+ min-height: 44px;
511
+ border-radius: var(--nf-radius);
512
+ background: var(--nf-primary);
513
+ color: var(--nf-login-on-brand);
514
+ font-size: 15px;
515
+ font-weight: 600;
516
+ }
517
+
518
+ .nf-kc .pf-v5-c-button.pf-m-primary:hover,
519
+ .nf-kc .pf-v5-c-button.pf-m-primary:focus-visible {
520
+ background: var(--nf-primary-hover);
521
+ }
522
+
523
+ .nf-kc .pf-v5-c-button.pf-m-secondary {
524
+ min-height: 44px;
525
+ border-radius: var(--nf-radius);
526
+ background: var(--nf-secondary);
527
+ color: var(--nf-heading);
528
+ }
529
+
530
+ .nf-kc .pf-v5-c-button.pf-m-secondary::after {
531
+ border: 1px solid var(--nf-border);
532
+ border-radius: var(--nf-radius);
533
+ }
534
+
535
+ .nf-kc .pf-v5-c-button.pf-m-secondary:hover {
536
+ background: var(--nf-secondary-hover);
537
+ }
538
+
539
+ .nf-kc .pf-v5-c-button:focus-visible {
540
+ outline: 2px solid var(--nf-focus);
541
+ outline-offset: 2px;
542
+ }
543
+
544
+ /* Bouton principal : la flèche de la page /login (« Connexion → »). */
545
+ .nf-kc #kc-login {
546
+ display: inline-flex;
547
+ align-items: center;
548
+ justify-content: center;
549
+ gap: 8px;
550
+ margin-top: 6px;
551
+ }
552
+
553
+ /* PatternFly dessine la bordure des boutons dans `::after`, en position
554
+ absolue : la flèche doit rendre la position au flux, sinon elle se colle
555
+ au coin haut-gauche. */
556
+ .nf-kc #kc-login::after {
557
+ content: "";
558
+ position: static;
559
+ inset: auto;
560
+ border: 0;
561
+ flex: none;
562
+ width: 18px;
563
+ height: 18px;
564
+ background: currentColor;
565
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l14 0'/%3E%3Cpath d='M13 18l6 -6'/%3E%3Cpath d='M13 6l6 6'/%3E%3C/svg%3E")
566
+ center / contain no-repeat;
567
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l14 0'/%3E%3Cpath d='M13 18l6 -6'/%3E%3Cpath d='M13 6l6 6'/%3E%3C/svg%3E")
568
+ center / contain no-repeat;
569
+ transition: transform 0.15s ease;
570
+ }
571
+
572
+ .nf-kc #kc-login:hover::after {
573
+ transform: translateX(3px);
574
+ }
575
+
576
+ /* « Se souvenir de moi » : case à la couleur de la marque. */
577
+ .nf-kc .pf-v5-c-check__input {
578
+ accent-color: var(--nf-primary);
579
+ width: 20px;
580
+ height: 20px;
581
+ }
582
+
583
+ .nf-kc .pf-v5-c-check__label {
584
+ font-size: 14px;
585
+ color: var(--nf-text);
586
+ }
587
+
588
+ /* ── Retour à l'application qui a demandé la connexion ──────────────────── */
589
+
590
+ .nf-back-form {
591
+ width: min(400px, 100%);
592
+ margin: 0;
593
+ }
594
+
595
+ .nf-back {
596
+ display: flex;
597
+ width: 100%;
598
+ font: inherit;
599
+ text-align: start;
600
+ cursor: pointer;
601
+ background: transparent;
602
+ align-items: center;
603
+ gap: 12px;
604
+ padding: 10px 12px;
605
+ border: 1px solid var(--nf-login-line);
606
+ border-radius: var(--nf-radius);
607
+ color: var(--nf-text);
608
+ text-decoration: none;
609
+ transition:
610
+ background-color 0.15s ease,
611
+ border-color 0.15s ease;
612
+ }
613
+
614
+ .nf-back:hover,
615
+ .nf-back:focus-visible {
616
+ border-color: var(--nf-focus);
617
+ background: var(--nf-secondary-hover);
618
+ color: var(--nf-heading);
619
+ }
620
+
621
+ .nf-back:focus-visible {
622
+ outline: 2px solid var(--nf-focus);
623
+ outline-offset: 2px;
624
+ }
625
+
626
+ .nf-kc .nf-back .nf-icon {
627
+ flex: none;
628
+ width: 18px;
629
+ height: 18px;
630
+ transition: transform 0.15s ease;
631
+ }
632
+
633
+ .nf-back:hover .nf-icon {
634
+ transform: translateX(-3px);
635
+ }
636
+
637
+ .nf-back strong {
638
+ display: block;
639
+ font-size: 14px;
640
+ font-weight: 600;
641
+ color: var(--nf-heading);
642
+ }
643
+
644
+ .nf-back small {
645
+ display: block;
646
+ font-size: 13px;
647
+ color: var(--nf-muted);
648
+ }
649
+
650
+ /* ── Pied du panneau : le périmètre SSO, quand le panneau de marque manque ── */
651
+
652
+ .nf-panel-footer {
653
+ display: grid;
654
+ gap: 10px;
655
+ width: min(400px, 100%);
656
+ padding-top: 20px;
657
+ border-top: 1px solid var(--nf-login-line);
658
+ font-size: 13px;
659
+ color: var(--nf-muted);
660
+ }
661
+
662
+ .nf-panel-footer > span {
663
+ display: inline-flex;
664
+ align-items: center;
665
+ gap: 8px;
666
+ }
667
+
668
+ .nf-kc .nf-panel-footer .nf-icon {
669
+ width: 16px;
670
+ height: 16px;
671
+ flex: none;
672
+ }
673
+
674
+ .nf-kc .nf-dot {
675
+ width: 7px;
676
+ height: 7px;
677
+ margin-inline: 4px;
678
+ border-radius: 50%;
679
+ background: var(--nf-login-ok);
680
+ }
681
+
682
+ @media (prefers-reduced-motion: reduce) {
683
+ .nf-kc #kc-login::after,
684
+ .nf-back,
685
+ .nf-kc .nf-back .nf-icon {
686
+ transition: none;
687
+ }
688
+ }