added client side check for trivial password policies (#32846)

* added client side check for trivial  password policies

fixes: #32845

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* changed to make use of template

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* nicer active policy structure

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-09-12 14:26:35 +02:00 committed by GitHub
parent 0f97e4cb39
commit 22f9d2077e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 99 additions and 1 deletions

View file

@ -14,6 +14,7 @@
<#nested> <#nested>
<div id="input-error-client-${name}"></div>
<#if error?has_content> <#if error?has_content>
<div class="${properties.kcFormHelperTextClass}" aria-live="polite"> <div class="${properties.kcFormHelperTextClass}" aria-live="polite">
<div class="${properties.kcInputHelperTextClass}"> <div class="${properties.kcInputHelperTextClass}">

View file

@ -13,7 +13,6 @@
</#if> </#if>
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post" novalidate="novalidate"> <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post" novalidate="novalidate">
<@userProfileCommons.userProfileFormFields; callback, attribute> <@userProfileCommons.userProfileFormFields; callback, attribute>
<#if callback = "afterField"> <#if callback = "afterField">
<#-- render password fields just under the username or email (if used as username) --> <#-- render password fields just under the username or email (if used as username) -->
@ -46,5 +45,50 @@
</div> </div>
</form> </form>
<template id="errorTemplate">
<div class="${properties.kcFormHelperTextClass}" aria-live="polite">
<div class="${properties.kcInputHelperTextClass}">
<div class="${properties.kcInputHelperTextItemClass} ${properties.kcError}">
<ul class="${properties.kcInputErrorMessageClass}">
</ul>
</div>
</div>
</div>
</template>
<template id="errorItemTemplate">
<li></li>
</template>
<script type="module">
import { validatePassword } from "${url.resourcesPath}/js/password-policy.js";
const template = document.querySelector("#errorTemplate").content.cloneNode(true);
const activePolicies = [
{ name: "length", policy: { value: ${passwordPolicies.length!-1}, error: "${msg('invalidPasswordMinLengthMessage')}"} },
{ name: "maxLength", policy: { value: ${passwordPolicies.maxLength!-1}, error: "${msg('invalidPasswordMaxLengthMessage')}"} },
{ name: "lowerCase", policy: { value: ${passwordPolicies.lowerCase!-1}, error: "${msg('invalidPasswordMinLowerCaseCharsMessage')}"} },
{ name: "upperCase", policy: { value: ${passwordPolicies.upperCase!-1}, error: "${msg('invalidPasswordMinUpperCaseCharsMessage')}"} },
{ name: "digits", policy: { value: ${passwordPolicies.digits!-1}, error: "${msg('invalidPasswordMinDigitsMessage')}"} },
{ name: "specialChars", policy: { value: ${passwordPolicies.specialChars!-1}, error: "${msg('invalidPasswordMinSpecialCharsMessage')}"} }
].filter(p => p.policy.value !== -1);
document.getElementById("password").addEventListener("change", (event) => {
const serverErrors = document.getElementById("input-error-password");
if (serverErrors) {
serverErrors.remove();
}
const errors = validatePassword(event.target.value, activePolicies);
const errorList = template.querySelector("ul");
const htmlErrors = errors.forEach((e) => {
const row = document.querySelector("#errorItemTemplate").content.cloneNode(true);
const li = row.querySelector("li");
li.textContent = e;
errorList.appendChild(li);
});
document.getElementById("input-error-client-password").appendChild(template);
});
</script>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -0,0 +1,53 @@
const policies = {
length: (policy, value) => {
if (value.length < policy.value) {
return templateError(policy);
}
},
maxLength: (policy, value) => {
if (value.length > policy.value) {
return templateError(policy);
}
},
upperCase: (policy, value) => {
if (
value.split("").filter((char) => char !== char.toUpperCase()).length >
policy.value
) {
return templateError(policy);
}
},
lowerCase: (policy, value) => {
if (
value.split("").filter((char) => char !== char.toLowerCase()).length >
policy.value
) {
return templateError(policy);
}
},
digits: (policy, value) => {
const digits = value.split("").filter((char) => char.match(/\d/));
if (digits.length < policy.value) {
return templateError(policy);
}
},
specialChars: (policy, value) => {
let specialChars = value.split("").filter((char) => char.match(/\W/));
if (specialChars.length < policy.value) {
return templateError(policy);
}
},
};
const templateError = (policy) => policy.error.replace("{0}", policy.value);
export function validatePassword(password, activePolicies) {
const errors = [];
for (const p of activePolicies) {
const validationError = policies[p.name](p.policy, password);
if (validationError) {
errors.push(validationError);
}
}
return errors;
}