keycloakify 11.11.3 → 11.13.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 (168) hide show
  1. package/README.md +1 -0
  2. package/bin/375.index.js +1 -1
  3. package/bin/main.js +3 -2
  4. package/bin/shared/constants.d.ts +2 -2
  5. package/bin/shared/constants.js +2 -1
  6. package/bin/shared/constants.js.map +1 -1
  7. package/login/DefaultPage.js +3 -0
  8. package/login/DefaultPage.js.map +1 -1
  9. package/login/KcContext/KcContext.d.ts +25 -1
  10. package/login/KcContext/KcContext.js.map +1 -1
  11. package/login/KcContext/getKcContextMock.d.ts +1 -1
  12. package/login/KcContext/kcContextMocks.d.ts +1 -1
  13. package/login/KcContext/kcContextMocks.js +7 -2
  14. package/login/KcContext/kcContextMocks.js.map +1 -1
  15. package/login/i18n/messages_defaultSet/ar.d.ts +2 -0
  16. package/login/i18n/messages_defaultSet/ar.js +3 -1
  17. package/login/i18n/messages_defaultSet/ar.js.map +1 -1
  18. package/login/i18n/messages_defaultSet/ca.d.ts +2 -0
  19. package/login/i18n/messages_defaultSet/ca.js +3 -1
  20. package/login/i18n/messages_defaultSet/ca.js.map +1 -1
  21. package/login/i18n/messages_defaultSet/cs.d.ts +2 -0
  22. package/login/i18n/messages_defaultSet/cs.js +3 -1
  23. package/login/i18n/messages_defaultSet/cs.js.map +1 -1
  24. package/login/i18n/messages_defaultSet/da.d.ts +2 -0
  25. package/login/i18n/messages_defaultSet/da.js +3 -1
  26. package/login/i18n/messages_defaultSet/da.js.map +1 -1
  27. package/login/i18n/messages_defaultSet/de.d.ts +2 -0
  28. package/login/i18n/messages_defaultSet/de.js +3 -1
  29. package/login/i18n/messages_defaultSet/de.js.map +1 -1
  30. package/login/i18n/messages_defaultSet/el.d.ts +2 -0
  31. package/login/i18n/messages_defaultSet/el.js +3 -1
  32. package/login/i18n/messages_defaultSet/el.js.map +1 -1
  33. package/login/i18n/messages_defaultSet/en.d.ts +2 -0
  34. package/login/i18n/messages_defaultSet/en.js +3 -1
  35. package/login/i18n/messages_defaultSet/en.js.map +1 -1
  36. package/login/i18n/messages_defaultSet/es.d.ts +2 -0
  37. package/login/i18n/messages_defaultSet/es.js +3 -1
  38. package/login/i18n/messages_defaultSet/es.js.map +1 -1
  39. package/login/i18n/messages_defaultSet/fa.d.ts +2 -0
  40. package/login/i18n/messages_defaultSet/fa.js +3 -1
  41. package/login/i18n/messages_defaultSet/fa.js.map +1 -1
  42. package/login/i18n/messages_defaultSet/fi.d.ts +2 -0
  43. package/login/i18n/messages_defaultSet/fi.js +3 -1
  44. package/login/i18n/messages_defaultSet/fi.js.map +1 -1
  45. package/login/i18n/messages_defaultSet/fr.d.ts +2 -0
  46. package/login/i18n/messages_defaultSet/fr.js +3 -1
  47. package/login/i18n/messages_defaultSet/fr.js.map +1 -1
  48. package/login/i18n/messages_defaultSet/hu.d.ts +2 -0
  49. package/login/i18n/messages_defaultSet/hu.js +3 -1
  50. package/login/i18n/messages_defaultSet/hu.js.map +1 -1
  51. package/login/i18n/messages_defaultSet/index.d.ts +20 -0
  52. package/login/i18n/messages_defaultSet/it.d.ts +2 -0
  53. package/login/i18n/messages_defaultSet/it.js +3 -1
  54. package/login/i18n/messages_defaultSet/it.js.map +1 -1
  55. package/login/i18n/messages_defaultSet/ja.d.ts +2 -0
  56. package/login/i18n/messages_defaultSet/ja.js +3 -1
  57. package/login/i18n/messages_defaultSet/ja.js.map +1 -1
  58. package/login/i18n/messages_defaultSet/ka.d.ts +2 -0
  59. package/login/i18n/messages_defaultSet/ka.js +3 -1
  60. package/login/i18n/messages_defaultSet/ka.js.map +1 -1
  61. package/login/i18n/messages_defaultSet/lt.d.ts +2 -0
  62. package/login/i18n/messages_defaultSet/lt.js +3 -1
  63. package/login/i18n/messages_defaultSet/lt.js.map +1 -1
  64. package/login/i18n/messages_defaultSet/lv.d.ts +2 -0
  65. package/login/i18n/messages_defaultSet/lv.js +3 -1
  66. package/login/i18n/messages_defaultSet/lv.js.map +1 -1
  67. package/login/i18n/messages_defaultSet/nl.d.ts +2 -0
  68. package/login/i18n/messages_defaultSet/nl.js +3 -1
  69. package/login/i18n/messages_defaultSet/nl.js.map +1 -1
  70. package/login/i18n/messages_defaultSet/no.d.ts +2 -0
  71. package/login/i18n/messages_defaultSet/no.js +3 -1
  72. package/login/i18n/messages_defaultSet/no.js.map +1 -1
  73. package/login/i18n/messages_defaultSet/pl.d.ts +2 -0
  74. package/login/i18n/messages_defaultSet/pl.js +3 -1
  75. package/login/i18n/messages_defaultSet/pl.js.map +1 -1
  76. package/login/i18n/messages_defaultSet/pt-BR.d.ts +2 -0
  77. package/login/i18n/messages_defaultSet/pt-BR.js +3 -1
  78. package/login/i18n/messages_defaultSet/pt-BR.js.map +1 -1
  79. package/login/i18n/messages_defaultSet/pt.d.ts +2 -0
  80. package/login/i18n/messages_defaultSet/pt.js +3 -1
  81. package/login/i18n/messages_defaultSet/pt.js.map +1 -1
  82. package/login/i18n/messages_defaultSet/ru.d.ts +2 -0
  83. package/login/i18n/messages_defaultSet/ru.js +3 -1
  84. package/login/i18n/messages_defaultSet/ru.js.map +1 -1
  85. package/login/i18n/messages_defaultSet/sk.d.ts +2 -0
  86. package/login/i18n/messages_defaultSet/sk.js +3 -1
  87. package/login/i18n/messages_defaultSet/sk.js.map +1 -1
  88. package/login/i18n/messages_defaultSet/sv.d.ts +2 -0
  89. package/login/i18n/messages_defaultSet/sv.js +3 -1
  90. package/login/i18n/messages_defaultSet/sv.js.map +1 -1
  91. package/login/i18n/messages_defaultSet/th.d.ts +2 -0
  92. package/login/i18n/messages_defaultSet/th.js +3 -1
  93. package/login/i18n/messages_defaultSet/th.js.map +1 -1
  94. package/login/i18n/messages_defaultSet/tr.d.ts +2 -0
  95. package/login/i18n/messages_defaultSet/tr.js +3 -1
  96. package/login/i18n/messages_defaultSet/tr.js.map +1 -1
  97. package/login/i18n/messages_defaultSet/uk.d.ts +2 -0
  98. package/login/i18n/messages_defaultSet/uk.js +3 -1
  99. package/login/i18n/messages_defaultSet/uk.js.map +1 -1
  100. package/login/i18n/messages_defaultSet/zh-CN.d.ts +2 -0
  101. package/login/i18n/messages_defaultSet/zh-CN.js +3 -1
  102. package/login/i18n/messages_defaultSet/zh-CN.js.map +1 -1
  103. package/login/i18n/messages_defaultSet/zh-TW.d.ts +2 -0
  104. package/login/i18n/messages_defaultSet/zh-TW.js +3 -1
  105. package/login/i18n/messages_defaultSet/zh-TW.js.map +1 -1
  106. package/login/pages/LinkIdpAction.d.ts +7 -0
  107. package/login/pages/LinkIdpAction.js +13 -0
  108. package/login/pages/LinkIdpAction.js.map +1 -0
  109. package/login/pages/Login.js +20 -13
  110. package/login/pages/Login.js.map +1 -1
  111. package/login/pages/Login.useScript.d.ts +21 -0
  112. package/login/pages/Login.useScript.js +47 -0
  113. package/login/pages/Login.useScript.js.map +1 -0
  114. package/login/pages/LoginPassword.js +15 -8
  115. package/login/pages/LoginPassword.js.map +1 -1
  116. package/login/pages/LoginPassword.useScript.d.ts +21 -0
  117. package/login/pages/LoginPassword.useScript.js +47 -0
  118. package/login/pages/LoginPassword.useScript.js.map +1 -0
  119. package/login/pages/LoginUsername.js +3 -3
  120. package/login/pages/LoginUsername.js.map +1 -1
  121. package/login/pages/LoginUsername.useScript.d.ts +1 -1
  122. package/login/pages/LoginUsername.useScript.js +3 -3
  123. package/login/pages/LoginUsername.useScript.js.map +1 -1
  124. package/package.json +14 -1
  125. package/src/bin/shared/constants.ts +2 -1
  126. package/src/bin/start-keycloak/start-keycloak.ts +2 -2
  127. package/src/login/DefaultPage.tsx +3 -0
  128. package/src/login/KcContext/KcContext.ts +26 -0
  129. package/src/login/KcContext/kcContextMocks.ts +27 -2
  130. package/src/login/i18n/messages_defaultSet/ar.ts +3 -1
  131. package/src/login/i18n/messages_defaultSet/ca.ts +3 -1
  132. package/src/login/i18n/messages_defaultSet/cs.ts +3 -1
  133. package/src/login/i18n/messages_defaultSet/da.ts +3 -1
  134. package/src/login/i18n/messages_defaultSet/de.ts +3 -1
  135. package/src/login/i18n/messages_defaultSet/el.ts +3 -1
  136. package/src/login/i18n/messages_defaultSet/en.ts +3 -1
  137. package/src/login/i18n/messages_defaultSet/es.ts +3 -1
  138. package/src/login/i18n/messages_defaultSet/fa.ts +3 -1
  139. package/src/login/i18n/messages_defaultSet/fi.ts +3 -1
  140. package/src/login/i18n/messages_defaultSet/fr.ts +3 -1
  141. package/src/login/i18n/messages_defaultSet/hu.ts +3 -1
  142. package/src/login/i18n/messages_defaultSet/it.ts +3 -1
  143. package/src/login/i18n/messages_defaultSet/ja.ts +3 -1
  144. package/src/login/i18n/messages_defaultSet/ka.ts +3 -1
  145. package/src/login/i18n/messages_defaultSet/lt.ts +3 -1
  146. package/src/login/i18n/messages_defaultSet/lv.ts +3 -1
  147. package/src/login/i18n/messages_defaultSet/nl.ts +3 -1
  148. package/src/login/i18n/messages_defaultSet/no.ts +3 -1
  149. package/src/login/i18n/messages_defaultSet/pl.ts +3 -1
  150. package/src/login/i18n/messages_defaultSet/pt-BR.ts +3 -1
  151. package/src/login/i18n/messages_defaultSet/pt.ts +3 -1
  152. package/src/login/i18n/messages_defaultSet/ru.ts +3 -1
  153. package/src/login/i18n/messages_defaultSet/sk.ts +3 -1
  154. package/src/login/i18n/messages_defaultSet/sv.ts +3 -1
  155. package/src/login/i18n/messages_defaultSet/th.ts +3 -1
  156. package/src/login/i18n/messages_defaultSet/tr.ts +3 -1
  157. package/src/login/i18n/messages_defaultSet/uk.ts +3 -1
  158. package/src/login/i18n/messages_defaultSet/zh-CN.ts +3 -1
  159. package/src/login/i18n/messages_defaultSet/zh-TW.ts +3 -1
  160. package/src/login/pages/LinkIdpAction.tsx +53 -0
  161. package/src/login/pages/Login.tsx +45 -1
  162. package/src/login/pages/Login.useScript.tsx +71 -0
  163. package/src/login/pages/LoginPassword.tsx +44 -1
  164. package/src/login/pages/LoginPassword.useScript.tsx +71 -0
  165. package/src/login/pages/LoginUsername.tsx +6 -6
  166. package/src/login/pages/LoginUsername.useScript.tsx +4 -4
  167. package/stories/login/pages/LinkIdpAction.stories.tsx +36 -0
  168. package/vite-plugin/index.js +3 -2
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Combined Username + Password login page (login.ftl) with optional WebAuthn passkey support.
3
+ * Renders standard login form plus conditional passkey authenticator section.
4
+ */
1
5
  import type { JSX } from "keycloakify/tools/JSX";
