<#assign xKeycloakify={ "messages": {}, "pageId": "{{pageId}}", "ftlTemplateFileName": "{{ftlTemplateFileName}}", "themeType": "{{themeType}}", "themeName": "{{themeName}}", "keycloakifyVersion": "{{keycloakifyVersion}}", "themeVersion": "{{themeVersion}}", "resourcesPath": "" }> <#if url?? && url?is_hash && url.resourcesPath?? && url.resourcesPath?is_string> <#assign xKeycloakify = xKeycloakify + { "resourcesPath": url.resourcesPath }> <#if resourceUrl?? && resourceUrl?is_string> <#assign xKeycloakify = xKeycloakify + { "resourcesPath": resourceUrl }> const kcContext = ${toJsDeclarationString(.data_model, [])?no_esc}; kcContext.keycloakifyVersion = "${xKeycloakify.keycloakifyVersion}"; kcContext.themeVersion = "${xKeycloakify.themeVersion}"; kcContext.themeType = "${xKeycloakify.themeType}"; kcContext.themeName = "${xKeycloakify.themeName}"; kcContext.pageId = "${xKeycloakify.pageId}"; kcContext.ftlTemplateFileName = "${xKeycloakify.ftlTemplateFileName}"; <@addNonAutomaticallyGatherableMessagesToXKeycloakifyMessages /> kcContext["x-keycloakify"] = {}; kcContext["x-keycloakify"].resourcesPath = "${xKeycloakify.resourcesPath}"; { var messages = {}; <#list xKeycloakify.messages as key, resolvedMsg> messages["${key}"] = decodeHtmlEntities("${resolvedMsg?js_string}"); kcContext["x-keycloakify"].messages = messages; } if( kcContext.url instanceof Object && typeof kcContext.url.resourcesPath === "string" ){ kcContext.url.resourcesCommonPath = kcContext.url.resourcesPath + "/{{RESOURCES_COMMON}}"; } if( kcContext.messagesPerField ){ var existsError_singleFieldName = kcContext.messagesPerField.existsError; kcContext.messagesPerField.existsError = function (){ for( let i = 0; i < arguments.length; i++ ){ if( existsError_singleFieldName(arguments[i]) ){ return true; } } return false; }; kcContext.messagesPerField.exists = function (fieldName) { return kcContext.messagesPerField.get(fieldName) !== ""; }; kcContext.messagesPerField.printIfExists = function (fieldName, text) { return kcContext.messagesPerField.exists(fieldName) ? text : undefined; }; kcContext.messagesPerField.getFirstError = function () { for( let i = 0; i < arguments.length; i++ ){ const fieldName = arguments[i]; if( kcContext.messagesPerField.existsError(fieldName) ){ return kcContext.messagesPerField.get(fieldName); } } }; } attributes_to_attributesByName: { if( !kcContext.profile ){ break attributes_to_attributesByName; } if( !kcContext.profile.attributes ){ break attributes_to_attributesByName; } var attributes = kcContext.profile.attributes; delete kcContext.profile.attributes; kcContext.profile.attributesByName = {}; attributes.forEach(function(attribute){ kcContext.profile.attributesByName[attribute.name] = attribute; }); } window.kcContext = kcContext; <#if xKeycloakify.themeType == "login" > { const script = document.createElement("script"); script.type = "importmap"; script.textContent = JSON.stringify({ imports: { "rfc4648": kcContext.url.resourcesCommonPath + "/node_modules/rfc4648/lib/rfc4648.js" } }, null, 2); document.head.appendChild(script); } function decodeHtmlEntities(htmlStr){ var element = decodeHtmlEntities.element; if (!element) { element = document.createElement("textarea"); decodeHtmlEntities.element = element; } element.innerHTML = htmlStr; return element.value; } <#function toJsDeclarationString object path> <#local isHash = -1> <#attempt> <#local isHash = object?is_hash || object?is_hash_ex> <#recover> <#return "ABORT: Can't evaluate if " + path?join(".") + " is a hash"> <#if isHash> <#if path?size gt 10> <#return "ABORT: Too many recursive calls, path: " + path?join(".")> <#local keys = -1> <#attempt> <#local keys = object?keys> <#recover> <#return "ABORT: We can't list keys on object"> <#local outSeq = []> <#list keys as key> <#if ["class","declaredConstructors","superclass","declaringClass" ]?seq_contains(key) > <#continue> <#if ( areSamePath(path, ["url"]) && ["loginUpdatePasswordUrl", "loginUpdateProfileUrl", "loginUsernameReminderUrl", "loginUpdateTotpUrl"]?seq_contains(key) ) || ( key == "updateProfileCtx" && areSamePath(path, []) ) || ( <#-- https://github.com/keycloakify/keycloakify/pull/65#issuecomment-991896344 (reports with saml-post-form.ftl) --> <#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) --> <#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 --> <#-- https://github.com/keycloakify/keycloakify/issues/357 --> <#-- https://github.com/keycloakify/keycloakify/discussions/406#discussioncomment-7514787 --> key == "loginAction" && areSamePath(path, ["url"]) && ["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(xKeycloakify.pageId) && !(auth?has_content && auth.showTryAnotherWayLink()) ) || ( <#-- https://github.com/keycloakify/keycloakify/issues/362 --> ["secretData", "value"]?seq_contains(key) && areSamePath(path, [ "totp", "otpCredentials", "*" ]) ) || ( ["contextData", "idpConfig", "idp", "authenticationSession"]?seq_contains(key) && areSamePath(path, ["brokerContext"]) && ["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(xKeycloakify.pageId) ) || ( key == "identityProviderBrokerCtx" && areSamePath(path, []) && ["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(xKeycloakify.pageId) ) || ( ["masterAdminClient", "delegateForUpdate", "defaultRole", "smtpConfig"]?seq_contains(key) && areSamePath(path, ["realm"]) ) || ( xKeycloakify.pageId == "error.ftl" && areSamePath(path, ["realm"]) && !["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key) ) || ( xKeycloakify.pageId == "applications.ftl" && ( key == "realm" || key == "container" ) && isSubpath(path, ["applications", "applications"]) ) || ( key == "delegateForUpdate" && areSamePath(path, ["user"]) ) || ( <#-- Security audit forwarded by Garth (Gmail) --> key == "saml.signing.private.key" && areSamePath(path, ["client", "attributes"]) ) || ( <#-- See: https://github.com/keycloakify/keycloakify/issues/534 --> key == "password" && areSamePath(path, ["login"]) ) || ( <#-- Remove realmAttributes added by https://github.com/jcputney/keycloak-theme-additional-info-extension for peace of mind. --> key == "realmAttributes" && areSamePath(path, []) ) || ( <#-- attributesByName adds a lot of noise to the output and is not needed, we already have profile.attributes --> key == "attributesByName" && areSamePath(path, ["profile"]) ) || ( <#-- We already have the attributes in profile speedup the rendering by filtering it out from the register object --> (key == "attributes" || key == "attributesByName") && areSamePath(path, ["register"]) ) || ( areSamePath(path, ["properties"]) && ( key?starts_with("kc") || key == "locales" || key == "import" || key == "parent" || key == "meta" || key == "stylesCommon" || key == "styles" || key == "accountResourceProvider" ) ) || ( key == "execution" && areSamePath(path, []) ) || ( key == "entity" && areSamePath(path, ["user"]) ) || ( key == "attributes" && areSamePath(path, ["realm"]) ) || ( xKeycloakify.pageId == "index.ftl" && xKeycloakify.themeType == "account" && areSamePath(path, ["realm"]) && ![ "name", "registrationEmailAsUsername", "editUsernameAllowed", "isInternationalizationEnabled", "identityFederationEnabled", "userManagedAccessAllowed" ]?seq_contains(key) ) || ( ["flowContext", "session", "realm"]?seq_contains(key) && areSamePath(path, ["social"]) ) > <#-- <#local outSeq += ["/*" + path?join(".") + "." + key + " excluded*/"]> --> <#continue> <#-- https://github.com/keycloakify/keycloakify/discussions/406 --> <#if ( key == "attemptedUsername" && areSamePath(path, ["auth"]) && [ "register.ftl", "terms.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl" ]?seq_contains(xKeycloakify.pageId) )> <#attempt> <#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 --> <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> <#local outSeq += ["/*" + path?join(".") + "." + key + " excluded*/"]> <#continue> <#recover> <#local outSeq += ["/*Accessing attemptedUsername throwed an exception */"]> {{userDefinedExclusions}} <#attempt> <#if !object[key]??> <#continue> <#recover> <#local outSeq += ["/*Couldn't test if '" + key + "' is available on this object*/"]> <#continue> <#local propertyValue = -1> <#attempt> <#local propertyValue = object[key]> <#recover> <#local outSeq += ["/*Couldn't dereference '" + key + "' on this object*/"]> <#continue> <#local recOut = toJsDeclarationString(propertyValue, path + [ key ])> <#if recOut?starts_with("ABORT:")> <#local errorMessage = recOut?remove_beginning("ABORT:")> <#if errorMessage != " It's a method" > <#local outSeq += ["/*" + key + ": " + errorMessage + "*/"]> <#continue> <#local outSeq += ['"' + key + '": ' + recOut + ","]> <#return (["{"] + outSeq?map(str -> ""?right_pad(4 * (path?size + 1)) + str) + [ ""?right_pad(4 * path?size) + "}"])?join("\n")> <#local isMethod = -1> <#attempt> <#local isMethod = object?is_method> <#recover> <#return "ABORT: Can't test if it'sa method."> <#if isMethod> <#if areSamePath(path, ["auth", "showUsername"])> <#attempt> <#return auth.showUsername()?c> <#recover> <#return "ABORT: Couldn't evaluate auth.showUsername()"> <#if areSamePath(path, ["auth", "showResetCredentials"])> <#attempt> <#return auth.showResetCredentials()?c> <#recover> <#return "ABORT: Couldn't evaluate auth.showResetCredentials()"> <#if areSamePath(path, ["auth", "showTryAnotherWayLink"])> <#attempt> <#return auth.showTryAnotherWayLink()?c> <#recover> <#return "ABORT: Couldn't evaluate auth.showTryAnotherWayLink()"> <#if areSamePath(path, ["url", "getLogoutUrl"])> <#local returnValue = -1> <#attempt> <#local returnValue = url.getLogoutUrl()> <#recover> <#return "ABORT: Couldn't evaluate url.getLogoutUrl()"> <#return 'function(){ return "' + returnValue + '"; }'> <#if areSamePath(path, ["totp", "policy", "getAlgorithmKey"])> <#local returnValue = "error"> <#if mode?? && mode = "manual"> <#attempt> <#local returnValue = totp.policy.getAlgorithmKey()> <#recover> <#return "ABORT: Couldn't evaluate totp.policy.getAlgorithmKey()"> <#return 'function(){ return "' + returnValue + '"; }'> <#assign fieldNames = [{{fieldNames}}]> <#if profile?? && profile.attributes??> <#list profile.attributes as attribute> <#if fieldNames?seq_contains(attribute.name)> <#continue> <#assign fieldNames += [attribute.name]> <#if areSamePath(path, ["messagesPerField", "get"])> <#local jsFunctionCode = "function (fieldName) { "> <#list fieldNames as fieldName> <#-- See: https://github.com/keycloakify/keycloakify/issues/217 --> <#if xKeycloakify.pageId == "login.ftl" > <#if fieldName == "username"> <#local jsFunctionCode += "if(fieldName === 'username' || fieldName === 'password' ){ "> <#if messagesPerField.exists('username') || messagesPerField.exists('password')> <#local jsFunctionCode += "return kcContext.message && kcContext.message.summary ? kcContext.message.summary : 'error'; "> <#else> <#local jsFunctionCode += "return ''; "> <#local jsFunctionCode += "} "> <#continue> <#if fieldName == "password"> <#continue> <#local jsFunctionCode += "if(fieldName === '" + fieldName + "'){ "> <#if messagesPerField.exists('${fieldName}')> <#local jsFunctionCode += 'return decodeHtmlEntities("' + messagesPerField.get('${fieldName}')?js_string + '"); '> <#else> <#local jsFunctionCode += "return ''; "> <#local jsFunctionCode += "} "> <#local jsFunctionCode += "}"> <#return jsFunctionCode> <#if areSamePath(path, ["messagesPerField", "existsError"])> <#local jsFunctionCode = "function (fieldName) { "> <#list fieldNames as fieldName> <#-- See: https://github.com/keycloakify/keycloakify/issues/217 --> <#if xKeycloakify.pageId == "login.ftl" > <#if fieldName == "username"> <#local jsFunctionCode += "if(fieldName === 'username' || fieldName === 'password' ){ "> <#if messagesPerField.existsError('username') || messagesPerField.existsError('password')> <#local jsFunctionCode += "return true; "> <#else> <#local jsFunctionCode += "return false; "> <#local jsFunctionCode += "} "> <#continue> <#if fieldName == "password"> <#continue> <#local jsFunctionCode += "if(fieldName === '" + fieldName + "' ){ "> <#if messagesPerField.existsError('${fieldName}')> <#local jsFunctionCode += 'return true; '> <#else> <#local jsFunctionCode += "return false; "> <#local jsFunctionCode += "}"> <#local jsFunctionCode += "}"> <#return jsFunctionCode> <#if xKeycloakify.themeType == "account" && areSamePath(path, ["realm", "isInternationalizationEnabled"])> <#attempt> <#return realm.isInternationalizationEnabled()?c> <#recover> <#return "ABORT: Couldn't evaluate realm.isInternationalizationEnabled()"> <#return "ABORT: It's a method"> <#local isBoolean = -1> <#attempt> <#local isBoolean = object?is_boolean> <#recover> <#return "ABORT: Can't test if it's a boolean"> <#if isBoolean> <#return object?c> <#local isEnumerable = -1> <#attempt> <#local isEnumerable = object?is_enumerable> <#recover> <#return "ABORT: Can't test if it's an enumerable"> <#if isEnumerable> <#local outSeq = []> <#local i = 0> <#list object as array_item> <#if !array_item??> <#local outSeq += ["null,"]> <#continue> <#local recOut = toJsDeclarationString(array_item, path + [ i ])> <#local i = i + 1> <#if recOut?starts_with("ABORT:")> <#local errorMessage = recOut?remove_beginning("ABORT:")> <#if errorMessage != " It's a method" > <#local outSeq += ["/*" + i?string + ": " + errorMessage + "*/"]> <#continue> <#local outSeq += [recOut + ","]> <#return (["["] + outSeq?map(str -> ""?right_pad(4 * (path?size + 1)) + str) + [ ""?right_pad(4 * path?size) + "]"])?join("\n")> <#local isDate = -1> <#attempt> <#local isDate = object?is_date_like> <#recover> <#return "ABORT: Can't test if it's a date"> <#if isDate> <#return '"' + object?datetime?iso_utc + '"'> <#local isNumber = -1> <#attempt> <#local isNumber = object?is_number> <#recover> <#return "ABORT: Can't test if it's a number"> <#if isNumber> <#return object?c> <#local isString = -1> <#attempt> <#local isString = object?is_string> <#recover> <#return "ABORT: Can't test if it's a string"> <#if isString> <@addToXKeycloakifyMessagesIfMessageKey str=object /> <#attempt> <#return '"' + object?js_string + '"'>; <#recover> <#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non number, non enumerable object"> <#function isSubpath path searchedPath> <#if path?size < searchedPath?size> <#return false> <#local i=0> <#list path as property> <#if i == searchedPath?size > <#continue> <#local searchedProperty=searchedPath[i]> <#local i+= 1> <#if searchedProperty?is_string && searchedProperty == "*"> <#continue> <#if searchedProperty?is_string && !property?is_string> <#return false> <#if searchedProperty?is_number && !property?is_number> <#return false> <#if searchedProperty?string != property?string> <#return false> <#return true> <#function areSamePath path searchedPath> <#return path?size == searchedPath?size && isSubpath(path, searchedPath)> <#macro addToXKeycloakifyMessagesIfMessageKey str> <#if !msg?? || !msg?is_method> <#return> <#if (str?length > 200)> <#return> <#local key=removeBrackets(str)> <#if key?length==0> <#return> <#if !(key?matches(r"^[a-zA-Z0-9-_.]*$"))> <#return> <#local resolvedMsg=msg(key)> <#if resolvedMsg==key> <#return> <#local messages=xKeycloakify.messages> <#local messages = messages + { key: resolvedMsg }> <#assign xKeycloakify = xKeycloakify + { "messages": messages }> <#function removeBrackets str> <#if str?starts_with("${") && str?ends_with("}")> <#return str[2..(str?length-2)]> <#else> <#return str> <#macro addNonAutomaticallyGatherableMessagesToXKeycloakifyMessages> <#if profile?? && profile?is_hash && profile.attributes?? && profile.attributes?is_enumerable> <#list profile.attributes as attribute> <#if !( attribute.annotations?? && attribute.annotations?is_hash && attribute.annotations.inputOptionLabelsI18nPrefix?? && attribute.annotations.inputOptionLabelsI18nPrefix?is_string )> <#continue> <#local prefix=attribute.annotations.inputOptionLabelsI18nPrefix> <#if !( attribute.validators?? && attribute.validators?is_hash && attribute.validators.options?? && attribute.validators.options?is_hash && attribute.validators.options.options?? && attribute.validators.options.options?is_enumerable )> <#continue> <#list attribute.validators.options.options as option> <#if !option?is_string> <#continue> <@addToXKeycloakifyMessagesIfMessageKey str="${prefix}.${option}" /> <#if xKeycloakify.pageId == "terms.ftl" || termsAcceptanceRequired?? && termsAcceptanceRequired> <@addToXKeycloakifyMessagesIfMessageKey str="termsText" /> <#if requiredActions?? && requiredActions?is_enumerable> <#list requiredActions as requiredAction> <#if !requiredAction?is_string> <#continue> <@addToXKeycloakifyMessagesIfMessageKey str="requiredAction.${requiredAction}" />