Skip to content

Commit

Permalink
Login-UI Password Strength Indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
andreas-blaettlinger committed Oct 23, 2023
1 parent 6946677 commit 282fd93
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
</button>
</div>

<#if properties.showPasswordQuality = "true">
<script type="module" src="${url.resourcesPath}/js/passwordEntropy.js"></script>
<password-entropy data-password-poor="${msg('poorPassword')}"
data-password-weak="${msg('weakPassword')}"
data-password-good="${msg('goodPassword')}"
data-password-strong="${msg('strongPassword')}">
</password-entropy>
</#if>

<#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('password'))?no_esc}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ passwordNew=New Password
passwordNewConfirm=New Password confirmation
hidePassword=Hide password
showPassword=Show password
poorPassword=Poor password
weakPassword=Weak password
goodPassword=Good password
strongPassword=Strong password
rememberMe=Remember me
authenticatorCode=One-time code
address=Address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>

<#if properties.showPasswordQuality = "true">
<script type="module" src="${url.resourcesPath}/js/passwordEntropy.js"></script>
<password-entropy data-password-poor="${msg('poorPassword')}"
data-password-weak="${msg('weakPassword')}"
data-password-good="${msg('goodPassword')}"
data-password-strong="${msg('strongPassword')}">
</password-entropy>
</#if>

<#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
Expand Down
8 changes: 8 additions & 0 deletions themes/src/main/resources/theme/base/login/register.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@
</button>
</div>

<#if properties.showPasswordQuality = "true">
<script type="module" src="${url.resourcesPath}/js/passwordEntropy.js"></script>
<password-entropy data-password-poor="${msg('poorPassword')}"
data-password-weak="${msg('weakPassword')}"
data-password-good="${msg('goodPassword')}"
data-password-strong="${msg('strongPassword')}">
</password-entropy>
</#if>

<#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const template = document.createElement("template");
template.innerHTML = `
${
[...document.styleSheets].map((styleSheet) => `<link href="${styleSheet.href}" rel="stylesheet"/>`)
.join("\n")
}
<style>
.progress {
margin-top: 1px;
margin-bottom: 0;
}
</style>
<div class="progress">
<div class="progress-bar" role="status"></div>
</div>
`;

class PasswordEntropy extends HTMLElement {
constructor() {
super();
}

connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.append(template.content.cloneNode(true));

const passwordElement = this.parentElement.querySelector("input[type=password]");
passwordElement.oninput = () => this.update(passwordElement.value);

this.entropyStatusElement = this.shadowRoot.querySelector(".progress-bar");

this.rating = [this.dataset.passwordPoor, this.dataset.passwordWeak, this.dataset.passwordGood, this.dataset.passwordStrong];
this.ratingClassNames = ["progress-bar-danger", "progress-bar-warning", "progress-bar-info", "progress-bar-success"];
this.ratingRange = 25;

this.style.display = "none";
}

update = (password) => {
if (!password.length) {
this.style.display = "none";
return;
}

const entropy = this.calculateEntropy(password);

this.updateElements(entropy);
}

// 00-24 poor password
// 25-49 weak password
// 50-74 good password
// 75-100 strong password
// entropy source: https://www.baeldung.com/cs/password-entropy
calculateEntropy = (password) => {
let charSetSize = 0;
if (/[a-z]/.test(password)) charSetSize += 26;
if (/[A-Z]/.test(password)) charSetSize += 26;
if (/[0-9]/.test(password)) charSetSize += 10;
if (/\W|_/.test(password)) charSetSize += 32; // 149_878 available unicode chars
return Math.log2(charSetSize ** password.length);
}

clamp = (number, min, max) => Math.max(min, Math.min(number, max));

updateElements = (entropy) => {
const ratingIndex = Math.trunc(entropy / this.ratingRange);
const clampedIndex = this.clamp(ratingIndex, 0, this.rating.length - 1);

this.ratingClassNames.forEach(clazz => this.entropyStatusElement.classList.remove(clazz));
this.entropyStatusElement.innerText = this.rating[clampedIndex];
this.entropyStatusElement.classList.add(this.ratingClassNames[clampedIndex]);
this.entropyStatusElement.style.width = `${(clampedIndex+1) * this.ratingRange}%`;
this.style.display = "block";
}
}

customElements.define("password-entropy", PasswordEntropy);
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,6 @@ kcRecoveryCodesConfirmation=kc-recovery-codes-confirmation
kcCheckClass=pf-c-check
kcCheckInputClass=pf-c-check__input
kcCheckLabelClass=pf-c-check__label

## UI configuration
showPasswordQuality=true

0 comments on commit 282fd93

Please sign in to comment.