Skip to content

Commit 499f202

Browse files
devondragonclaude
andcommitted
Implement password validation fixes for Demo App
This commit implements all required and optional password validation enhancements to work with the Spring User Framework 3.5.1-SNAPSHOT password validation fixes. ## Critical Fixes (Phase 1) - Fix password reset form action from /user/resetPassword to /user/savePassword - Add confirmPassword field name to match password input field - Update framework dependency to 3.5.1-SNAPSHOT ## Optional Improvements (Phase 2) - Update password field name from currentPassword to oldPassword for consistency with backend PasswordDto ## UX Enhancements (Phase 3) - Extract password strength meter to shared module (password-validation.js) - Refactor register.js to use shared password validation utilities - Add password strength meter to forgot-password-change form - Add password strength meter to update-password form - Add password requirements checklist to both forms ## Testing - All existing tests pass - Build successful ## Files Changed - src/main/resources/templates/user/forgot-password-change.html - src/main/resources/templates/user/update-password.html - src/main/resources/static/js/user/register.js - src/main/resources/static/js/user/reset-password.js - src/main/resources/static/js/user/update-password.js - src/main/resources/static/js/utils/password-validation.js (new) - build.gradle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 38b5f6c commit 499f202

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-SNAPSHOT'
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)