Skip to content

Commit 71d8646

Browse files
authored
Merge pull request #43 from devondragon/feature/demo-password-validation-fixes
Implement password validation fixes for Demo App
2 parents 38b5f6c + 5b6c3b6 commit 71d8646

7 files changed

Lines changed: 179 additions & 55 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ repositories {
3939

4040
dependencies {
4141
// DigitalSanctuary Spring User Framework
42-
implementation 'com.digitalsanctuary:ds-spring-user-framework:3.5.0'
42+
implementation 'com.digitalsanctuary:ds-spring-user-framework:3.5.1'
4343

4444
// Spring Boot starters
4545
implementation 'org.springframework.boot:spring-boot-starter-actuator'

src/main/resources/static/js/user/register.js

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import {
66
hideError,
77
clearErrors,
88
} from "/js/shared.js";
9+
import {
10+
calculateStrength,
11+
updateStrengthBar,
12+
initPasswordStrengthMeter,
13+
initPasswordRequirements,
14+
} from "/js/utils/password-validation.js";
915

1016
document.addEventListener("DOMContentLoaded", () => {
1117
const form = document.querySelector("#registerForm");
@@ -31,33 +37,14 @@ document.addEventListener("DOMContentLoaded", () => {
3137
});
3238
});
3339

34-
// Toggle password requirements visibility
40+
// Initialize password requirements visibility toggle
3541
const passwordRules = document.getElementById("password-requirements");
3642
const passwordError = document.querySelector("#passwordError");
37-
if (passwordField && passwordRules) {
38-
passwordField.addEventListener("focus", () => {
39-
passwordRules.classList.remove("d-none");
40-
// Hide the error while user is editing
41-
passwordError.classList.add("d-none");
42-
});
43-
44-
passwordField.addEventListener("blur", () => {
45-
passwordRules.classList.add("d-none");
46-
});
47-
}
43+
initPasswordRequirements(passwordField, passwordRules, passwordError);
4844

49-
// Password strength UI
50-
passwordField.addEventListener("input", () => {
51-
const passwordStrength = document.getElementById("password-strength");
52-
const password = passwordField.value;
53-
if (password) {
54-
passwordStrength.classList.remove("d-none");
55-
const score = calculateStrength(password);
56-
updateStrengthBar(score, strengthLevel, strengthLabel);
57-
} else {
58-
passwordStrength.classList.add("d-none");
59-
}
60-
});
45+
// Initialize password strength meter
46+
const passwordStrength = document.getElementById("password-strength");
47+
initPasswordStrengthMeter(passwordField, passwordStrength, strengthLevel, strengthLabel);
6148
});
6249

6350
async function handleRegistration(event) {
@@ -160,29 +147,3 @@ async function handleRegistration(event) {
160147
return msg.toLowerCase().includes("password");
161148
}
162149
}
163-
164-
function calculateStrength(password) {
165-
let score = 0;
166-
if (password.length >= 8) score++; // Rule 1: Length
167-
if (/[A-Z]/.test(password)) score++; // Rule 2: Uppercase
168-
if (/[a-z]/.test(password)) score++; // Rule 3: Lowercase
169-
if (/[0-9]/.test(password)) score++; // Rule 4: Number
170-
if (/[^A-Za-z0-9]/.test(password)) score++; // Rule 5: Special character
171-
return score;
172-
}
173-
174-
function updateStrengthBar(score, strengthLevel, strengthLabel) {
175-
const levels = [
176-
{ width: "0%", color: "", label: "" },
177-
{ width: "20%", color: "bg-danger", label: "Very Weak" },
178-
{ width: "40%", color: "bg-warning", label: "Weak" },
179-
{ width: "60%", color: "bg-info", label: "Fair" },
180-
{ width: "80%", color: "bg-primary", label: "Strong" },
181-
{ width: "100%", color: "bg-success", label: "Very Strong" },
182-
];
183-
184-
const level = levels[score] || levels[0];
185-
strengthLevel.style.width = level.width;
186-
strengthLevel.className = `progress-bar ${level.color}`;
187-
strengthLabel.textContent = `Password strength: ${level.label}`;
188-
}

src/main/resources/static/js/user/reset-password.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
// File: /js/user/reset-password.js
22
import { showMessage, showError, clearErrors } from "/js/shared.js";
3+
import {
4+
initPasswordStrengthMeter,
5+
initPasswordRequirements,
6+
} from "/js/utils/password-validation.js";
37

