@keycloakify/login-ui 250004.2.2 → 250004.3.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.
@@ -265,6 +265,10 @@ export declare namespace KcContext {
265
265
  };
266
266
  usernameHidden?: boolean;
267
267
  social?: Login["social"];
268
+ enableWebAuthnConditionalUI?: boolean;
269
+ authenticators?: {
270
+ authenticators: WebauthnAuthenticate.WebauthnAuthenticator[];
271
+ };
268
272
  };
269
273
  type LoginPassword = Common & {
270
274
  pageId: "login-password.ftl";
@@ -5,6 +5,7 @@ import { useKcClsx } from "@keycloakify/login-ui/useKcClsx";
5
5
  import { useKcContext } from "../../KcContext";
6
6
  import { useI18n } from "../../i18n";
7
7
  import { Template } from "../../components/Template";
8
+ import { useScript } from "./useScript";
8
9
 
9
10
  export function Page() {
10
11
  const { kcContext } = useKcContext();
@@ -12,13 +13,26 @@ export function Page() {
12
13
 
13
14
  const { kcClsx } = useKcClsx();
14
15
 
15
- const { social, realm, url, usernameHidden, login, registrationDisabled, messagesPerField } =
16
- kcContext;
16
+ const {
17
+ social,
18
+ realm,
19
+ url,
20
+ usernameHidden,
21
+ login,
22
+ registrationDisabled,
23
+ messagesPerField,
24
+ enableWebAuthnConditionalUI,
25
+ authenticators
26
+ } = kcContext;
17
27
 
18
28
  const { msg, msgStr } = useI18n();
19
29
 
20
30
  const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
21
31
 
32
+ const authButtonId = "authenticateWebAuthnButton";
33
+
34
+ useScript({ authButtonId });
35
+
22
36
  return (
23
37
  <Template
24
38
  displayMessage={!messagesPerField.existsError("username")}
@@ -171,6 +185,45 @@ export function Page() {
171
185
  )}
172
186
  </div>
173
187
  </div>
188
+ {enableWebAuthnConditionalUI && (
189
+ <>
190
+ <form id="webauth" action={url.loginAction} method="post">
191
+ <input type="hidden" id="clientDataJSON" name="clientDataJSON" />
192
+ <input type="hidden" id="authenticatorData" name="authenticatorData" />
193
+ <input type="hidden" id="signature" name="signature" />
194
+ <input type="hidden" id="credentialId" name="credentialId" />
195
+ <input type="hidden" id="userHandle" name="userHandle" />
196
+ <input type="hidden" id="error" name="error" />
197
+ </form>
198
+
199
+ {authenticators !== undefined && Object.keys(authenticators).length !== 0 && (
200
+ <>
201
+ <form id="authn_select" className={kcClsx("kcFormClass")}>
202
+ {authenticators.authenticators.map((authenticator, i) => (
203
+ <input
204
+ key={i}
205
+ type="hidden"
206
+ name="authn_use_chk"
207
+ readOnly
208
+ value={authenticator.credentialId}
209
+ />
210
+ ))}
211
+ </form>
212
+ </>
213
+ )}
214
+ <a
215
+ id={authButtonId}
216
+ href="#"
217
+ className={kcClsx(
218
+ "kcButtonSecondaryClass",
219
+ "kcButtonBlockClass",
220
+ "kcMarginTopClass"
221
+ )}
222
+ >
223
+ {msg("passkey-doAuthenticate")}
224
+ </a>
225
+ </>
226
+ )}
174
227
  </Template>
175
228
  );
176
229
  }
@@ -0,0 +1,64 @@
1
+ import { useEffect } from "react";
2
+ import { assert } from "tsafe/assert";
3
+ import { useInsertScriptTags } from "@keycloakify/login-ui/tools/useInsertScriptTags";
4
+ import { waitForElementMountedOnDom } from "@keycloakify/login-ui/tools/waitForElementMountedOnDom";
5
+ // NOTE: If you are in a Vite project you can use `import.meta.env.BASE_URL` directly, this is a shim to support Webpack.
6
+ import { BASE_URL } from "../../../kc.gen";
7
+ import { useI18n } from "../../i18n";
8
+ import { useKcContext } from "../../KcContext";
9
+
10
+ export function useScript(params: { authButtonId: string }) {
11
+ const { authButtonId } = params;
12
+
13
+ const { kcContext } = useKcContext();
14
+ assert(kcContext.pageId === "login-username.ftl");
15
+
16
+ const { msgStr, isFetchingTranslations } = useI18n();
17
+
18
+ const { insertScriptTags } = useInsertScriptTags({
19
+ effectId: "LoginRecoveryAuthnCodeConfigLoginUsername",
20
+ scriptTags: [
21
+ {
22
+ type: "module",
23
+ textContent: () => `
24
+ import { authenticateByWebAuthn } from "${BASE_URL}keycloak-theme/login/js/webauthnAuthenticate.js";
25
+ import { initAuthenticate } from "${BASE_URL}keycloak-theme/login/js/passkeysConditionalAuth.js";
26
+
27
+ const authButton = document.getElementById("${authButtonId}");
28
+ const input = {
29
+ isUserIdentified : ${kcContext.isUserIdentified},
30
+ challenge : ${JSON.stringify(kcContext.challenge)},
31
+ userVerification : ${JSON.stringify(kcContext.userVerification)},
32
+ rpId : ${JSON.stringify(kcContext.rpId)},
33
+ createTimeout : ${kcContext.createTimeout}
34
+ };
35
+ authButton.addEventListener("click", () => {
36
+ authenticateByWebAuthn({
37
+ ...input,
38
+ errmsg : ${JSON.stringify(msgStr("webauthn-unsupported-browser-text"))}
39
+ });
40
+ });
41
+
42
+ initAuthenticate({
43
+ ...input,
44
+ errmsg : ${JSON.stringify(msgStr("passkey-unsupported-browser-text"))}
45
+ });
46
+ `
47
+ }
48
+ ]
49
+ });
50
+
51
+ useEffect(() => {
52
+ if (isFetchingTranslations) {
53
+ return;
54
+ }
55
+
56
+ (async () => {
57
+ await waitForElementMountedOnDom({
58
+ elementId: authButtonId
59
+ });
60
+
61
+ insertScriptTags();
62
+ })();
63
+ }, [isFetchingTranslations]);
64
+ }
@@ -1,4 +1,4 @@
1
- import { base64url } from "rfc4648";
1
+ import { base64url } from "./rfc4648.js";
2
2
  import { returnSuccess, returnFailure } from "./webauthnAuthenticate.js";
3
3
 
4
4
  export function initAuthenticate(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keycloakify/login-ui",
3
- "version": "250004.2.2",
3
+ "version": "250004.3.0",
4
4
  "description": "React implementation of Keycloak Login v2 for Keycloakify",
5
5
  "repository": {
6
6
  "type": "git",
@@ -327,6 +327,11 @@ export declare namespace KcContext {
327
327
  };
328
328
  usernameHidden?: boolean;
329
329
  social?: Login["social"];
330
+
331
+ enableWebAuthnConditionalUI?: boolean;
332
+ authenticators?: {
333
+ authenticators: WebauthnAuthenticate.WebauthnAuthenticator[];
334
+ };
330
335
  };
331
336
 
332
337
  export type LoginPassword = Common & {