From e5e39d036d02a8cedcb430f27e5848e894614be0 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Tue, 2 Mar 2021 22:48:36 +0100 Subject: [PATCH] Implement login --- package.json | 1 + .../generateFtl/ftl2js.ftl | 68 +++- src/lib/LoginPage.tsx | 297 +++++++++++++++++- src/lib/Template.tsx | 113 ++++--- src/lib/i18n/useKeycloakTranslation.tsx | 23 +- src/lib/keycloakFtlValues.ts | 31 +- .../tools/allPropertiesValuesToUndefined.ts | 10 + tsconfig.json | 2 +- yarn.lock | 2 +- 9 files changed, 475 insertions(+), 72 deletions(-) create mode 100644 src/lib/tools/allPropertiesValuesToUndefined.ts diff --git a/package.json b/package.json index 40d762a5..9d093c58 100755 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "dependencies": { "cheerio": "^1.0.0-rc.5", "evt": "^1.9.12", + "minimal-polyfills": "^2.1.6", "powerhooks": "^0.0.14", "tss-react": "^0.0.9" } diff --git a/src/bin/build-keycloak-theme/generateFtl/ftl2js.ftl b/src/bin/build-keycloak-theme/generateFtl/ftl2js.ftl index 54486631..1daca7c5 100644 --- a/src/bin/build-keycloak-theme/generateFtl/ftl2js.ftl +++ b/src/bin/build-keycloak-theme/generateFtl/ftl2js.ftl @@ -4,12 +4,19 @@ "loginAction": "${url.loginAction}", "resourcesPath": "${url.resourcesPath}", "resourcesCommonPath": "${url.resourcesCommonPath}", - "loginRestartFlowUrl": "${url.loginRestartFlowUrl}" + "loginRestartFlowUrl": "${url.loginRestartFlowUrl}", + "loginResetCredentialsUrl": "${url.loginResetCredentialsUrl}", + "registrationUrl": "${url.registrationUrl}" }, "realm": { "displayName": "${realm.displayName!''}" || undefined, "displayNameHtml": "${realm.displayNameHtml!''}" || undefined, - "internationalizationEnabled": ${realm.internationalizationEnabled?c} + "internationalizationEnabled": ${realm.internationalizationEnabled?c}, + "password": ${realm.password?c}, + "loginWithEmailAllowed": ${realm.loginWithEmailAllowed?c}, + "registrationEmailAsUsername": ${realm.registrationEmailAsUsername?c}, + "rememberMe": ${realm.rememberMe?c}, + "resetPasswordAllowed": ${realm.resetPasswordAllowed?c} }, "locale": (function (){ @@ -54,6 +61,7 @@ "showUsername": ${auth.showUsername()?c}, "showResetCredentials": ${auth.showResetCredentials()?c}, "showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c} + "selectedCredential": "${auth.selectedCredential!''}" || undefined }; <#if auth.showUsername() && !auth.showResetCredentials()> @@ -79,7 +87,7 @@ <#if scripts??> <#list scripts as script> - out.push("${script}"); + out.push("${script}"); @@ -107,7 +115,59 @@ return false; - })() + })(), + "social": { + "displayInfo": ${social.displayInfo?c}, + "providers": (()=>{ + <#if social.providers??> + + var out= []; + + <#list social.providers as p> + out.push({ + "loginUrl": "${p.loginUrl}", + "alias": "${p.alias}", + "providerId": "${p.providerId}", + "displayName": "${p.displayName}" + }); + + + return out; + + + + return undefined; + + })() + }, + "usernameEditDisabled": (function () { + + <#if usernameEditDisabled??> + return true; + + return false; + + })(), + "login": { + "username": "${login.username!''}" || undefined, + "rememberMe": (function (){ + + <#if login.rememberMe??> + return true; + + return false; + + + })() + }, + "registrationDisabled": (function (){ + + <#if registrationDisabled??> + return true; + + return false; + + }) } \ No newline at end of file diff --git a/src/lib/LoginPage.tsx b/src/lib/LoginPage.tsx index 9540e829..7836b2a7 100644 --- a/src/lib/LoginPage.tsx +++ b/src/lib/LoginPage.tsx @@ -1,18 +1,154 @@ -/* -import { useState, memo } from "react"; -import { KcProperties, Template } from "./Template"; -import { assert } from "evt/tools/typeSafety/assert"; -import { keycloakPagesContext } from "./keycloakFtlValues"; +import { useState, memo } from "react"; +import { allPropertiesValuesToUndefined } from "./tools/allPropertiesValuesToUndefined"; +import { Template, defaultKcTemplateProperties } from "./Template"; +import type { KcTemplateProperties, KcClasses } from "./Template"; +import { assert } from "evt/tools/typeSafety/assert"; +import { keycloakPagesContext } from "./keycloakFtlValues"; +import { useKeycloakThemeTranslation } from "./i18n/useKeycloakTranslation"; +import { cx } from "tss-react"; +import { useConstCallback } from "powerhooks"; -export type Props = { - properties: KcProperties; +export type KcLoginPageProperties = KcTemplateProperties & KcClasses< + "kcLogoLink" | + "kcLogoClass" | + "kcContainerClass" | + "kcContentClass" | + "kcFeedbackAreaClass" | + "kcLocaleClass" | + "kcAlertIconClasserror" | + "kcFormAreaClass" | + "kcFormSocialAccountListClass" | + "kcFormSocialAccountDoubleListClass" | + "kcFormSocialAccountListLinkClass" | + "kcWebAuthnKeyIcon" | + "kcFormClass" | + "kcFormGroupErrorClass" | + "kcLabelClass" | + "kcInputClass" | + "kcInputWrapperClass" | + "kcFormOptionsClass" | + "kcFormButtonsClass" | + "kcFormSettingClass" | + "kcTextareaClass" | + "kcInfoAreaClass" | + "kcButtonClass" | + "kcButtonPrimaryClass" | + "kcButtonDefaultClass" | + "kcButtonLargeClass" | + "kcButtonBlockClass" | + "kcInputLargeClass" | + "kcSrOnlyClass" | + "kcSelectAuthListClass" | + "kcSelectAuthListItemClass" | + "kcSelectAuthListItemInfoClass" | + "kcSelectAuthListItemLeftClass" | + "kcSelectAuthListItemBodyClass" | + "kcSelectAuthListItemDescriptionClass" | + "kcSelectAuthListItemHeadingClass" | + "kcSelectAuthListItemHelpTextClass" | + "kcAuthenticatorDefaultClass" | + "kcAuthenticatorPasswordClass" | + "kcAuthenticatorOTPClass" | + "kcAuthenticatorWebAuthnClass" | + "kcAuthenticatorWebAuthnPasswordlessClass" | + "kcSelectOTPListClass" | + "kcSelectOTPListItemClass" | + "kcAuthenticatorOtpCircleClass" | + "kcSelectOTPItemHeadingClass" | + "kcFormOptionsWrapperClass" +>; + +export const defaultKcLoginPageProperties: KcLoginPageProperties = { + ...defaultKcTemplateProperties, + "kcLogoLink": "http://www.keycloak.org", + "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", + + "kcFormClass": "form-horizontal", + "kcFormGroupErrorClass": "has-error", + "kcLabelClass": "control-label", + "kcInputClass": "form-control", + "kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", + "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", + + "kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details", + + // 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", + + // 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", + "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", + "kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg", + "kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg", + "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", + "kcSelectOTPListItemClass": "card-pf-body card-pf-top-element", + "kcAuthenticatorOtpCircleClass": "fa fa-mobile card-pf-icon-circle", + "kcSelectOTPItemHeadingClass": "card-pf-title text-center" }; -export const LoginPage = memo((props: Props)=>{ - const [{ }] = useState(() => { +/** Tu use if you don't want any default */ +export const allClearKcLoginPageProperties = + allPropertiesValuesToUndefined(defaultKcLoginPageProperties); + +export type Props = { + properties?: KcLoginPageProperties; +}; + +export const LoginPage = memo((props: Props) => { + + const { properties = {} } = props; + + const { t, tStr } = useKeycloakThemeTranslation(); + + Object.assign(properties, defaultKcLoginPageProperties); + + const [{ + social, realm, url, + usernameEditDisabled, login, + auth, registrationDisabled + }] = useState(() => { assert(keycloakPagesContext !== undefined); @@ -20,12 +156,145 @@ export const LoginPage = memo((props: Props)=>{ }); + const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); - return ( -