2
6
  import { useState } from "react";
3
7
  import { kcSanitize } from "keycloakify/lib/kcSanitize";
@@ -7,6 +11,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
7
11
  import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
8
12
  import type { KcContext } from "../KcContext";
9
13
  import type { I18n } from "../i18n";
14
+ import { useScript } from "keycloakify/login/pages/Login.useScript";
10
15
 
11
16
  export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
12
17
  const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@@ -16,12 +21,21 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
16
21
  classes
17
22
  });
18
23
 
19
- const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField } = kcContext;
24
+ const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField, enableWebAuthnConditionalUI, authenticators } =
25
+ kcContext;
20
26
 
21
27
  const { msg, msgStr } = i18n;
22
28
 
23
29
  const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
24
30
 
31
+ const webAuthnButtonId = "authenticateWebAuthnButton";
32
+
33
+ useScript({
34
+ webAuthnButtonId,
35
+ kcContext,
36
+ i18n
37
+ });
38
+
25
39
  return (
26
40
  <Template
27
41
  kcContext={kcContext}
@@ -191,6 +205,36 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
191
205
  )}
192
206
  </div>
193
207
  </div>
208
+ {enableWebAuthnConditionalUI && (
209
+ <>
210
+ <form id="webauth" action={url.loginAction} method="post">
211
+ <input type="hidden" id="clientDataJSON" name="clientDataJSON" />
212
+ <input type="hidden" id="authenticatorData" name="authenticatorData" />
213
+ <input type="hidden" id="signature" name="signature" />
214
+ <input type="hidden" id="credentialId" name="credentialId" />
215
+ <input type="hidden" id="userHandle" name="userHandle" />
216
+ <input type="hidden" id="error" name="error" />
217
+ </form>
218
+
219
+ {authenticators !== undefined && authenticators.authenticators.length !== 0 && (
220
+ <>
221
+ <form id="authn_select" className={kcClsx("kcFormClass")}>
222
+ {authenticators.authenticators.map((authenticator, i) => (
223
+ <input key={i} type="hidden" name="authn_use_chk" readOnly value={authenticator.credentialId} />
224
+ ))}
225
+ </form>
226
+ </>
227
+ )}
228
+ <br />
229
+
230
+ <input
231
+ id={webAuthnButtonId}
232
+ type="button"
233
+ className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonBlockClass", "kcButtonLargeClass")}
234
+ value={msgStr("passkey-doAuthenticate")}
235
+ />
236
+ </>
237
+ )}
194
238
  </Template>
