keycloakify 10.1.0-rc.0 → 10.1.0

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 (205) hide show
  1. package/PUBLIC_URL.js +2 -2
  2. package/PUBLIC_URL.js.map +1 -1
  3. package/account/KcContext/kcContextMocks.js +3 -3
  4. package/account/KcContext/kcContextMocks.js.map +1 -1
  5. package/bin/193.index.js +198 -62
  6. package/bin/{365.index.js → 20.index.js} +302 -263
  7. package/bin/31.index.js +15 -23
  8. package/bin/{430.index.js → 33.index.js} +579 -4
  9. package/bin/{678.index.js → 36.index.js} +2 -577
  10. package/bin/{440.index.js → 499.index.js} +259 -181
  11. package/bin/526.index.js +3 -784
  12. package/bin/599.index.js +4 -1
  13. package/bin/780.index.js +1 -1
  14. package/bin/{525.index.js → 903.index.js} +4840 -97
  15. package/bin/932.index.js +115 -886
  16. package/bin/main.js +4 -4
  17. package/bin/shared/buildContext.d.ts +0 -2
  18. package/bin/shared/buildContext.js.map +1 -1
  19. package/bin/shared/constants.d.ts +5 -6
  20. package/bin/shared/constants.js +5 -6
  21. package/bin/shared/constants.js.map +1 -1
  22. package/bin/shared/copyKeycloakResourcesToPublic.d.ts +2 -4
  23. package/bin/shared/copyKeycloakResourcesToPublic.js.map +1 -1
  24. package/login/KcContext/KcContext.d.ts +9 -18
  25. package/login/KcContext/KcContext.js.map +1 -1
  26. package/login/KcContext/kcContextMocks.js +6 -10
  27. package/login/KcContext/kcContextMocks.js.map +1 -1
  28. package/login/Template.js +4 -59
  29. package/login/Template.js.map +1 -1
  30. package/login/Template.useStylesAndScripts.d.ts +17 -0
  31. package/login/Template.useStylesAndScripts.js +69 -0
  32. package/login/Template.useStylesAndScripts.js.map +1 -0
  33. package/login/pages/Login.js +1 -1
  34. package/login/pages/Login.js.map +1 -1
  35. package/login/pages/LoginIdpLinkConfirmOverride.js +0 -1
  36. package/login/pages/LoginIdpLinkConfirmOverride.js.map +1 -1
  37. package/login/pages/LoginPasskeysConditionalAuthenticate.js +6 -49
  38. package/login/pages/LoginPasskeysConditionalAuthenticate.js.map +1 -1
  39. package/login/pages/LoginPasskeysConditionalAuthenticate.useScript.d.ts +20 -0
  40. package/login/pages/LoginPasskeysConditionalAuthenticate.useScript.js +49 -0
  41. package/login/pages/LoginPasskeysConditionalAuthenticate.useScript.js.map +1 -0
  42. package/login/pages/LoginRecoveryAuthnCodeConfig.js +4 -126
  43. package/login/pages/LoginRecoveryAuthnCodeConfig.js.map +1 -1
  44. package/login/pages/LoginRecoveryAuthnCodeConfig.useScript.d.ts +9 -0
  45. package/login/pages/LoginRecoveryAuthnCodeConfig.useScript.js +133 -0
  46. package/login/pages/LoginRecoveryAuthnCodeConfig.useScript.js.map +1 -0
  47. package/login/pages/LoginUsername.js +1 -1
  48. package/login/pages/LoginUsername.js.map +1 -1
  49. package/login/pages/Register.js +6 -3
  50. package/login/pages/Register.js.map +1 -1
  51. package/login/pages/WebauthnAuthenticate.js +13 -116
  52. package/login/pages/WebauthnAuthenticate.js.map +1 -1
  53. package/login/pages/WebauthnAuthenticate.useScript.d.ts +21 -0
  54. package/login/pages/WebauthnAuthenticate.useScript.js +41 -0
  55. package/login/pages/WebauthnAuthenticate.useScript.js.map +1 -0
  56. package/login/pages/WebauthnRegister.js +8 -178
  57. package/login/pages/WebauthnRegister.js.map +1 -1
  58. package/login/pages/WebauthnRegister.useScript.d.ts +27 -0
  59. package/login/pages/WebauthnRegister.useScript.js +49 -0
  60. package/login/pages/WebauthnRegister.useScript.js.map +1 -0
  61. package/package.json +125 -15
  62. package/res/account-v1/account.ftl +70 -0
  63. package/res/account-v1/applications.ftl +76 -0
  64. package/res/account-v1/federatedIdentity.ftl +42 -0
  65. package/res/account-v1/log.ftl +35 -0
  66. package/res/account-v1/messages/messages_ar.properties +406 -0
  67. package/res/account-v1/messages/messages_ca.properties +147 -0
  68. package/res/account-v1/messages/messages_cs.properties +171 -0
  69. package/res/account-v1/messages/messages_da.properties +339 -0
  70. package/res/account-v1/messages/messages_de.properties +353 -0
  71. package/res/account-v1/messages/messages_en.properties +404 -0
  72. package/res/account-v1/messages/messages_es.properties +147 -0
  73. package/res/account-v1/messages/messages_fi.properties +400 -0
  74. package/res/account-v1/messages/messages_fr.properties +180 -0
  75. package/res/account-v1/messages/messages_hu.properties +334 -0
  76. package/res/account-v1/messages/messages_it.properties +336 -0
  77. package/res/account-v1/messages/messages_ja.properties +335 -0
  78. package/res/account-v1/messages/messages_lt.properties +154 -0
  79. package/res/account-v1/messages/messages_lv.properties +197 -0
  80. package/res/account-v1/messages/messages_nl.properties +371 -0
  81. package/res/account-v1/messages/messages_no.properties +152 -0
  82. package/res/account-v1/messages/messages_pl.properties +248 -0
  83. package/res/account-v1/messages/messages_pt_BR.properties +349 -0
  84. package/res/account-v1/messages/messages_ru.properties +235 -0
  85. package/res/account-v1/messages/messages_sk.properties +196 -0
  86. package/res/account-v1/messages/messages_sv.properties +150 -0
  87. package/res/account-v1/messages/messages_tr.properties +315 -0
  88. package/res/account-v1/messages/messages_zh_CN.properties +153 -0
  89. package/res/account-v1/password.ftl +59 -0
  90. package/res/account-v1/resource-detail.ftl +277 -0
  91. package/res/account-v1/resources/css/account.css +277 -0
  92. package/res/account-v1/resources/img/icon-sidebar-active.png +0 -0
  93. package/res/account-v1/resources/img/keycloak-logo.png +0 -0
  94. package/res/account-v1/resources/img/logo.png +0 -0
  95. package/res/account-v1/resources/resources-common/img/favicon.ico +0 -0
  96. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css +5 -0
  97. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/css/patternfly.min.css +8 -0
  98. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2 +0 -0
  99. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2 +0 -0
  100. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2 +0 -0
  101. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2 +0 -0
  102. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf +0 -0
  103. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.woff +0 -0
  104. package/res/account-v1/resources.ftl +403 -0
  105. package/res/account-v1/sessions.ftl +44 -0
  106. package/res/account-v1/template.ftl +88 -0
  107. package/res/account-v1/theme.properties +14 -0
  108. package/res/account-v1/totp.ftl +141 -0
  109. package/res/public/.keycloakify/account/css/account.css +277 -0
  110. package/res/public/.keycloakify/account/img/icon-sidebar-active.png +0 -0
  111. package/res/public/.keycloakify/account/img/keycloak-logo.png +0 -0
  112. package/res/public/.keycloakify/account/img/logo.png +0 -0
  113. package/res/public/.keycloakify/account/resources-common/img/favicon.ico +0 -0
  114. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css +5 -0
  115. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/css/patternfly.min.css +8 -0
  116. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2 +0 -0
  117. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2 +0 -0
  118. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2 +0 -0
  119. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2 +0 -0
  120. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf +0 -0
  121. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.woff +0 -0
  122. package/res/public/.keycloakify/login/css/login.css +629 -0
  123. package/res/public/.keycloakify/login/img/feedback-error-arrow-down.png +0 -0
  124. package/res/public/.keycloakify/login/img/feedback-error-sign.png +0 -0
  125. package/res/public/.keycloakify/login/img/feedback-success-arrow-down.png +0 -0
  126. package/res/public/.keycloakify/login/img/feedback-success-sign.png +0 -0
  127. package/res/public/.keycloakify/login/img/feedback-warning-arrow-down.png +0 -0
  128. package/res/public/.keycloakify/login/img/feedback-warning-sign.png +0 -0
  129. package/res/public/.keycloakify/login/img/keycloak-bg.png +0 -0
  130. package/res/public/.keycloakify/login/img/keycloak-logo-text.png +0 -0
  131. package/res/public/.keycloakify/login/img/keycloak-logo.png +0 -0
  132. package/res/public/.keycloakify/login/js/authChecker.js +49 -0
  133. package/res/public/.keycloakify/login/js/common.js +48 -0
  134. package/res/public/.keycloakify/login/js/kcMultivalued.js +106 -0
  135. package/res/public/.keycloakify/login/js/kcNumberFormat.js +21 -0
  136. package/res/public/.keycloakify/login/js/kcNumberUnFormat.js +19 -0
  137. package/res/public/.keycloakify/login/js/menu-button-links.js +315 -0
  138. package/{src/bin/shared/downloadKeycloakDefaultTheme/extra-assets → res/public/.keycloakify/login/js}/passkeysConditionalAuth.js +1 -1
  139. package/res/public/.keycloakify/login/js/passwordVisibility.js +15 -0
  140. package/res/public/.keycloakify/login/js/userProfile.js +71 -0
  141. package/res/public/.keycloakify/login/js/webauthnRegister.js +140 -0
  142. package/res/public/.keycloakify/login/resources-common/img/favicon.ico +0 -0
  143. package/res/public/.keycloakify/login/resources-common/lib/pficon/pficon.css +22 -0
  144. package/res/public/.keycloakify/login/resources-common/lib/pficon/pficon.woff2 +0 -0
  145. package/res/public/.keycloakify/login/resources-common/node_modules/@patternfly/patternfly/patternfly.min.css +2 -0
  146. package/res/public/.keycloakify/login/resources-common/node_modules/jquery/dist/jquery.min.js +2 -0
  147. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css +5 -0
  148. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/css/patternfly.min.css +8 -0
  149. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.ttf +0 -0
  150. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff +0 -0
  151. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2 +0 -0
  152. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2 +0 -0
  153. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2 +0 -0
  154. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2 +0 -0
  155. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf +0 -0
  156. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.woff +0 -0
  157. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/fontawesome-webfont.woff2 +0 -0
  158. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/img/bg-login.jpg +0 -0
  159. package/res/public/.keycloakify/login/resources-common/node_modules/rfc4648/lib/rfc4648.js +178 -0
  160. package/src/PUBLIC_URL.ts +5 -3
  161. package/src/account/KcContext/kcContextMocks.ts +3 -3
  162. package/src/bin/copy-keycloak-resources-to-public.ts +1 -1
  163. package/src/bin/initialize-account-theme/initializeAccountTheme_singlePage.ts +4 -1
  164. package/src/bin/initialize-email-theme.ts +28 -8
  165. package/src/bin/keycloakify/buildJars/buildJar.ts +3 -7
  166. package/src/bin/keycloakify/generateFtl/generateFtl.ts +3 -7
  167. package/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +65 -90
  168. package/src/bin/keycloakify/keycloakify.ts +38 -21
  169. package/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts +2 -2
  170. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts +3 -3
  171. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts +3 -3
  172. package/src/bin/shared/buildContext.ts +1 -8
  173. package/src/bin/shared/constants.ts +5 -7
  174. package/src/bin/shared/copyKeycloakResourcesToPublic.ts +30 -40
  175. package/src/bin/start-keycloak/start-keycloak.ts +3 -7
  176. package/src/login/KcContext/KcContext.ts +9 -18
  177. package/src/login/KcContext/kcContextMocks.ts +5 -13
  178. package/src/login/Template.tsx +4 -67
  179. package/src/login/Template.useStylesAndScripts.ts +94 -0
  180. package/src/login/pages/Login.tsx +1 -1
  181. package/src/login/pages/LoginIdpLinkConfirmOverride.tsx +0 -1
  182. package/src/login/pages/LoginPasskeysConditionalAuthenticate.tsx +6 -81
  183. package/src/login/pages/LoginPasskeysConditionalAuthenticate.useScript.tsx +72 -0
  184. package/src/login/pages/LoginRecoveryAuthnCodeConfig.tsx +4 -126
  185. package/src/login/pages/LoginRecoveryAuthnCodeConfig.useScript.tsx +142 -0
  186. package/src/login/pages/LoginUsername.tsx +1 -1
  187. package/src/login/pages/Register.tsx +35 -13
  188. package/src/login/pages/WebauthnAuthenticate.tsx +20 -133
  189. package/src/login/pages/WebauthnAuthenticate.useScript.tsx +64 -0
  190. package/src/login/pages/WebauthnRegister.tsx +8 -195
  191. package/src/login/pages/WebauthnRegister.useScript.tsx +93 -0
  192. package/src/tools/useInsertScriptTags.ts +14 -4
  193. package/src/vite-plugin/vite-plugin.ts +11 -15
  194. package/tools/useInsertScriptTags.d.ts +2 -2
  195. package/tools/useInsertScriptTags.js +8 -2
  196. package/tools/useInsertScriptTags.js.map +1 -1
  197. package/vite-plugin/index.js +3357 -47132
  198. package/bin/697.index.js +0 -4749
  199. package/bin/shared/downloadKeycloakStaticResources.d.ts +0 -9
  200. package/bin/shared/downloadKeycloakStaticResources.js.map +0 -1
  201. package/src/bin/keycloakify/generateResources/bringInAccountV1.ts +0 -89
  202. package/src/bin/shared/downloadKeycloakDefaultTheme/downloadKeycloakDefaultTheme.ts +0 -337
  203. package/src/bin/shared/downloadKeycloakDefaultTheme/index.ts +0 -1
  204. package/src/bin/shared/downloadKeycloakStaticResources.ts +0 -53
  205. /package/{src/bin/shared/downloadKeycloakDefaultTheme/extra-assets → res/public/.keycloakify/login/js}/webauthnAuthenticate.js +0 -0
