@keycloakify/login-ui 250004.2.3 → 250004.3.2

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,15 @@ 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
+ };
272
+ isUserIdentified: "true" | "false";
273
+ challenge: string;
274
+ userVerification: UserVerificationRequirement | "not specified";
275
+ rpId: string;
276
+ createTimeout: string | number;
268
277
  };
269
278
  type LoginPassword = Common & {
270
279
  pageId: "login-password.ftl";
@@ -219,7 +219,7 @@ export const kcContextMocks = [
219
219
  } })),
220
220
  id(Object.assign(Object.assign({}, kcContextCommonMock), { pageId: "login-username.ftl", url: loginUrl, realm: Object.assign(Object.assign({}, kcContextCommonMock.realm), { loginWithEmailAllowed: true, rememberMe: true, password: true, resetPasswordAllowed: true, registrationAllowed: true }), social: {
221
221
  displayInfo: true
222
- }, usernameHidden: false, login: {}, registrationDisabled: false })),
222
+ }, usernameHidden: false, login: {}, registrationDisabled: false, challenge: "", userVerification: "not specified", rpId: "", createTimeout: "0", isUserIdentified: "false" })),
223
223
  id(Object.assign(Object.assign({}, kcContextCommonMock), { pageId: "login-password.ftl", url: loginUrl, realm: Object.assign(Object.assign({}, kcContextCommonMock.realm), { resetPasswordAllowed: true }) })),