195
239
  );
196
240
  }
@@ -0,0 +1,71 @@
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
+ import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
6
+
7
+ type KcContextLike = {
8
+ url: {
9
+ resourcesPath: string;
10
+ };
11
+ isUserIdentified: "true" | "false";
12
+ challenge: string;
13
+ userVerification: KcContext.WebauthnAuthenticate["userVerification"];
14
+ rpId: string;
15
+ createTimeout: number | string;
16
+ };
17
+
18
+ assert<keyof KcContextLike extends keyof KcContext.Login ? true : false>();
19
+ assert<KcContext.Login extends KcContextLike ? true : false>();
20
+
21
+ type I18nLike = {
22
+ msgStr: (key: "webauthn-unsupported-browser-text") => string;
23
+ isFetchingTranslations: boolean;
24
+ };
25
+
26
+ export function useScript(params: { webAuthnButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
27
+ const { webAuthnButtonId, kcContext, i18n } = params;
28
+
29
+ const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
30
+
31
+ const { msgStr, isFetchingTranslations } = i18n;
32
+
33
+ const { insertScriptTags } = useInsertScriptTags({
34
+ componentOrHookName: "Login",
35
+ scriptTags: [
36
+ {
37
+ type: "module",
38
+ textContent: () => `
39
+
40
+ import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
41
+ const authButton = document.getElementById('${webAuthnButtonId}');
42
+ authButton.addEventListener("click", function() {
43
+ const input = {
44
+ isUserIdentified : ${isUserIdentified},
45
+ challenge : '${challenge}',
46
+ userVerification : '${userVerification}',
47
+ rpId : '${rpId}',
48
+ createTimeout : ${createTimeout},
49
+ errmsg : ${JSON.stringify(msgStr("webauthn-unsupported-browser-text"))}
50
+ };
51
+ authenticateByWebAuthn(input);
52
+ });
53
+ `
54
+ }
55
+ ]
56
+ });
57
+
58
+ useEffect(() => {
59
+ if (isFetchingTranslations) {
60
+ return;
61
+ }
62
+
63
+ (async () => {
64
+ await waitForElementMountedOnDom({
65
+ elementId: webAuthnButtonId
66
+ });
67
+
68
+ insertScriptTags();
69
+ })();
70
+ }, [isFetchingTranslations]);
71
+ }
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Password step (login-password.ftl) for flows where username is already captured.
3
+ * Adds conditional WebAuthn passkey authenticate section when enabled.
4
+ */
1
5
  import type { JSX } from "keycloakify/tools/JSX";
2
6
  import { useState } from "react";
3
7
  import { kcSanitize } from "keycloakify/lib/kcSanitize";
@@ -7,6 +11,7 @@ import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
7
11
  import type { PageProps } from "keycloakify/login/pages/PageProps";
8
12
  import type { KcContext } from "../KcContext";
9
13
  import type { I18n } from "../i18n";
14
+ import { useScript } from "keycloakify/login/pages/LoginPassword.useScript";
10
15
 
11
16
  export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>, I18n>) {
12
17
  const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@@ -16,12 +21,20 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
16
21
  classes
17
22
  });
