From 138208bf8219c13144c655c18dfb7dd6ab88fca0 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 13 Apr 2024 01:26:41 +0200 Subject: [PATCH 001/631] Update prepare template for Keycloak 24 --- src/account/Template.tsx | 14 +++++---- src/lib/usePrepareTemplate.ts | 32 +++++++++++-------- src/login/Template.tsx | 44 ++++++++++++++++++++++----- src/login/kcContext/KcContext.ts | 6 ++++ src/login/kcContext/kcContextMocks.ts | 3 +- src/tools/headInsert.ts | 26 ++++++++++++---- 6 files changed, 92 insertions(+), 33 deletions(-) diff --git a/src/account/Template.tsx b/src/account/Template.tsx index 1c81f6ba..dab8aa99 100644 --- a/src/account/Template.tsx +++ b/src/account/Template.tsx @@ -16,12 +16,14 @@ export default function Template(props: TemplateProps) { const { locale, url, features, realm, message, referrer } = kcContext; const { isReady } = usePrepareTemplate({ - "doFetchDefaultThemeResources": doUseDefaultCss, - "styles": [ - `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`, - `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`, - `${url.resourcesPath}/css/account.css` - ], + "styles": !doUseDefaultCss + ? [] + : [ + `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`, + `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`, + `${url.resourcesPath}/css/account.css` + ], + "scripts": [], "htmlClassName": getClassName("kcHtmlClass"), "bodyClassName": clsx("admin-console", "user", getClassName("kcBodyClass")), "htmlLangProperty": locale?.currentLanguageTag, diff --git a/src/lib/usePrepareTemplate.ts b/src/lib/usePrepareTemplate.ts index 98f0bbd6..bca15c35 100644 --- a/src/lib/usePrepareTemplate.ts +++ b/src/lib/usePrepareTemplate.ts @@ -4,17 +4,27 @@ import { clsx } from "keycloakify/tools/clsx"; import { assert } from "tsafe/assert"; export function usePrepareTemplate(params: { - doFetchDefaultThemeResources: boolean; - styles?: string[]; - scripts?: string[]; + styles: string[]; + scripts: { + isModule: boolean; + source: + | { + type: "url"; + src: string; + } + | { + type: "inline"; + code: string; + }; + }[]; htmlClassName: string | undefined; bodyClassName: string | undefined; - htmlLangProperty?: string | undefined; - documentTitle?: string; + htmlLangProperty: string | undefined; + documentTitle: string | undefined; }) { - const { doFetchDefaultThemeResources, styles = [], scripts = [], htmlClassName, bodyClassName, htmlLangProperty, documentTitle } = params; + const { styles, scripts, htmlClassName, bodyClassName, htmlLangProperty, documentTitle } = params; - const [isReady, setReady] = useReducer(() => true, !doFetchDefaultThemeResources); + const [isReady, setReady] = useReducer(() => true, styles.length === 0 && scripts.length === 0); useEffect(() => { if (htmlLangProperty === undefined) { @@ -35,10 +45,6 @@ export function usePrepareTemplate(params: { }, [documentTitle]); useEffect(() => { - if (!doFetchDefaultThemeResources) { - return; - } - let isUnmounted = false; const removeArray: (() => void)[] = []; @@ -64,10 +70,10 @@ export function usePrepareTemplate(params: { setReady(); })(); - scripts.forEach(src => { + scripts.forEach(script => { const { remove } = headInsert({ "type": "javascript", - src + ...script }); removeArray.push(remove); diff --git a/src/login/Template.tsx b/src/login/Template.tsx index 4d6646bb..c983afb6 100644 --- a/src/login/Template.tsx +++ b/src/login/Template.tsx @@ -27,15 +27,45 @@ export default function Template(props: TemplateProps) { const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; - const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext; + const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession } = kcContext; const { isReady } = usePrepareTemplate({ - "doFetchDefaultThemeResources": doUseDefaultCss, - "styles": [ - `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`, - `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`, - `${url.resourcesCommonPath}/lib/zocial/zocial.css`, - `${url.resourcesPath}/css/login.css` + "styles": !doUseDefaultCss + ? [] + : [ + `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`, + `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`, + `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`, + `${url.resourcesCommonPath}/lib/pficon/pficon.css`, + `${url.resourcesPath}/css/login.css` + ], + "scripts": [ + { + "isModule": true, + "source": { + "type": "url", + "src": `${url.resourcesPath}/js/menu-button-links.js` + } + }, + ...(authenticationSession === undefined + ? [] + : [ + { + "isModule": true, + "source": { + "type": "inline" as const, + "code": [ + `import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`, + ``, + `checkCookiesAndSetTimer(`, + ` "${authenticationSession.authSessionId}",`, + ` "${authenticationSession.tabId}",`, + ` "${url.ssoLoginInOtherTabsUrl}"`, + `);` + ].join("\n") + } + } + ]) ], "htmlClassName": getClassName("kcHtmlClass"), "bodyClassName": getClassName("kcBodyClass"), diff --git a/src/login/kcContext/KcContext.ts b/src/login/kcContext/KcContext.ts index e1ae9190..65e63c55 100644 --- a/src/login/kcContext/KcContext.ts +++ b/src/login/kcContext/KcContext.ts @@ -50,6 +50,7 @@ export declare namespace KcContext { resourcesCommonPath: string; loginRestartFlowUrl: string; loginUrl: string; + ssoLoginInOtherTabsUrl: string; }; realm: { name: string; @@ -117,6 +118,11 @@ export declare namespace KcContext { exists: (fieldName: string) => boolean; }; properties: Record; + authenticationSession?: { + authSessionId: string; + tabId: string; + ssoLoginInOtherTabsUrl: string; + }; }; export type SamlPostForm = Common & { diff --git a/src/login/kcContext/kcContextMocks.ts b/src/login/kcContext/kcContextMocks.ts index c5262ed7..ca09bb3a 100644 --- a/src/login/kcContext/kcContextMocks.ts +++ b/src/login/kcContext/kcContextMocks.ts @@ -112,7 +112,8 @@ export const kcContextCommonMock: KcContext.Common = { resourcesPath, "resourcesCommonPath": `${resourcesPath}/${resources_common}`, "loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg", - "loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg" + "loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg", + "ssoLoginInOtherTabsUrl": "/auth/realms/myrealm/login-actions/switch?client_id=account&tab_id=HoAx28ja4xg" }, "realm": { "name": "myrealm", diff --git a/src/tools/headInsert.ts b/src/tools/headInsert.ts index 0a123772..e103904c 100644 --- a/src/tools/headInsert.ts +++ b/src/tools/headInsert.ts @@ -10,7 +10,16 @@ export function headInsert( } | { type: "javascript"; - src: string; + isModule: boolean; + source: + | { + type: "url"; + src: string; + } + | { + type: "inline"; + code: string; + }; } ): { remove: () => void; prLoaded: Promise } { const htmlElement = document.createElement( @@ -35,14 +44,19 @@ export function headInsert( case "css": return { "href": params.href, - "type": "text/css", - "rel": "stylesheet", - "media": "screen,print" + "rel": "stylesheet" }; case "javascript": return { - "src": params.src, - "type": "text/javascript" + ...(() => { + switch (params.source.type) { + case "inline": + return { "textContent": params.source.code }; + case "url": + return { "src": params.source.src }; + } + })(), + "type": params.isModule ? "module" : "text/javascript" }; } })() From de5bc8238234bec3bee09db5187df8edd649a0ed Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 13 Apr 2024 02:18:06 +0200 Subject: [PATCH 002/631] Update css classes keys to reflect Keycloak 24 --- src/login/TemplateProps.ts | 186 ++++++++++++++++++----------- src/login/lib/useGetClassName.ts | 199 +++++++++++++++++-------------- 2 files changed, 227 insertions(+), 158 deletions(-) diff --git a/src/login/TemplateProps.ts b/src/login/TemplateProps.ts index 9591f1d0..e80d2bd2 100644 --- a/src/login/TemplateProps.ts +++ b/src/login/TemplateProps.ts @@ -22,78 +22,122 @@ export type TemplateProps({ "defaultClasses": { "kcBodyClass": undefined, - "kcHtmlClass": "login-pf", - "kcLoginClass": "login-pf-page", - "kcContentWrapperClass": "row", - "kcHeaderClass": "login-pf-page-header", "kcHeaderWrapperClass": undefined, - "kcFormCardClass": "card-pf", - "kcFormCardAccountClass": "login-pf-accounts", - "kcFormSocialAccountClass": "login-pf-social-section", - "kcFormSocialAccountContentClass": "col-xs-12 col-sm-6", - "kcFormHeaderClass": "login-pf-header", "kcLocaleWrapperClass": undefined, - "kcFeedbackErrorIcon": "pficon pficon-error-circle-o", - "kcFeedbackWarningIcon": "pficon pficon-warning-triangle-o", - "kcFeedbackSuccessIcon": "pficon pficon-ok", - "kcFeedbackInfoIcon": "pficon pficon-info", - "kcResetFlowIcon": "pficon pficon-arrow fa-2x", - "kcFormGroupClass": "form-group", - "kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", - "kcSignUpClass": "login-pf-signup", "kcInfoAreaWrapperClass": undefined, - - "kcLogoClass": "login-pf-brand", - "kcContainerClass": "container-fluid", - "kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3", - "kcFeedbackAreaClass": "col-md-12", - "kcLocaleClass": "col-xs-12 col-sm-1", - "kcAlertIconClasserror": "pficon pficon-error-circle-o", - - "kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2", - "kcFormSocialAccountListClass": "login-pf-social list-unstyled login-pf-social-all", - "kcFormSocialAccountDoubleListClass": "login-pf-social-double-col", - "kcFormSocialAccountListLinkClass": "login-pf-social-link", - "kcWebAuthnKeyIcon": "pficon pficon-key", - "kcWebAuthnDefaultIcon": "pficon pficon-key", - - "kcFormClass": "form-horizontal", - "kcFormGroupErrorClass": "has-error", - "kcLabelClass": "control-label", - "kcInputClass": "form-control", - "kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text", - "kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", "kcFormButtonsWrapperClass": undefined, - "kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", - "kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", - "kcFormSettingClass": "login-pf-settings", - "kcTextareaClass": "form-control", + "kcFormOptionsWrapperClass": undefined, - "kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details", - - // user-profile grouping - "kcFormGroupHeader": "pf-c-form__group", - - // css classes for form buttons main class used for all buttons - "kcButtonClass": "btn", - // classes defining priority of the button - primary or default (there is typically only one priority button for the form) - "kcButtonPrimaryClass": "btn-primary", - "kcButtonDefaultClass": "btn-default", - // classes defining size of the button - "kcButtonLargeClass": "btn-lg", - "kcButtonBlockClass": "btn-block", - - // css classes for input - "kcInputLargeClass": "input-lg", - "kcInputGroup": "pf-c-input-group", - - // css classes for form accessability - "kcSrOnlyClass": "sr-only", - - // css classes for select-authenticator form - "kcSelectAuthListClass": "list-group list-view-pf", - "kcSelectAuthListItemClass": "list-group-item list-view-pf-stacked", - "kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill", - "kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties", - "kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon", - "kcSelectAuthListItemTitle": "select-auth-box-paragraph", - "kcSelectAuthListItemInfoClass": "list-view-pf-main-info", - "kcSelectAuthListItemLeftClass": "list-view-pf-left", - "kcSelectAuthListItemBodyClass": "list-view-pf-body", - "kcSelectAuthListItemDescriptionClass": "list-view-pf-description", - "kcSelectAuthListItemHeadingClass": "list-group-item-heading", - "kcSelectAuthListItemHelpTextClass": "list-group-item-text", - - // css classes for the authenticators - "kcAuthenticatorDefaultClass": "fa list-view-pf-icon-lg", - "kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg", + "kcLogoIdP-facebook": "fa fa-facebook", "kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg", + "kcLogoIdP-bitbucket": "fa fa-bitbucket", "kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg", + "kcWebAuthnDefaultIcon": "pficon pficon-key", + "kcLogoIdP-stackoverflow": "fa fa-stack-overflow", + "kcSelectAuthListItemClass": "pf-l-stack__item select-auth-box-parent pf-l-split", + "kcLogoIdP-microsoft": "fa fa-windows", + "kcLoginOTPListItemHeaderClass": "pf-c-tile__header", + "kcLocaleItemClass": "pf-c-dropdown__menu-item", + "kcLoginOTPListItemIconBodyClass": "pf-c-tile__icon", + "kcInputHelperTextAfterClass": "pf-c-form__helper-text pf-c-form__helper-text-after", + "kcFormClass": "form-horizontal", + "kcSelectAuthListClass": "pf-l-stack select-auth-container", + "kcInputClassRadioCheckboxLabelDisabled": "pf-m-disabled", + "kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon", + "kcRecoveryCodesWarning": "kc-recovery-codes-warning", + "kcFormSettingClass": "login-pf-settings", + "kcWebAuthnBLE": "fa fa-bluetooth-b", + "kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", + "kcSelectAuthListItemArrowIconClass": "fa fa-angle-right fa-lg", + "kcFeedbackAreaClass": "col-md-12", + "kcFormPasswordVisibilityButtonClass": "pf-c-button pf-m-control", + "kcLogoIdP-google": "fa fa-google", + "kcCheckLabelClass": "pf-c-check__label", + "kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill", + "kcAuthenticatorDefaultClass": "fa fa-list list-view-pf-icon-lg", + "kcLogoIdP-gitlab": "fa fa-gitlab", + "kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2", + "kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", + "kcInputClassRadioLabel": "pf-c-radio__label", "kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg", - - //css classes for the OTP Login Form - "kcSelectOTPListClass": "card-pf card-pf-view card-pf-view-select card-pf-view-single-select col-xs-12", - "kcSelectOTPListItemClass": "card-pf-body card-pf-top-element", - "kcAuthenticatorOtpCircleClass": "fa fa-mobile card-pf-icon-circle", - "kcSelectOTPItemHeadingClass": "card-pf-title text-center", - "kcFormOptionsWrapperClass": undefined + "kcSelectAuthListItemHeadingClass": "pf-l-stack__item select-auth-box-headline pf-c-title", + "kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details", + "kcLogoLink": "http://www.keycloak.org", + "kcContainerClass": "container-fluid", + "kcSelectAuthListItemTitle": "select-auth-box-paragraph", + "kcHtmlClass": "login-pf", + "kcLoginOTPListItemTitleClass": "pf-c-tile__title", + "kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift", + "kcWebAuthnUnknownIcon": "pficon pficon-key unknown-transport-class", + "kcFormSocialAccountNameClass": "kc-social-provider-name", + "kcLogoIdP-openshift-v3": "pf-icon pf-icon-openshift", + "kcLoginOTPListInputClass": "pf-c-tile__input", + "kcWebAuthnUSB": "fa fa-usb", + "kcInputClassRadio": "pf-c-radio", + "kcWebAuthnKeyIcon": "pficon pficon-key", + "kcFeedbackInfoIcon": "fa fa-fw fa-info-circle", + "kcCommonLogoIdP": "kc-social-provider-logo kc-social-gray", + "kcRecoveryCodesActions": "kc-recovery-codes-actions", + "kcFormGroupHeader": "pf-c-form__group", + "kcFormSocialAccountSectionClass": "kc-social-section kc-social-gray", + "kcLogoIdP-instagram": "fa fa-instagram", + "kcAlertClass": "pf-c-alert pf-m-inline", + "kcHeaderClass": "login-pf-page-header", + "kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", + "kcFormPasswordVisibilityIconShow": "fa fa-eye", + "kcFormSocialAccountLinkClass": "pf-c-login__main-footer-links-item-link", + "kcLocaleMainClass": "pf-c-dropdown", + "kcInputGroup": "pf-c-input-group", + "kcTextareaClass": "form-control", + "kcButtonBlockClass": "pf-m-block", + "kcButtonClass": "pf-c-button", + "kcWebAuthnNFC": "fa fa-wifi", + "kcLocaleClass": "col-xs-12 col-sm-1", + "kcInputClassCheckboxInput": "pf-c-check__input", + "kcFeedbackErrorIcon": "fa fa-fw fa-exclamation-circle", + "kcInputLargeClass": "input-lg", + "kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text", + "kcRecoveryCodesList": "kc-recovery-codes-list", + "kcFormSocialAccountListClass": "pf-c-login__main-footer-links kc-social-links", + "kcAlertTitleClass": "pf-c-alert__title kc-feedback-text", + "kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg", + "kcCheckInputClass": "pf-c-check__input", + "kcLogoIdP-linkedin": "fa fa-linkedin", + "kcLogoIdP-twitter": "fa fa-twitter", + "kcFeedbackWarningIcon": "fa fa-fw fa-exclamation-triangle", + "kcResetFlowIcon": "pficon pficon-arrow fa", + "kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties", + "kcFeedbackSuccessIcon": "fa fa-fw fa-check-circle", + "kcLoginOTPListClass": "pf-c-tile", + "kcSrOnlyClass": "sr-only", + "kcFormSocialAccountListGridClass": "pf-l-grid kc-social-grid", + "kcButtonDefaultClass": "btn-default", + "kcFormGroupErrorClass": "has-error", + "kcSelectAuthListItemDescriptionClass": "pf-l-stack__item select-auth-box-desc", + "kcSelectAuthListItemBodyClass": "pf-l-split__item pf-l-stack", + "kcWebAuthnInternal": "pficon pficon-key", + "kcSelectAuthListItemArrowClass": "pf-l-split__item select-auth-box-arrow", + "kcCheckClass": "pf-c-check", + "kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3", + "kcLogoClass": "login-pf-brand", + "kcLoginOTPListItemIconClass": "fa fa-mobile", + "kcLoginClass": "login-pf-page", + "kcSignUpClass": "login-pf-signup", + "kcButtonLargeClass": "btn-lg", + "kcFormCardClass": "card-pf", + "kcLocaleListClass": "pf-c-dropdown__menu pf-m-align-right", + "kcInputClass": "pf-c-form-control", + "kcFormGroupClass": "form-group", + "kcLogoIdP-paypal": "fa fa-paypal", + "kcInputClassCheckbox": "pf-c-check", + "kcRecoveryCodesConfirmation": "kc-recovery-codes-confirmation", + "kcFormPasswordVisibilityIconHide": "fa fa-eye-slash", + "kcInputClassRadioInput": "pf-c-radio__input", + "kcFormSocialAccountListButtonClass": "pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray", + "kcInputClassCheckboxLabel": "pf-c-check__label", + "kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", + "kcFormHeaderClass": "login-pf-header", + "kcFormSocialAccountGridItem": "pf-l-grid__item", + "kcButtonPrimaryClass": "pf-m-primary", + "kcInputHelperTextBeforeClass": "pf-c-form__helper-text pf-c-form__helper-text-before", + "kcLogoIdP-github": "fa fa-github", + "kcLabelClass": "pf-c-form__label pf-c-form__label-text" } }); From a7a3ec711bb82f8036e48766b8f4dde27e7e7569 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 13 Apr 2024 03:26:15 +0200 Subject: [PATCH 003/631] Fully sync login template with Keycloak 24 --- scripts/generate-i18n-messages.ts | 2 +- src/login/Template.tsx | 117 +++++++++++++++++++----------- src/login/TemplateProps.ts | 5 +- src/login/lib/useGetClassName.ts | 3 + 4 files changed, 81 insertions(+), 46 deletions(-) diff --git a/scripts/generate-i18n-messages.ts b/scripts/generate-i18n-messages.ts index aa899487..9d721740 100644 --- a/scripts/generate-i18n-messages.ts +++ b/scripts/generate-i18n-messages.ts @@ -17,7 +17,7 @@ const isSilent = true; const logger = getLogger({ isSilent }); async function main() { - const keycloakVersion = "23.0.4"; + const keycloakVersion = "24.0.2"; const thisCodebaseRootDirPath = getThisCodebaseRootDirPath(); diff --git a/src/login/Template.tsx b/src/login/Template.tsx index c983afb6..79ce2a33 100644 --- a/src/login/Template.tsx +++ b/src/login/Template.tsx @@ -11,10 +11,9 @@ export default function Template(props: TemplateProps) { displayInfo = false, displayMessage = true, displayRequiredFields = false, - displayWide = false, - showAnotherWayIfPresent = true, headerNode, showUsernameNode = null, + socialProvidersNode = null, infoNode = null, kcContext, i18n, @@ -25,7 +24,7 @@ export default function Template(props: TemplateProps) { const { getClassName } = useGetClassName({ doUseDefaultCss, classes }); - const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; + const { msg, msgStr, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession } = kcContext; @@ -85,12 +84,12 @@ export default function Template(props: TemplateProps) { -
+
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && ( - - ) : ( - <> - {showUsernameNode} -
@@ -168,13 +197,21 @@ export default function Template(props: TemplateProps) {
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */} {displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && ( -
- {message.type === "success" && } - {message.type === "warning" && } - {message.type === "error" && } - {message.type === "info" && } +
+
+ {message.type === "success" && } + {message.type === "warning" && } + {message.type === "error" && } + {message.type === "info" && } +
) {
)} {children} - {auth !== undefined && auth.showTryAnotherWayLink && showAnotherWayIfPresent && ( -
-
+ {auth !== undefined && auth.showTryAnotherWayLink && ( + +
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} @@ -211,6 +239,7 @@ export default function Template(props: TemplateProps) {
)} + {socialProvidersNode} {displayInfo && (
diff --git a/src/login/TemplateProps.ts b/src/login/TemplateProps.ts index e80d2bd2..8eb9934c 100644 --- a/src/login/TemplateProps.ts +++ b/src/login/TemplateProps.ts @@ -11,10 +11,10 @@ export type TemplateProps({ "kcInfoAreaWrapperClass": undefined, "kcFormButtonsWrapperClass": undefined, "kcFormOptionsWrapperClass": undefined, + "kcLocaleDropDownClass": undefined, + "kcLocaleListItemClass": undefined, + "kcContentWrapperClass": undefined, "kcLogoIdP-facebook": "fa fa-facebook", "kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg", From 00651c0c3caaa4ce7b097593b7f2043c66a67c1d Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 13 Apr 2024 04:15:23 +0200 Subject: [PATCH 004/631] Drop compat with Keycloak prior to v12 #359 --- ..._object_to_js_code_declaring_an_object.ftl | 307 ++++-------------- 1 file changed, 68 insertions(+), 239 deletions(-) diff --git a/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl b/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl index c5619d9f..e9a6f614 100644 --- a/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl +++ b/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl @@ -30,30 +30,19 @@ <#list fieldNames as fieldName> if(fieldName === "${fieldName}" ){ - <#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 --> - <#if !messagesPerField.existsError??> + <#-- https://github.com/keycloakify/keycloakify/pull/218 --> + <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistErrorOnUsernameOrPassword = ""> - <#assign doExistMessageForUsernameOrPassword = ""> - - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - <#if !doExistMessageForUsernameOrPassword> - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - - return <#if doExistMessageForUsernameOrPassword>text<#else>undefined; + <#attempt> + <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> + <#recover> + <#assign doExistErrorOnUsernameOrPassword = true> + + <#if doExistErrorOnUsernameOrPassword> + return text; <#else> <#assign doExistMessageForField = ""> @@ -70,49 +59,19 @@ <#else> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistMessageForField = ""> - <#assign doExistErrorOnUsernameOrPassword = ""> + <#attempt> + <#assign doExistMessageForField = messagesPerField.exists('${fieldName}')> + <#recover> + <#assign doExistMessageForField = true> + - <#attempt> - <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> - <#recover> - <#assign doExistErrorOnUsernameOrPassword = true> - - - <#if doExistErrorOnUsernameOrPassword> - return text; - <#else> - - <#assign doExistMessageForField = ""> - - <#attempt> - <#assign doExistMessageForField = messagesPerField.exists('${fieldName}')> - <#recover> - <#assign doExistMessageForField = true> - - - return <#if doExistMessageForField>text<#else>undefined; - - - - <#else> - - <#assign doExistMessageForField = ""> - - <#attempt> - <#assign doExistMessageForField = messagesPerField.exists('${fieldName}')> - <#recover> - <#assign doExistMessageForField = true> - - - return <#if doExistMessageForField>text<#else>undefined; - - + return <#if doExistMessageForField>text<#else>undefined; + } @@ -128,72 +87,30 @@ <#list fieldNames as fieldName> if(fieldName === "${fieldName}" ){ - <#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 --> - <#if !messagesPerField.existsError??> + <#-- https://github.com/keycloakify/keycloakify/pull/218 --> + <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistErrorOnUsernameOrPassword = ""> - <#assign doExistMessageForUsernameOrPassword = ""> + <#attempt> + <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> + <#recover> + <#assign doExistErrorOnUsernameOrPassword = true> + - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - <#if !doExistMessageForUsernameOrPassword> - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - - return <#if doExistMessageForUsernameOrPassword>true<#else>false; - - <#else> - - <#assign doExistMessageForField = ""> - - <#attempt> - <#assign doExistMessageForField = messagesPerField.exists('${fieldName}')> - <#recover> - <#assign doExistMessageForField = true> - - - return <#if doExistMessageForField>true<#else>false; - - + return <#if doExistErrorOnUsernameOrPassword>true<#else>false; <#else> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistErrorMessageForField = ""> - <#assign doExistErrorOnUsernameOrPassword = ""> + <#attempt> + <#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')> + <#recover> + <#assign doExistErrorMessageForField = true> + - <#attempt> - <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> - <#recover> - <#assign doExistErrorOnUsernameOrPassword = true> - - - return <#if doExistErrorOnUsernameOrPassword>true<#else>false; - - <#else> - - <#assign doExistErrorMessageForField = ""> - - <#attempt> - <#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')> - <#recover> - <#assign doExistErrorMessageForField = true> - - - return <#if doExistErrorMessageForField>true<#else>false; - - + return <#if doExistErrorMessageForField>true<#else>false; @@ -214,88 +131,42 @@ <#list fieldNames as fieldName> if(fieldName === "${fieldName}" ){ - <#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 --> - <#if !messagesPerField.existsError??> + <#-- https://github.com/keycloakify/keycloakify/pull/218 --> + <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistErrorOnUsernameOrPassword = ""> - <#assign doExistMessageForUsernameOrPassword = ""> + <#attempt> + <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> + <#recover> + <#assign doExistErrorOnUsernameOrPassword = true> + + + <#if doExistErrorOnUsernameOrPassword> <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')> + return "${kcSanitize(msg('invalidUserMessage'))?no_esc}"; <#recover> - <#assign doExistMessageForUsernameOrPassword = true> + return "Invalid username or password."; - <#if !doExistMessageForUsernameOrPassword> - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - - <#if !doExistMessageForUsernameOrPassword> - return ""; - <#else> - <#attempt> - return "${kcSanitize(msg('invalidUserMessage'))?no_esc}"; - <#recover> - return "Invalid username or password."; - - - <#else> <#attempt> return "${messagesPerField.get('${fieldName}')?no_esc}"; <#recover> - return "invalid field"; + return ""; <#else> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> - - <#assign doExistErrorOnUsernameOrPassword = ""> - - <#attempt> - <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> - <#recover> - <#assign doExistErrorOnUsernameOrPassword = true> - - - <#if doExistErrorOnUsernameOrPassword> - - <#attempt> - return "${kcSanitize(msg('invalidUserMessage'))?no_esc}"; - <#recover> - return "Invalid username or password."; - - - <#else> - - <#attempt> - return "${messagesPerField.get('${fieldName}')?no_esc}"; - <#recover> - return ""; - - - - - <#else> - - <#attempt> - return "${messagesPerField.get('${fieldName}')?no_esc}"; - <#recover> - return "invalid field"; - - - + <#attempt> + return "${messagesPerField.get('${fieldName}')?no_esc}"; + <#recover> + return "invalid field"; + @@ -315,72 +186,30 @@ <#list fieldNames as fieldName> if(fieldName === "${fieldName}" ){ - <#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 --> - <#if !messagesPerField.existsError??> + <#-- https://github.com/keycloakify/keycloakify/pull/218 --> + <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistErrorOnUsernameOrPassword = ""> - <#assign doExistMessageForUsernameOrPassword = ""> + <#attempt> + <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> + <#recover> + <#assign doExistErrorOnUsernameOrPassword = true> + - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - <#if !doExistMessageForUsernameOrPassword> - <#attempt> - <#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')> - <#recover> - <#assign doExistMessageForUsernameOrPassword = true> - - - - return <#if doExistMessageForUsernameOrPassword>true<#else>false; - - <#else> - - <#assign doExistMessageForField = ""> - - <#attempt> - <#assign doExistMessageForField = messagesPerField.exists('${fieldName}')> - <#recover> - <#assign doExistMessageForField = true> - - - return <#if doExistMessageForField>true<#else>false; - - + return <#if doExistErrorOnUsernameOrPassword>true<#else>false; <#else> - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#assign doExistErrorMessageForField = ""> - <#assign doExistErrorOnUsernameOrPassword = ""> + <#attempt> + <#assign doExistErrorMessageForField = messagesPerField.exists('${fieldName}')> + <#recover> + <#assign doExistErrorMessageForField = true> + - <#attempt> - <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> - <#recover> - <#assign doExistErrorOnUsernameOrPassword = true> - - - return <#if doExistErrorOnUsernameOrPassword>true<#else>false; - - <#else> - - <#assign doExistErrorMessageForField = ""> - - <#attempt> - <#assign doExistErrorMessageForField = messagesPerField.exists('${fieldName}')> - <#recover> - <#assign doExistErrorMessageForField = true> - - - return <#if doExistErrorMessageForField>true<#else>false; - - + return <#if doExistErrorMessageForField>true<#else>false; From a0367066b4f48a9c46878c0ef2ff9e976a6927f4 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 13 Apr 2024 04:28:28 +0200 Subject: [PATCH 005/631] Feat polifill for getFirstError and make existsError accept more than one field (kcContext.messagePerField) --- ..._object_to_js_code_declaring_an_object.ftl | 83 ++++++++++++------- src/login/kcContext/KcContext.ts | 5 +- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl b/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl index e9a6f614..06e64ab3 100644 --- a/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl +++ b/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl @@ -79,47 +79,61 @@ }, - "existsError": function (fieldName) { + "existsError": function (){ - <#if !messagesPerField?? || !(messagesPerField?is_hash)> - throw new Error("You're not supposed to use messagesPerField.printIfExists in this page"); - <#else> - <#list fieldNames as fieldName> - if(fieldName === "${fieldName}" ){ + function existsError_singleFieldName(fieldName) { - <#-- https://github.com/keycloakify/keycloakify/pull/218 --> - <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> + <#if !messagesPerField?? || !(messagesPerField?is_hash)> + throw new Error("You're not supposed to use messagesPerField.printIfExists in this page"); + <#else> + <#list fieldNames as fieldName> + if(fieldName === "${fieldName}" ){ - <#assign doExistErrorOnUsernameOrPassword = ""> + <#-- https://github.com/keycloakify/keycloakify/pull/218 --> + <#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'> - <#attempt> - <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> - <#recover> - <#assign doExistErrorOnUsernameOrPassword = true> - + <#assign doExistErrorOnUsernameOrPassword = ""> - return <#if doExistErrorOnUsernameOrPassword>true<#else>false; + <#attempt> + <#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')> + <#recover> + <#assign doExistErrorOnUsernameOrPassword = true> + - <#else> + return <#if doExistErrorOnUsernameOrPassword>true<#else>false; - <#assign doExistErrorMessageForField = ""> + <#else> - <#attempt> - <#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')> - <#recover> - <#assign doExistErrorMessageForField = true> - + <#assign doExistErrorMessageForField = ""> - return <#if doExistErrorMessageForField>true<#else>false; + <#attempt> + <#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')> + <#recover> + <#assign doExistErrorMessageForField = true> + - + return <#if doExistErrorMessageForField>true<#else>false; - } - + - throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated"); + } + - + throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated"); + + + + } + + for( let i = 0; i < arguments.length; i++ ){ + + if( existsError_singleFieldName(arguments[i]) ){ + return true; + } + + } + + return false; }, "get": function (fieldName) { @@ -219,6 +233,19 @@ throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated"); + }, + "getFirstError": function () { + + for( let i = 0; i < arguments.length; i++ ){ + + const fieldName = arguments[i]; + + if( out.messagesPerField.existsError(fieldName) ){ + return out.messagesPerField.get(fieldName); + } + + } + } }; diff --git a/src/login/kcContext/KcContext.ts b/src/login/kcContext/KcContext.ts index 65e63c55..4e58f6da 100644 --- a/src/login/kcContext/KcContext.ts +++ b/src/login/kcContext/KcContext.ts @@ -101,7 +101,7 @@ export declare namespace KcContext { * @param fields * @return boolean */ - existsError: (fieldName: string) => boolean; + existsError: (fieldName: string, ...otherFiledNames: string[]) => boolean; /** * Get message for given field. * @@ -116,6 +116,8 @@ export declare namespace KcContext { * @return boolean */ exists: (fieldName: string) => boolean; + + getFirstError: (...fieldNames: string[]) => string; }; properties: Record; authenticationSession?: { @@ -165,6 +167,7 @@ export declare namespace KcContext { alias: string; providerId: string; displayName: string; + iconClasses?: string; }[]; }; }; From f6bdd92f9e0504bedde7c0c3ddc87e5833410d43 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 13 Apr 2024 04:46:13 +0200 Subject: [PATCH 006/631] Update Login template for Keycloak 24 --- src/login/pages/Login.tsx | 255 ++++++++++++++++++++------------------ 1 file changed, 136 insertions(+), 119 deletions(-) diff --git a/src/login/pages/Login.tsx b/src/login/pages/Login.tsx index 919b2171..b739e3b5 100644 --- a/src/login/pages/Login.tsx +++ b/src/login/pages/Login.tsx @@ -1,6 +1,5 @@ -import { useState, type FormEventHandler } from "react"; +import { useState } from "react"; import { clsx } from "keycloakify/tools/clsx"; -import { useConstCallback } from "keycloakify/tools/useConstCallback"; import type { PageProps } from "keycloakify/login/pages/PageProps"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import type { KcContext } from "../kcContext"; @@ -14,115 +13,162 @@ export default function Login(props: PageProps>(e => { - e.preventDefault(); - - setIsLoginButtonDisabled(true); - - const formElement = e.target as HTMLFormElement; - - //NOTE: Even if we login with email Keycloak expect username and password in - //the POST request. - formElement.querySelector("input[name='email']")?.setAttribute("name", "username"); - - formElement.submit(); - }); - return (