48
document.addEventListener("DOMContentLoaded", () => {
59
const form = document.querySelector("#resetPasswordForm");
@@ -9,6 +13,17 @@ document.addEventListener("DOMContentLoaded", () => {
913
const matchPasswordField = document.querySelector("#matchPassword");
1014
const matchPasswordError = document.querySelector("#matchPasswordError");
1115

16+
// Initialize password strength meter
17+
const passwordStrength = document.getElementById("password-strength");
18+
const strengthLevel = document.getElementById("strengthLevel");
19+
const strengthLabel = document.getElementById("strengthLabel");
20+
initPasswordStrengthMeter(passwordField, passwordStrength, strengthLevel, strengthLabel);
21+
22+
// Initialize password requirements visibility toggle
23+
const passwordRules = document.getElementById("password-requirements");
24+
const passwordError = document.querySelector("#passwordError");
25+
initPasswordRequirements(passwordField, passwordRules, passwordError);
26+
1227
form.addEventListener("submit", async (event) => {
1328
event.preventDefault();
1429
clearErrors();

src/main/resources/static/js/user/update-password.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
// File: /js/user/update-password.js
22
import { showMessage, showError, clearErrors } from "/js/shared.js";
3+
import {
4+
initPasswordStrengthMeter,
5+
initPasswordRequirements,
6+
} from "/js/utils/password-validation.js";
37

48
document.addEventListener("DOMContentLoaded", () => {
59
const form = document.querySelector("#updatePasswordForm");
@@ -9,6 +13,17 @@ document.addEventListener("DOMContentLoaded", () => {
913
const confirmPasswordField = document.querySelector("#confirmPassword");
1014
const confirmPasswordError = document.querySelector("#confirmPasswordError");
1115

16+
// Initialize password strength meter for new password field
17+
const passwordStrength = document.getElementById("password-strength");
18+
const strengthLevel = document.getElementById("strengthLevel");
19+
const strengthLabel = document.getElementById("strengthLabel");
20+
initPasswordStrengthMeter(newPasswordField, passwordStrength, strengthLevel, strengthLabel);
21+
22+
// Initialize password requirements visibility toggle
23+
const passwordRules = document.getElementById("password-requirements");
24+
const newPasswordError = document.querySelector("#newPasswordError");
25+
initPasswordRequirements(newPasswordField, passwordRules, newPasswordError);
26+
1227
form.addEventListener("submit", async (event) => {
1328
event.preventDefault();
1429
clearErrors();
@@ -25,9 +40,8 @@ document.addEventListener("DOMContentLoaded", () => {
2540

2641
// Prepare JSON payload
2742
const requestData = {
28-
currentPassword: currentPassword,
43+
oldPassword: currentPassword,
2944
newPassword: newPassword,
30-
confirmPassword: confirmPassword,
3145
};
3246

3347
try {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// File: /js/utils/password-validation.js
2+
/**
3+
* Shared password strength calculation and UI update utilities.
4+
* Used across registration, password reset, and password update forms.
5+
*/
6+
7+
/**
8+
* Calculate password strength score based on policy requirements.
9+
* @param {string} password - The password to evaluate
10+
* @returns {number} Score from 0-5 based on criteria met
11+
*/
12+
export function calculateStrength(password) {
13+
let score = 0;
14+
if (password.length >= 8) score++; // Rule 1: Length (min 8 chars)
15+
if (/[A-Z]/.test(password)) score++; // Rule 2: Uppercase letter
16+
if (/[a-z]/.test(password)) score++; // Rule 3: Lowercase letter
17+
if (/[0-9]/.test(password)) score++; // Rule 4: Digit
18+
if (/[^A-Za-z0-9]/.test(password)) score++; // Rule 5: Special character
19+
return score;
20+
}
21+
22+
/**
23+
* Update the password strength progress bar and label.
24+
* @param {number} score - Strength score (0-5)
25+
* @param {HTMLElement} strengthLevel - Progress bar element
26+
* @param {HTMLElement} strengthLabel - Label element for text
27+
*/
28+
export function updateStrengthBar(score, strengthLevel, strengthLabel) {
29+
const levels = [
30+
{ width: "0%", color: "", label: "" },
31+
{ width: "20%", color: "bg-danger", label: "Very Weak" },
32+
{ width: "40%", color: "bg-warning", label: "Weak" },
33+
{ width: "60%", color: "bg-info", label: "Fair" },
34+
{ width: "80%", color: "bg-primary", label: "Strong" },
35+
{ width: "100%", color: "bg-success", label: "Very Strong" },
36+
];
37+
38+
const level = levels[score] || levels[0];
39+
strengthLevel.style.width = level.width;
40+
strengthLevel.className = `progress-bar ${level.color}`;
41+
strengthLabel.textContent = `Password strength: ${level.label}`;
42+
}
43+
44+
/**
45+
* Initialize password strength meter on a password field.
46+
* @param {HTMLElement} passwordField - The password input field
47+
* @param {HTMLElement} strengthContainer - Container for the strength meter
48+
* @param {HTMLElement} strengthLevel - Progress bar element
49+
* @param {HTMLElement} strengthLabel - Label element
50+
*/
51+
export function initPasswordStrengthMeter(
52+
passwordField,
53+
strengthContainer,
54+
strengthLevel,
55+
strengthLabel
56+
) {
57+
passwordField.addEventListener("input", () => {
58+
const password = passwordField.value;
59+
if (password) {
60+
strengthContainer.classList.remove("d-none");
61+
const score = calculateStrength(password);
62+
updateStrengthBar(score, strengthLevel, strengthLabel);
63+
} else {
64+
strengthContainer.classList.add("d-none");
65+
}
66+
});
67+
}
68+
69+
/**
70+
* Initialize password requirements visibility toggle.
71+
* Shows requirements on focus, hides on blur.
72+
* @param {HTMLElement} passwordField - The password input field
73+
* @param {HTMLElement} requirementsContainer - Requirements list container
74+
* @param {HTMLElement} errorContainer - Error message container (optional)
75+
*/
76+
export function initPasswordRequirements(
77+
passwordField,
78+
requirementsContainer,
79+
errorContainer = null
80+
) {
81+
if (!passwordField || !requirementsContainer) return;
82+
83+
passwordField.addEventListener("focus", () => {
84+
requirementsContainer.classList.remove("d-none");
85+
if (errorContainer) {
86+
errorContainer.classList.add("d-none");
87+
}
88+
});
89+
90+
passwordField.addEventListener("blur", () => {
91+
requirementsContainer.classList.add("d-none");
92+
});
93+
}

src/main/resources/templates/user/forgot-password-change.html

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,36 @@ <h1 th:utext="#{password.reset-your-password}">Reset Your Password</h1>
2222
<div id="globalError" class="alert alert-danger d-none text-center" role="alert"></div>
2323

2424
<!-- Form -->
25-
<form id="resetPasswordForm" th:action="@{/user/resetPassword}" method="POST">
25+
<form id="resetPasswordForm" th:action="@{/user/savePassword}" method="POST">
2626
<div class="mb-3">
2727
<label for="password" class="form-label" th:utext="#{label.user.password}">New Password</label>
2828
<input type="password" id="password" name="newPassword" class="form-control" required>
29+
<!-- Password Strength display -->
30+
<div id="password-strength" class="mt-1 d-none">
31+
<div id="strengthBar" class="progress">
32+
<div id="strengthLevel" class="progress-bar" role="progressbar"
33+
style="width: 0%;"></div>
34+
</div>
35+
<small id="strengthLabel" class="text-muted">Password strength: </small>
36+
</div>
37+
<!-- Password requirements displayed on focus -->
38+
<div id="password-requirements" class="form-text text-muted mt-1 d-none"
39+
style="font-size: 0.875rem;">
40+
<strong>Password must:</strong>
41+
<ul class="mb-0 ps-3">
42+
<li>Be at least <strong>8</strong> characters</li>
43+
<li>Include at least <strong>1 uppercase</strong> letter (A–Z)</li>
44+
<li>Include at least <strong>1 lowercase</strong> letter (a–z)</li>
45+
<li>Include at least <strong>1 digit</strong> (0–9)</li>
46+
<li>Include at least <strong>1 special</strong> character (~`!@#$%^&*()_-+={}[]|\:;"'&lt;&gt;,.?/)</li>
47+
</ul>
48+
</div>
49+
<div id="passwordError" class="form-text text-danger d-none"></div>
2950
</div>
3051

3152
<div class="mb-3">
3253
<label for="matchPassword" class="form-label" th:utext="#{label.user.confirm-pass}">Confirm Password</label>
33-
<input type="password" id="matchPassword" class="form-control" required>
54+
<input type="password" id="matchPassword" name="confirmPassword" class="form-control" required>
3455
<div id="matchPasswordError" class="form-text text-danger d-none"></div>
3556
</div>
3657

src/main/resources/templates/user/update-password.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ <h1 th:utext="#{password.update-password}">Update Password</h1>
3434
<label for="newPassword" class="col-sm-4 col-form-label" th:utext="#{label.user.new-password}">New Password</label>
3535
<div class="col-sm-8">
3636
<input type="password" id="newPassword" name="newPassword" class="form-control" required>
37+
<!-- Password Strength display -->
38+
<div id="password-strength" class="mt-1 d-none">
39+
<div id="strengthBar" class="progress">
40+
<div id="strengthLevel" class="progress-bar" role="progressbar"
41+
style="width: 0%;"></div>
42+
</div>
43+
<small id="strengthLabel" class="text-muted">Password strength: </small>
44+
</div>
45+
<!-- Password requirements displayed on focus -->
46+
<div id="password-requirements" class="form-text text-muted mt-1 d-none"
47+
style="font-size: 0.875rem;">
48+
<strong>Password must:</strong>
49+
<ul class="mb-0 ps-3">
50+
<li>Be at least <strong>8</strong> characters</li>
51+
<li>Include at least <strong>1 uppercase</strong> letter (A–Z)</li>
52+
<li>Include at least <strong>1 lowercase</strong> letter (a–z)</li>
53+
<li>Include at least <strong>1 digit</strong> (0–9)</li>
54+
<li>Include at least <strong>1 special</strong> character (~`!@#$%^&*()_-+={}[]|\:;"'&lt;&gt;,.?/)</li>
55+
</ul>
56+
</div>
3757
<div id="newPasswordError" class="form-text text-danger d-none"></div>
3858
</div>
3959
</div>

0 commit comments

Comments
 (0)