18
23
 
19
- const { realm, url, messagesPerField } = kcContext;
24
+ const { realm, url, messagesPerField, enableWebAuthnConditionalUI, authenticators } = kcContext;
20
25
 
21
26
  const { msg, msgStr } = i18n;
22
27
 
23
28
  const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
24
29
 
30
+ const webAuthnButtonId = "authenticateWebAuthnButton";
31
+
32
+ useScript({
33
+ webAuthnButtonId,
34
+ kcContext,
35
+ i18n
36
+ });
37
+
25
38
  return (
26
39
  <Template
27
40
  kcContext={kcContext}
@@ -98,6 +111,36 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
98
111
  </form>
99
112
  </div>
100
113
  </div>
114
+ {enableWebAuthnConditionalUI && (
115
+ <>
116
+ <form id="webauth" action={url.loginAction} method="post">
117
+ <input type="hidden" id="clientDataJSON" name="clientDataJSON" />
118
+ <input type="hidden" id="authenticatorData" name="authenticatorData" />
119
+ <input type="hidden" id="signature" name="signature" />
120
+ <input type="hidden" id="credentialId" name="credentialId" />
121
+ <input type="hidden" id="userHandle" name="userHandle" />
122
+ <input type="hidden" id="error" name="error" />
123
+ </form>
124
+
125
+ {authenticators !== undefined && authenticators.authenticators.length !== 0 && (
126
+ <>
127
+ <form id="authn_select" className={kcClsx("kcFormClass")}>
128
+ {authenticators.authenticators.map((authenticator, i) => (
129
+ <input key={i} type="hidden" name="authn_use_chk" readOnly value={authenticator.credentialId} />
130
+ ))}
131
+ </form>
132
+ </>
133
+ )}
134
+ <br />
135
+
136
+ <input
137
+ id={webAuthnButtonId}
138
+ type="button"
139
+ className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonBlockClass", "kcButtonLargeClass")}
140
+ value={msgStr("passkey-doAuthenticate")}
141
+ />
142
+ </>
143
+ )}
101
144
  </Template>
102
145
  );