@@ -0,0 +1,94 @@
1
+ import { useEffect } from "react";
2
+ import { assert } from "keycloakify/tools/assert";
3
+ import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
4
+ import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
5
+ import { KcContext } from "keycloakify/login/KcContext/KcContext";
6
+
7
+ export type KcContextLike = {
8
+ url: {
9
+ resourcesCommonPath: string;
10
+ resourcesPath: string;
11
+ ssoLoginInOtherTabsUrl: string;
12
+ };
13
+ locale?: {
14
+ currentLanguageTag: string;
15
+ };
16
+ scripts: string[];
17
+ };
18
+
19
+ assert<keyof KcContextLike extends keyof KcContext ? true : false>();
20
+ assert<KcContext extends KcContextLike ? true : false>();
21
+
22
+ export function useStylesAndScripts(params: {
23
+ kcContext: KcContextLike;
24
+ doUseDefaultCss: boolean;
25
+ }) {
26
+ const { kcContext, doUseDefaultCss } = params;
27
+
28
+ const { url, locale, scripts } = kcContext;
29
+
30
+ useEffect(() => {
31
+ const { currentLanguageTag } = locale ?? {};
32
+
33
+ if (currentLanguageTag === undefined) {
34
+ return;
35
+ }
36
+
37
+ const html = document.querySelector("html");
38
+ assert(html !== null);
39
+ html.lang = currentLanguageTag;
40
+ }, []);
41
+
42
+ const { areAllStyleSheetsLoaded } = useInsertLinkTags({
43
+ componentOrHookName: "Template",
44
+ hrefs: !doUseDefaultCss
45
+ ? []
46
+ : [
47
+ `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`,
48
+ `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,
49
+ `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`,
50
+ `${url.resourcesCommonPath}/lib/pficon/pficon.css`,
51
+ `${url.resourcesPath}/css/login.css`
52
+ ]
53
+ });
54
+
55
+ const { insertScriptTags } = useInsertScriptTags({
56
+ componentOrHookName: "Template",
57
+ scriptTags: [
58
+ {
59
+ type: "importmap",
60
+ textContent: JSON.stringify({
61
+ imports: {
62
+ rfc4648: `${url.resourcesCommonPath}/node_modules/rfc4648/lib/rfc4648.js`
63
+ }
64
+ })
65
+ },
66
+ {
67
+ type: "module",
68
+ src: `${url.resourcesPath}/js/menu-button-links.js`
69
+ },
70
+ ...scripts.map(src => ({
71
+ type: "text/javascript" as const,
72
+ src
73
+ })),
74
+ {
75
+ type: "module",
76
+ textContent: `
77
+ import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";
78
+
79
+ checkCookiesAndSetTimer(
80
+ "${url.ssoLoginInOtherTabsUrl}"
81
+ );
82
+ `
83
+ }
84
+ ]
85
+ });
86
+
87
+ useEffect(() => {
88
+ if (areAllStyleSheetsLoaded) {
89
+ insertScriptTags();
90
+ }
91
+ }, [areAllStyleSheetsLoaded]);
92
+
93
+ return { isReadyToRender: areAllStyleSheetsLoaded };
94
+ }
@@ -43,7 +43,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
43
43
  }
44
44
  socialProvidersNode={
45
45
  <>
46
- {realm.password && social.providers !== undefined && social.providers.length !== 0 && (
46
+ {realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
47
47
  <div id="kc-social-providers" className={kcClsx("kcFormSocialAccountSectionClass")}>
48
48
  <hr />
49
49
  <h2>{msg("identity-provider-login-label")}</h2>
@@ -3,7 +3,6 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
3
3
  import type { KcContext } from "../KcContext";
4
4
  import type { I18n } from "../i18n";
5
5
 
6
- // NOTE: Added with Keycloak 25
7
6
  export default function LoginIdpLinkConfirmOverride(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm-override.ftl" }>, I18n>) {
8
7
  const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
9
8
 
@@ -1,33 +1,17 @@
1
- import { useEffect, Fragment } from "react";
1
+ import { Fragment } from "react";
2
2
  import { clsx } from "keycloakify/tools/clsx";
3
3
  import type { PageProps } from "keycloakify/login/pages/PageProps";
4
- import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
5
4
  import { getKcClsx } from "keycloakify/login/lib/kcClsx";
6
- import { assert } from "keycloakify/tools/assert";
5
+ import { useScript } from "keycloakify/login/pages/LoginPasskeysConditionalAuthenticate.useScript";
7
6
  import type { KcContext } from "../KcContext";
8
7
  import type { I18n } from "../i18n";
9
8
 
10
- // NOTE: From Keycloak 25.0.4
11
9
  export default function LoginPasskeysConditionalAuthenticate(
12
10
  props: PageProps<Extract<KcContext, { pageId: "login-passkeys-conditional-authenticate.ftl" }>, I18n>
13
11
  ) {
14
12
  const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
15
13
 
16
- const {
17
- messagesPerField,
18
- login,
19
- url,
20
- usernameHidden,
21
- shouldDisplayAuthenticators,
22
- authenticators,
23
- registrationDisabled,
24
- realm,
25
- isUserIdentified,
26
- challenge,
27
- userVerification,
28
- rpId,
29
- createTimeout
30
- } = kcContext;
14
+ const { messagesPerField, login, url, usernameHidden, shouldDisplayAuthenticators, authenticators, registrationDisabled, realm } = kcContext;
31
15
 
32
16
  const { msg, msgStr, advancedMsg } = i18n;
33
17
 
@@ -36,46 +20,9 @@ export default function LoginPasskeysConditionalAuthenticate(
36
20
  classes
37
21
  });
38
22
 
39
- const { insertScriptTags } = useInsertScriptTags({
40
- componentOrHookName: "LoginRecoveryAuthnCodeConfig",
41
- scriptTags: [
42
- {
43
- type: "module",
44
- textContent: `
45
- import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
46
- import { initAuthenticate } from "${url.resourcesPath}/js/passkeysConditionalAuth.js";
23
+ const authButtonId = "authenticateWebAuthnButton";
47
24
 
48
- const authButton = document.getElementById('authenticateWebAuthnButton');
49
- const input = {
50
- isUserIdentified : ${isUserIdentified},
51
- challenge : '${challenge}',
52
- userVerification : '${userVerification}',
53
- rpId : '${rpId}',
54
- createTimeout : ${createTimeout},
55
- errmsg : "${msgStr("webauthn-unsupported-browser-text")}"
56
- };
57
- authButton.addEventListener("click", () => {
58
- authenticateByWebAuthn(input);
59
- });
60
-
61
- const args = {
62
- isUserIdentified : ${isUserIdentified},
63
- challenge : '${challenge}',
64
- userVerification : '${userVerification}',
65
- rpId : '${rpId}',
66
- createTimeout : ${createTimeout},
67
- errmsg : "${msgStr("passkey-unsupported-browser-text")}"
68
- };
69
-
70
- document.addEventListener("DOMContentLoaded", (event) => initAuthenticate(args));
71
- `
72
- }
73
- ]
74
- });
75
-
76
- useEffect(() => {
77
- insertScriptTags();
78
- }, []);
25
+ useScript({ authButtonId, kcContext, i18n });
79
26
 
80
27
  return (
81
28
  <Template
@@ -213,29 +160,7 @@ export default function LoginPasskeysConditionalAuthenticate(
213
160
  )}
214
161
  <div id="kc-form-passkey-button" className={kcClsx("kcFormButtonsClass")} style={{ display: "none" }}>
215
162
  <input
216
- id="authenticateWebAuthnButton"
217
- type="button"
218
- onClick={() => {
219
- assert("doAuthenticate" in window);
220
- assert(typeof window.doAuthenticate === "function");
221
- window.doAuthenticate(
222
- [],
223
- rpId,
224
- challenge,
225
- typeof isUserIdentified === "boolean" ? isUserIdentified : isUserIdentified === "true",
226
- createTimeout,
227
- userVerification,
228
- msgStr("passkey-unsupported-browser-text")
229
- );
230
- }}
231
- autoFocus
232
- value={msgStr("passkey-doAuthenticate")}
233
- className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonBlockClass", "kcButtonLargeClass")}
234
- />
235
- </div>
236
- <div id="kc-form-passkey-button" className={kcClsx("kcFormButtonsClass")} style={{ display: "none" }}>
237
- <input
238
- id="authenticateWebAuthnButton"
163
+ id={authButtonId}
239
164
  type="button"
240
165
  autoFocus
241
166
  value={msgStr("passkey-doAuthenticate")}
@@ -0,0 +1,72 @@
1
+ import { useEffect } from "react";
2
+ import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
3
+ import { assert } from "keycloakify/tools/assert";
4
+ import { KcContext } from "keycloakify/login/KcContext/KcContext";
5
+
6
+ type KcContextLike = {
7
+ url: {
8
+ resourcesPath: string;
9
+ };
10
+ isUserIdentified: boolean | "true" | "false";
11
+ challenge: string;
12
+ userVerification: string;
13
+ rpId: string;
14
+ createTimeout: number | string;
15
+ };
16
+
17
+ assert<keyof KcContextLike extends keyof KcContext.LoginPasskeysConditionalAuthenticate ? true : false>();
18
+ assert<KcContext.LoginPasskeysConditionalAuthenticate extends KcContextLike ? true : false>();
19
+
20
+ type I18nLike = {
21
+ msgStr: (key: "webauthn-unsupported-browser-text" | "passkey-unsupported-browser-text") => string;
22
+ isFetchingTranslations: boolean;
23
+ };
24
+
25
+ export function useScript(params: { authButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
26
+ const { authButtonId, kcContext, i18n } = params;
27
+
28
+ const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
29
+
30
+ const { msgStr, isFetchingTranslations } = i18n;
31
+
32
+ const { insertScriptTags } = useInsertScriptTags({
33
+ componentOrHookName: "LoginRecoveryAuthnCodeConfig",
34
+ scriptTags: [
35
+ {
36
+ type: "module",
37
+ textContent: () => `
38
+ import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
39
+ import { initAuthenticate } from "${url.resourcesPath}/js/passkeysConditionalAuth.js";
40
+
41
+ const authButton = document.getElementById("${authButtonId}");
42
+ const input = {
43
+ isUserIdentified : ${isUserIdentified},
44
+ challenge : ${JSON.stringify(challenge)},
45
+ userVerification : ${JSON.stringify(userVerification)},
46
+ rpId : ${JSON.stringify(rpId)},
47
+ createTimeout : ${createTimeout}
48
+ };
49
+ authButton.addEventListener("click", () => {
50
+ authenticateByWebAuthn({
51
+ ...input,
52
+ errmsg : ${JSON.stringify(msgStr("webauthn-unsupported-browser-text"))}
53
+ });
54
+ });
55
+
56
+ initAuthenticate({
57
+ ...input,
58
+ errmsg : ${JSON.stringify(msgStr("passkey-unsupported-browser-text"))}
59
+ });
60
+ `
61
+ }
62
+ ]
63
+ });
64
+
65
+ useEffect(() => {
66
+ if (isFetchingTranslations) {
67
+ return;
68
+ }
69
+
70
+ insertScriptTags();
71
+ }, [isFetchingTranslations]);
72
+ }
@@ -1,7 +1,6 @@
1
- import { useEffect } from "react";
2
1
  import { clsx } from "keycloakify/tools/clsx";
3
2
  import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
4
- import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
3
+ import { useScript } from "keycloakify/login/pages/LoginRecoveryAuthnCodeConfig.useScript";
5
4
  import type { PageProps } from "keycloakify/login/pages/PageProps";
6
5
  import type { KcContext } from "../KcContext";
7
6
  import type { I18n } from "../i18n";
@@ -18,130 +17,9 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
18
17
 
19
18
  const { msg, msgStr } = i18n;
20
19
 
21
- const { insertScriptTags } = useInsertScriptTags({
22
- componentOrHookName: "LoginRecoveryAuthnCodeConfig",
23
- scriptTags: [
24
- {
25
- type: "text/javascript",
26
- textContent: `
20
+ const olRecoveryCodesListId = "kc-recovery-codes-list";
27
21
 
28
- /* copy recovery codes */
29
- function copyRecoveryCodes() {
30
- var tmpTextarea = document.createElement("textarea");
31
- var codes = document.getElementById("kc-recovery-codes-list").getElementsByTagName("li");
32
- for (i = 0; i < codes.length; i++) {
33
- tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\\n";
34
- }
35
- document.body.appendChild(tmpTextarea);
36
- tmpTextarea.select();
37
- document.execCommand("copy");
38
- document.body.removeChild(tmpTextarea);
39
- }
40
-
41
- var copyButton = document.getElementById("copyRecoveryCodes");
42
- copyButton && copyButton.addEventListener("click", function () {
43
- copyRecoveryCodes();
44
- });
45
-
46
- /* download recovery codes */
47
- function formatCurrentDateTime() {
48
- var dt = new Date();
49
- var options = {
50
- month: 'long',
51
- day: 'numeric',
52
- year: 'numeric',
53
- hour: 'numeric',
54
- minute: 'numeric',
55
- timeZoneName: 'short'
56
- };
57
-
58
- return dt.toLocaleString('en-US', options);
59
- }
60
-
61
- function parseRecoveryCodeList() {
62
- var recoveryCodes = document.querySelectorAll(".kc-recovery-codes-list li");
63
- var recoveryCodeList = "";
64
-
65
- for (var i = 0; i < recoveryCodes.length; i++) {
66
- var recoveryCodeLiElement = recoveryCodes[i].innerText;
67
- recoveryCodeList += recoveryCodeLiElement + "\\r\\n";
68
- }
69
-
70
- return recoveryCodeList;
71
- }
72
-
73
- function buildDownloadContent() {
74
- var recoveryCodeList = parseRecoveryCodeList();
75
- var dt = new Date();
76
- var options = {
77
- month: 'long',
78
- day: 'numeric',
79
- year: 'numeric',
80
- hour: 'numeric',
81
- minute: 'numeric',
82
- timeZoneName: 'short'
83
- };
84
-
85
- return fileBodyContent =
86
- "${msgStr("recovery-codes-download-file-header")}\\n\\n" +
87
- recoveryCodeList + "\\n" +
88
- "${msgStr("recovery-codes-download-file-description")}\\n\\n" +
89
- "${msgStr("recovery-codes-download-file-date")} " + formatCurrentDateTime();
90
- }
91
-
92
- function setUpDownloadLinkAndDownload(filename, text) {
93
- var el = document.createElement('a');
94
- el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
95
- el.setAttribute('download', filename);
96
- el.style.display = 'none';
97
- document.body.appendChild(el);
98
- el.click();
99
- document.body.removeChild(el);
100
- }
101
-
102
- function downloadRecoveryCodes() {
103
- setUpDownloadLinkAndDownload('kc-download-recovery-codes.txt', buildDownloadContent());
104
- }
105
-
106
- var downloadButton = document.getElementById("downloadRecoveryCodes");
107
- downloadButton && downloadButton.addEventListener("click", downloadRecoveryCodes);
108
-
109
- /* print recovery codes */
110
- function buildPrintContent() {
111
- var recoveryCodeListHTML = document.getElementById('kc-recovery-codes-list').innerHTML;
112
- var styles =
113
- \`@page { size: auto; margin-top: 0; }
114
- body { width: 480px; }
115
- div { list-style-type: none; font-family: monospace }
116
- p:first-of-type { margin-top: 48px }\`;
117
-
118
- return printFileContent =
119
- "<html><style>" + styles + "</style><body>" +
120
- "<title>kc-download-recovery-codes</title>" +
121
- "<p>${msgStr("recovery-codes-download-file-header")}</p>" +
122
- "<div>" + recoveryCodeListHTML + "</div>" +
123
- "<p>${msgStr("recovery-codes-download-file-description")}</p>" +
124
- "<p>${msgStr("recovery-codes-download-file-date")} " + formatCurrentDateTime() + "</p>" +
125
- "</body></html>";
126
- }
127
-
128
- function printRecoveryCodes() {
129
- var w = window.open();
130
- w.document.write(buildPrintContent());
131
- w.print();
132
- w.close();
133
- }
134
-
135
- var printButton = document.getElementById("printRecoveryCodes");
136
- printButton && printButton.addEventListener("click", printRecoveryCodes);
137
- `
138
- }
139
- ]
140
- });
141
-
142
- useEffect(() => {
143
- insertScriptTags();
144
- }, []);
22
+ useScript({ olRecoveryCodesListId, i18n });
145
23
 
146
24
  return (
147
25
  <Template
@@ -164,7 +42,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
164
42
  </div>
165
43
  </div>
166
44
 
167
- <ol id="kc-recovery-codes-list" className={kcClsx("kcRecoveryCodesList")}>
45
+ <ol id={olRecoveryCodesListId} className={kcClsx("kcRecoveryCodesList")}>
168
46
  {recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList.map((code, index) => (
169
47
  <li key={index}>
170
48
  <span>{index + 1}:</span> {code.slice(0, 4)}-{code.slice(4, 8)}-{code.slice(8)}
@@ -0,0 +1,142 @@
1
+ import { useEffect } from "react";
2
+ import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
3
+
4
+ type I18nLike = {
5
+ msgStr: (key: "recovery-codes-download-file-header" | "recovery-codes-download-file-description" | "recovery-codes-download-file-date") => string;
6
+ isFetchingTranslations: boolean;
7
+ };
8
+
9
+ export function useScript(params: { olRecoveryCodesListId: string; i18n: I18nLike }) {
10
+ const { olRecoveryCodesListId, i18n } = params;
11
+
12
+ const { msgStr, isFetchingTranslations } = i18n;
13
+
14
+ const { insertScriptTags } = useInsertScriptTags({
15
+ componentOrHookName: "LoginRecoveryAuthnCodeConfig",
16
+ scriptTags: [
17
+ {
18
+ type: "text/javascript",
19
+ textContent: () => `
20
+
21
+ /* copy recovery codes */
22
+ function copyRecoveryCodes() {
23
+ var tmpTextarea = document.createElement("textarea");
24
+ var codes = document.querySelectorAll("#${olRecoveryCodesListId} li");
25
+ for (i = 0; i < codes.length; i++) {
26
+ tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\\n";
27
+ }
28
+ document.body.appendChild(tmpTextarea);
29
+ tmpTextarea.select();
30
+ document.execCommand("copy");
31
+ document.body.removeChild(tmpTextarea);
32
+ }
33
+
34
+ var copyButton = document.getElementById("copyRecoveryCodes");
35
+ copyButton && copyButton.addEventListener("click", function () {
36
+ copyRecoveryCodes();
37
+ });
38
+
39
+ /* download recovery codes */
40
+ function formatCurrentDateTime() {
41
+ var dt = new Date();
42
+ var options = {
43
+ month: 'long',
44
+ day: 'numeric',
45
+ year: 'numeric',
46
+ hour: 'numeric',
47
+ minute: 'numeric',
48
+ timeZoneName: 'short'
49
+ };
50
+
51
+ return dt.toLocaleString('en-US', options);
52
+ }
53
+
54
+ function parseRecoveryCodeList() {
55
+ var recoveryCodes = document.querySelectorAll("#${olRecoveryCodesListId} li");
56
+ var recoveryCodeList = "";
57
+
58
+ for (var i = 0; i < recoveryCodes.length; i++) {
59
+ var recoveryCodeLiElement = recoveryCodes[i].innerText;
60
+ recoveryCodeList += recoveryCodeLiElement + "\\r\\n";
61
+ }
62
+
63
+ return recoveryCodeList;
64
+ }
65
+
66
+ function buildDownloadContent() {
67
+ var recoveryCodeList = parseRecoveryCodeList();
68
+ var dt = new Date();
69
+ var options = {
70
+ month: 'long',
71
+ day: 'numeric',
72
+ year: 'numeric',
73
+ hour: 'numeric',
74
+ minute: 'numeric',
75
+ timeZoneName: 'short'
76
+ };
77
+
78
+ return fileBodyContent =
79
+ ${JSON.stringify(msgStr("recovery-codes-download-file-header"))} + "\\n\\n" +
80
+ recoveryCodeList + "\\n" +
81
+ ${JSON.stringify(msgStr("recovery-codes-download-file-description"))} + "\\n\\n" +
82
+ ${JSON.stringify(msgStr("recovery-codes-download-file-date"))} + " " + formatCurrentDateTime();
83
+ }
84
+
85
+ function setUpDownloadLinkAndDownload(filename, text) {
86
+ var el = document.createElement('a');
87
+ el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
88
+ el.setAttribute('download', filename);
89
+ el.style.display = 'none';
90
+ document.body.appendChild(el);
91
+ el.click();
92
+ document.body.removeChild(el);
93
+ }
94
+
95
+ function downloadRecoveryCodes() {
96
+ setUpDownloadLinkAndDownload('kc-download-recovery-codes.txt', buildDownloadContent());
97
+ }
98
+
99
+ var downloadButton = document.getElementById("downloadRecoveryCodes");
100
+ downloadButton && downloadButton.addEventListener("click", downloadRecoveryCodes);
101
+
102
+ /* print recovery codes */
103
+ function buildPrintContent() {
104
+ var recoveryCodeListHTML = document.getElementById('${olRecoveryCodesListId}').innerHTML;
105
+ var styles =
106
+ \`@page { size: auto; margin-top: 0; }
107
+ body { width: 480px; }
108
+ div { list-style-type: none; font-family: monospace }
109
+ p:first-of-type { margin-top: 48px }\`;
110
+
111
+ return printFileContent =
112
+ "<html><style>" + styles + "</style><body>" +
113
+ "<title>kc-download-recovery-codes</title>" +
114
+ "<p>" + ${JSON.stringify(msgStr("recovery-codes-download-file-header"))} + "</p>" +
115
+ "<div>" + recoveryCodeListHTML + "</div>" +
116
+ "<p>" + ${JSON.stringify(msgStr("recovery-codes-download-file-description"))} + "</p>" +
117
+ "<p>" + ${JSON.stringify(msgStr("recovery-codes-download-file-date"))} + " " + formatCurrentDateTime() + "</p>" +
118
+ "</body></html>";
119
+ }
120
+
121
+ function printRecoveryCodes() {
122
+ var w = window.open();
123
+ w.document.write(buildPrintContent());
124
+ w.print();
125
+ w.close();
126
+ }
127
+
128
+ var printButton = document.getElementById("printRecoveryCodes");
129
+ printButton && printButton.addEventListener("click", printRecoveryCodes);
130
+ `
131
+ }
132
+ ]
133
+ });
134
+
135
+ useEffect(() => {
136
+ if (isFetchingTranslations) {
137
+ return;
138
+ }
139
+
140
+ insertScriptTags();
141
+ }, [isFetchingTranslations]);
142
+ }
@@ -40,7 +40,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
40
40
  headerNode={msg("doLogIn")}
41
41
  socialProvidersNode={
42
42
  <>
43
- {realm.password && social.providers !== undefined && social.providers.length !== 0 && (
43
+ {realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
44
44
  <div id="kc-social-providers" className={kcClsx("kcFormSocialAccountSectionClass")}>
45
45
  <hr />
46
46
  <h2>{msg("identity-provider-login-label")}</h2>