224
224
  id(Object.assign(Object.assign({}, kcContextCommonMock), { pageId: "webauthn-authenticate.ftl", url: loginUrl, authenticators: {
225
225
  authenticators: []
package/core/kcClsx.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type ClassKey = "kcBodyClass" | "kcHeaderWrapperClass" | "kcLocaleWrapperClass" | "kcInfoAreaWrapperClass" | "kcFormButtonsWrapperClass" | "kcFormOptionsWrapperClass" | "kcCheckboxInputClass" | "kcLocaleDropDownClass" | "kcLocaleListItemClass" | "kcContentWrapperClass" | "kcLogoIdP-facebook" | "kcAuthenticatorOTPClass" | "kcLogoIdP-bitbucket" | "kcAuthenticatorWebAuthnClass" | "kcWebAuthnDefaultIcon" | "kcLogoIdP-stackoverflow" | "kcSelectAuthListItemClass" | "kcLogoIdP-microsoft" | "kcLoginOTPListItemHeaderClass" | "kcLocaleItemClass" | "kcLoginOTPListItemIconBodyClass" | "kcInputHelperTextAfterClass" | "kcFormClass" | "kcSelectAuthListClass" | "kcInputClassRadioCheckboxLabelDisabled" | "kcSelectAuthListItemIconClass" | "kcRecoveryCodesWarning" | "kcFormSettingClass" | "kcWebAuthnBLE" | "kcInputWrapperClass" | "kcSelectAuthListItemArrowIconClass" | "kcFeedbackAreaClass" | "kcFormPasswordVisibilityButtonClass" | "kcLogoIdP-google" | "kcCheckLabelClass" | "kcSelectAuthListItemFillClass" | "kcAuthenticatorDefaultClass" | "kcLogoIdP-gitlab" | "kcFormAreaClass" | "kcFormButtonsClass" | "kcInputClassRadioLabel" | "kcAuthenticatorWebAuthnPasswordlessClass" | "kcSelectAuthListItemHeadingClass" | "kcInfoAreaClass" | "kcContainerClass" | "kcSelectAuthListItemTitle" | "kcHtmlClass" | "kcLoginOTPListItemTitleClass" | "kcLogoIdP-openshift-v4" | "kcWebAuthnUnknownIcon" | "kcFormSocialAccountNameClass" | "kcLogoIdP-openshift-v3" | "kcLoginOTPListInputClass" | "kcWebAuthnUSB" | "kcInputClassRadio" | "kcWebAuthnKeyIcon" | "kcFeedbackInfoIcon" | "kcCommonLogoIdP" | "kcRecoveryCodesActions" | "kcFormGroupHeader" | "kcFormSocialAccountSectionClass" | "kcLogoIdP-instagram" | "kcAlertClass" | "kcHeaderClass" | "kcLabelWrapperClass" | "kcFormPasswordVisibilityIconShow" | "kcFormSocialAccountLinkClass" | "kcLocaleMainClass" | "kcInputGroup" | "kcTextareaClass" | "kcButtonBlockClass" | "kcButtonClass" | "kcWebAuthnNFC" | "kcLocaleClass" | "kcInputClassCheckboxInput" | "kcFeedbackErrorIcon" | "kcInputLargeClass" | "kcInputErrorMessageClass" | "kcRecoveryCodesList" | "kcFormSocialAccountListClass" | "kcAlertTitleClass" | "kcAuthenticatorPasswordClass" | "kcCheckInputClass" | "kcLogoIdP-linkedin" | "kcLogoIdP-twitter" | "kcFeedbackWarningIcon" | "kcResetFlowIcon" | "kcSelectAuthListItemIconPropertyClass" | "kcFeedbackSuccessIcon" | "kcLoginOTPListClass" | "kcSrOnlyClass" | "kcFormSocialAccountListGridClass" | "kcButtonDefaultClass" | "kcFormGroupErrorClass" | "kcSelectAuthListItemDescriptionClass" | "kcSelectAuthListItemBodyClass" | "kcWebAuthnInternal" | "kcSelectAuthListItemArrowClass" | "kcCheckClass" | "kcContentClass" | "kcLogoClass" | "kcLoginOTPListItemIconClass" | "kcLoginClass" | "kcSignUpClass" | "kcButtonLargeClass" | "kcFormCardClass" | "kcLocaleListClass" | "kcInputClass" | "kcFormGroupClass" | "kcLogoIdP-paypal" | "kcInputClassCheckbox" | "kcRecoveryCodesConfirmation" | "kcFormPasswordVisibilityIconHide" | "kcInputClassRadioInput" | "kcFormSocialAccountListButtonClass" | "kcInputClassCheckboxLabel" | "kcFormOptionsClass" | "kcFormHeaderClass" | "kcFormSocialAccountGridItem" | "kcButtonPrimaryClass" | "kcInputHelperTextBeforeClass" | "kcLogoIdP-github" | "kcLabelClass";
1
+ export type ClassKey = "kcBodyClass" | "kcHeaderWrapperClass" | "kcLocaleWrapperClass" | "kcInfoAreaWrapperClass" | "kcFormButtonsWrapperClass" | "kcFormOptionsWrapperClass" | "kcCheckboxInputClass" | "kcLocaleDropDownClass" | "kcLocaleListItemClass" | "kcContentWrapperClass" | "kcLogoIdP-facebook" | "kcAuthenticatorOTPClass" | "kcLogoIdP-bitbucket" | "kcAuthenticatorWebAuthnClass" | "kcWebAuthnDefaultIcon" | "kcLogoIdP-stackoverflow" | "kcSelectAuthListItemClass" | "kcLogoIdP-microsoft" | "kcLoginOTPListItemHeaderClass" | "kcLocaleItemClass" | "kcLoginOTPListItemIconBodyClass" | "kcInputHelperTextAfterClass" | "kcFormClass" | "kcSelectAuthListClass" | "kcInputClassRadioCheckboxLabelDisabled" | "kcSelectAuthListItemIconClass" | "kcRecoveryCodesWarning" | "kcFormSettingClass" | "kcWebAuthnBLE" | "kcInputWrapperClass" | "kcSelectAuthListItemArrowIconClass" | "kcFeedbackAreaClass" | "kcFormPasswordVisibilityButtonClass" | "kcLogoIdP-google" | "kcCheckLabelClass" | "kcSelectAuthListItemFillClass" | "kcAuthenticatorDefaultClass" | "kcLogoIdP-gitlab" | "kcFormAreaClass" | "kcFormButtonsClass" | "kcInputClassRadioLabel" | "kcAuthenticatorWebAuthnPasswordlessClass" | "kcSelectAuthListItemHeadingClass" | "kcInfoAreaClass" | "kcContainerClass" | "kcSelectAuthListItemTitle" | "kcHtmlClass" | "kcLoginOTPListItemTitleClass" | "kcLogoIdP-openshift-v4" | "kcWebAuthnUnknownIcon" | "kcFormSocialAccountNameClass" | "kcLogoIdP-openshift-v3" | "kcLoginOTPListInputClass" | "kcWebAuthnUSB" | "kcInputClassRadio" | "kcWebAuthnKeyIcon" | "kcFeedbackInfoIcon" | "kcCommonLogoIdP" | "kcRecoveryCodesActions" | "kcFormGroupHeader" | "kcFormSocialAccountSectionClass" | "kcLogoIdP-instagram" | "kcAlertClass" | "kcHeaderClass" | "kcLabelWrapperClass" | "kcFormPasswordVisibilityIconShow" | "kcFormSocialAccountLinkClass" | "kcLocaleMainClass" | "kcInputGroup" | "kcTextareaClass" | "kcButtonBlockClass" | "kcButtonClass" | "kcWebAuthnNFC" | "kcLocaleClass" | "kcInputClassCheckboxInput" | "kcFeedbackErrorIcon" | "kcInputLargeClass" | "kcInputErrorMessageClass" | "kcRecoveryCodesList" | "kcFormSocialAccountListClass" | "kcAlertTitleClass" | "kcAuthenticatorPasswordClass" | "kcCheckInputClass" | "kcLogoIdP-linkedin" | "kcLogoIdP-twitter" | "kcFeedbackWarningIcon" | "kcResetFlowIcon" | "kcSelectAuthListItemIconPropertyClass" | "kcFeedbackSuccessIcon" | "kcLoginOTPListClass" | "kcSrOnlyClass" | "kcFormSocialAccountListGridClass" | "kcButtonDefaultClass" | "kcFormGroupErrorClass" | "kcSelectAuthListItemDescriptionClass" | "kcSelectAuthListItemBodyClass" | "kcWebAuthnInternal" | "kcSelectAuthListItemArrowClass" | "kcCheckClass" | "kcContentClass" | "kcLogoClass" | "kcLoginOTPListItemIconClass" | "kcLoginClass" | "kcSignUpClass" | "kcButtonLargeClass" | "kcFormCardClass" | "kcLocaleListClass" | "kcInputClass" | "kcFormGroupClass" | "kcLogoIdP-paypal" | "kcInputClassCheckbox" | "kcRecoveryCodesConfirmation" | "kcFormPasswordVisibilityIconHide" | "kcInputClassRadioInput" | "kcFormSocialAccountListButtonClass" | "kcInputClassCheckboxLabel" | "kcFormOptionsClass" | "kcFormHeaderClass" | "kcFormSocialAccountGridItem" | "kcButtonPrimaryClass" | "kcButtonSecondaryClass" | "kcInputHelperTextBeforeClass" | "kcLogoIdP-github" | "kcLabelClass";
2
2
  export declare const getKcClsx: (params: {
3
3
  doUseDefaultCss: boolean;
4
4
  classes: Partial<Record<ClassKey, string>> | undefined;
package/core/kcClsx.js CHANGED
@@ -121,6 +121,7 @@ export const { getKcClsx } = createGetKcClsx({
121
121
  kcFormHeaderClass: "login-pf-header",
122
122
  kcFormSocialAccountGridItem: "pf-l-grid__item",
123
123
  kcButtonPrimaryClass: "pf-m-primary",
124
+ kcButtonSecondaryClass: "pf-m-secondary",
124
125
  kcInputHelperTextBeforeClass: "pf-c-form__helper-text pf-c-form__helper-text-before",
125
126
  "kcLogoIdP-github": "fa fa-github",
126
127
  kcLabelClass: "pf-c-form__label pf-c-form__label-text"
@@ -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
+ {authenticators !== undefined && Object.keys(authenticators).length !== 0 && (
199
+ <>
200
+ <form id="authn_select" className={kcClsx("kcFormClass")}>
201
+ {authenticators.authenticators.map((authenticator, i) => (
202
+ <input
203
+ key={i}
204
+ type="hidden"
205
+ name="authn_use_chk"
206
+ readOnly
207
+ value={authenticator.credentialId}
208
+ />
209
+ ))}
210
+ </form>
211
+ </>
212
+ )}
213
+ <br /> {/* We use a br here because kcMarginTopClass is not defined in login v1 */}
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keycloakify/login-ui",
3
- "version": "250004.2.3",
3
+ "version": "250004.3.2",
4
4
  "description": "React implementation of Keycloak Login v2 for Keycloakify",
5
5
  "repository": {
6
6
  "type": "git",
@@ -327,6 +327,16 @@ 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
+ };
335
+ isUserIdentified: "true" | "false";
336
+ challenge: string;
337
+ userVerification: UserVerificationRequirement | "not specified";
338
+ rpId: string;
339
+ createTimeout: string | number;
330
340
  };
331
341
 
332
342
  export type LoginPassword = Common & {
@@ -333,7 +333,13 @@ export const kcContextMocks = [
333
333
  },
334
334
  usernameHidden: false,
335
335
  login: {},
336
- registrationDisabled: false
336
+ registrationDisabled: false,
337
+
338
+ challenge: "",
339
+ userVerification: "not specified",
340
+ rpId: "",
341
+ createTimeout: "0",
342
+ isUserIdentified: "false"
337
343
  }),
338
344
  id<KcContext.LoginPassword>({
339
345
  ...kcContextCommonMock,
@@ -121,6 +121,7 @@ export type ClassKey =
121
121
  | "kcFormHeaderClass"
122
122
  | "kcFormSocialAccountGridItem"
123
123
  | "kcButtonPrimaryClass"
124
+ | "kcButtonSecondaryClass"
124
125
  | "kcInputHelperTextBeforeClass"
125
126
  | "kcLogoIdP-github"
126
127
  | "kcLabelClass";
@@ -249,6 +250,7 @@ export const { getKcClsx } = createGetKcClsx<ClassKey>({
249
250
  kcFormHeaderClass: "login-pf-header",
250
251
  kcFormSocialAccountGridItem: "pf-l-grid__item",
251
252
  kcButtonPrimaryClass: "pf-m-primary",
253
+ kcButtonSecondaryClass: "pf-m-secondary",
252
254
  kcInputHelperTextBeforeClass: "pf-c-form__helper-text pf-c-form__helper-text-before",
253
255
  "kcLogoIdP-github": "fa fa-github",
254
256
  kcLabelClass: "pf-c-form__label pf-c-form__label-text"