103
146
  }
@@ -0,0 +1,71 @@
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
+ import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
6
+
7
+ type KcContextLike = {
8
+ url: {
9
+ resourcesPath: string;
10
+ };
11
+ isUserIdentified: "true" | "false";
12
+ challenge: string;
13
+ userVerification: KcContext.WebauthnAuthenticate["userVerification"];
14
+ rpId: string;
15
+ createTimeout: number | string;
16
+ };
17
+
18
+ assert<keyof KcContextLike extends keyof KcContext.LoginPassword ? true : false>();
19
+ assert<KcContext.LoginPassword extends KcContextLike ? true : false>();
20
+
21
+ type I18nLike = {
22
+ msgStr: (key: "webauthn-unsupported-browser-text") => string;
23
+ isFetchingTranslations: boolean;
24
+ };
25
+
26
+ export function useScript(params: { webAuthnButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
27
+ const { webAuthnButtonId, kcContext, i18n } = params;
28
+
29
+ const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
30
+
31
+ const { msgStr, isFetchingTranslations } = i18n;
32
+
33
+ const { insertScriptTags } = useInsertScriptTags({
34
+ componentOrHookName: "LoginPassword",
35
+ scriptTags: [
36
+ {
37
+ type: "module",
38
+ textContent: () => `
39
+
40
+ import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
41
+ const authButton = document.getElementById('${webAuthnButtonId}');
42
+ authButton.addEventListener("click", function() {
43
+ const input = {
44
+ isUserIdentified : ${isUserIdentified},
45
+ challenge : '${challenge}',
46
+ userVerification : '${userVerification}',
47
+ rpId : '${rpId}',
48
+ createTimeout : ${createTimeout},
49
+ errmsg : ${JSON.stringify(msgStr("webauthn-unsupported-browser-text"))}
50
+ };
51
+ authenticateByWebAuthn(input);
52
+ });
53
+ `
54
+ }
55
+ ]
56
+ });
57
+
58
+ useEffect(() => {
59
+ if (isFetchingTranslations) {
60
+ return;
61
+ }
62
+
63
+ (async () => {
64
+ await waitForElementMountedOnDom({
65
+ elementId: webAuthnButtonId
66
+ });
67
+
68
+ insertScriptTags();
69
+ })();
70
+ }, [isFetchingTranslations]);
71
+ }
@@ -21,10 +21,10 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
21
21
 
22
22
  const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
23
23
 
24
- const authButtonId = "authenticateWebAuthnButton";
24
+ const webAuthnButtonId = "authenticateWebAuthnButton";
25
25
 
26
26
  useScript({
27
- authButtonId,
27
+ webAuthnButtonId,
28
28
  kcContext,
29
29
  i18n
30
30
  });
@@ -164,7 +164,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
164
164
  <input type="hidden" id="error" name="error" />
165
165
  </form>
166
166
 
167
- {authenticators !== undefined && Object.keys(authenticators).length !== 0 && (
167
+ {authenticators !== undefined && authenticators.authenticators.length !== 0 && (
168
168
  <>
169
169
  <form id="authn_select" className={kcClsx("kcFormClass")}>
170
170
  {authenticators.authenticators.map((authenticator, i) => (
@@ -174,13 +174,13 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
174
174
  </>
175
175
  )}
176
176
  <br />
177
-
177
+
178
178
  <input
179
- id={authButtonId}
179
+ id={webAuthnButtonId}
180
180
  type="button"
181
181
  className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonBlockClass", "kcButtonLargeClass")}
182
182
  value={msgStr("passkey-doAuthenticate")}
183
- />
183
+ />
184
184
  </>
185
185
  )}
186
186
  </Template>
@@ -23,8 +23,8 @@ type I18nLike = {
23
23
  isFetchingTranslations: boolean;
24
24
  };
25
25
 
26
- export function useScript(params: { authButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
27
- const { authButtonId, kcContext, i18n } = params;
26
+ export function useScript(params: { webAuthnButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
27
+ const { webAuthnButtonId, kcContext, i18n } = params;
28
28
 
29
29
  const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
30
30
 
@@ -38,7 +38,7 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
38
38
  textContent: () => `
39
39
 
40
40
  import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
41
- const authButton = document.getElementById('${authButtonId}');
41
+ const authButton = document.getElementById('${webAuthnButtonId}');
42
42
  authButton.addEventListener("click", function() {
43
43
  const input = {
44
44
  isUserIdentified : ${isUserIdentified},
@@ -62,7 +62,7 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
62
62
 
63
63
  (async () => {
64
64
  await waitForElementMountedOnDom({
65
- elementId: authButtonId
65
+ elementId: webAuthnButtonId
66
66
  });
67
67
 
68
68
  insertScriptTags();
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { createKcPageStory } from "../KcPageStory";
4
+
5
+ const { KcPageStory } = createKcPageStory({ pageId: "link-idp-action.ftl" });
6
+
7
+ const meta = {
8
+ title: "login/link-idp-action.ftl",
9
+ component: KcPageStory
10
+ } satisfies Meta<typeof KcPageStory>;
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ export const Default: Story = {
17
+ render: () => (
18
+ <KcPageStory
19
+ kcContext={{
20
+ idpDisplayName: "GitHub",
21
+ url: { loginAction: "/mock-login-action" }
22
+ }}
23
+ />
24
+ )
25
+ };
26
+
27
+ export const DifferentProvider: Story = {
28
+ render: () => (
29
+ <KcPageStory
30
+ kcContext={{
31
+ idpDisplayName: "Google",
32
+ url: { loginAction: "/custom-login-action" }
33
+ }}
34
+ />
35
+ )
36
+ };
@@ -1018,8 +1018,8 @@ __nccwpck_require__.r(__webpack_exports__);
1018
1018
  /* harmony export */ "CONTAINER_NAME": () => (/* binding */ CONTAINER_NAME),
1019
1019
  /* harmony export */ "CUSTOM_HANDLER_ENV_NAMES": () => (/* binding */ CUSTOM_HANDLER_ENV_NAMES),
1020
1020
  /* harmony export */ "FALLBACK_LANGUAGE_TAG": () => (/* binding */ FALLBACK_LANGUAGE_TAG),
1021
+ /* harmony export */ "KEYCLOAKIFY_LOGGING_JAR_BASENAME": () => (/* binding */ KEYCLOAKIFY_LOGGING_JAR_BASENAME),
1021
1022
  /* harmony export */ "KEYCLOAKIFY_LOGGING_VERSION": () => (/* binding */ KEYCLOAKIFY_LOGGING_VERSION),
1022
- /* harmony export */ "KEYCLOAKIFY_LOGIN_JAR_BASENAME": () => (/* binding */ KEYCLOAKIFY_LOGIN_JAR_BASENAME),
1023
1023
  /* harmony export */ "KEYCLOAKIFY_SPA_DEV_SERVER_PORT": () => (/* binding */ KEYCLOAKIFY_SPA_DEV_SERVER_PORT),
1024
1024
  /* harmony export */ "KEYCLOAK_THEME": () => (/* binding */ KEYCLOAK_THEME),
1025
1025
  /* harmony export */ "LOGIN_THEME_PAGE_IDS": () => (/* binding */ LOGIN_THEME_PAGE_IDS),
@@ -1057,6 +1057,7 @@ const LOGIN_THEME_PAGE_IDS = [
1057
1057
  "login-otp.ftl",
1058
1058
  "login-update-profile.ftl",
1059
1059
  "login-update-password.ftl",
1060
+ "link-idp-action.ftl",
1060
1061
  "login-idp-link-confirm.ftl",
1061
1062
  "login-idp-link-email.ftl",
1062
1063
  "login-page-expired.ftl",
@@ -1097,7 +1098,7 @@ const CUSTOM_HANDLER_ENV_NAMES = {
1097
1098
  const KEYCLOAK_THEME = "keycloak-theme";
1098
1099
  const KEYCLOAKIFY_SPA_DEV_SERVER_PORT = "KEYCLOAKIFY_SPA_DEV_SERVER_PORT";
1099
1100
  const KEYCLOAKIFY_LOGGING_VERSION = "1.0.3";
1100
- const KEYCLOAKIFY_LOGIN_JAR_BASENAME = `keycloakify-logging-${KEYCLOAKIFY_LOGGING_VERSION}.jar`;
1101
+ const KEYCLOAKIFY_LOGGING_JAR_BASENAME = `keycloakify-logging-${KEYCLOAKIFY_LOGGING_VERSION}.jar`;
1101
1102
  const TEST_APP_URL = "https://my-theme.keycloakify.dev";
1102
1103
  //# sourceMappingURL=constants.js.map
1103
1104