Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions app/eventyay/common/forms/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,20 @@ def render(self, name, value, attrs=None, renderer=None):
aria-valuemax="4">
</div>
</div>
<p class="text-muted password_strength_info d-none">
<span style="margin-left:5px;">
{message}
</span>
</p>
<div class="password_strength_info d-none">
<p class="text-muted mb-0">
<span style="margin-left:5px;">
{message}
</span>
</p>
</div>
Comment on lines +40 to +46
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline styles should be avoided in favor of CSS classes for better maintainability and consistency. Replace style=\"margin-left:5px;\" with a CSS class, for example class=\"ps-1\" (Bootstrap padding-start) or define a custom class in the CSS file.

Copilot uses AI. Check for mistakes.
<div class="password-requirements text-muted small mt-1">
<small>
Password must be at least 8 characters long and contain letters, numbers, and special characters.
</small>
</div>
</div>
""".format(message=_('This password would take <em class="password_strength_time"></em> to crack.'))
""".format(message=_('This password would take some time to crack.'))

self.attrs = add_class(self.attrs, 'password_strength')
self.attrs['autocomplete'] = 'new-password'
Expand Down
23 changes: 23 additions & 0 deletions app/eventyay/static/common/css/_forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -779,3 +779,26 @@ input.password_strength {
}
}
}

.password-validation-errors {
margin-top: 0.5rem;
padding: 0.5rem;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.25rem;
}

.password-validation-errors span {
font-size: 0.875rem;
margin-bottom: 0.25rem;
}

.password-requirements {
margin-top: 0.25rem;
color: #6c757d;
}

.password-strength-input:focus ~ .password-requirements {
color: #495057;
font-weight: 500;
}
106 changes: 91 additions & 15 deletions app/eventyay/static/common/js/password_strength.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,41 @@ const matchPasswords = (passwordField, confirmationFields) => {
}
}

const validatePasswordComplexity = (password) => {
const errors = []

if (password.length < 8) {
errors.push("Password must be at least 8 characters long")
}

if (!/[a-zA-Z]/.test(password)) {
errors.push("Password must contain at least one letter")
}

if (!/[0-9]/.test(password)) {
errors.push("Password must contain at least one number")
}

if (!/[!@#$%^&*()_\-+=\[\\\]{}|;':",.<>/?`~]/.test(password)) {
errors.push("Password must contain at least one special character")
}

const commonPasswords = [
"123456", "password", "123456789", "12345678", "12345", "111111", "1234567", "sunshine", "qwerty", "iloveyou",
"princess", "admin", "welcome", "666666", "abc123", "football", "123123", "monkey", "654321", "!@#$%^&*",
"charlie", "aa123456", "donald", "password1", "qwerty123", "letmein", "1234", "123321", "superman", "hello",
"whatever", "michael", "dragon", "baseball", "master", "trustno1", "jordan", "jennifer", "hunter", "cookie",
"secret", "mustang", "shadow", "summer", "ashley", "bailey", "passw0rd", "batman", "zaq1zaq1", "qazwsx",
"password123", "1q2w3e4r", "qwertyuiop", "123qwe", "123456a", "696969", "qwe123", "1qaz2wsx", "qwerty1",
"1234567890", "qwerty12", "123456789a", "password!", "password1234", "password12345", "password123456"
]
Comment on lines +64 to +72
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commonPasswords array is recreated on every call to validatePasswordComplexity(). Move this constant outside the function scope to avoid unnecessary array allocations on each validation.

Copilot uses AI. Check for mistakes.
if (commonPasswords.includes(password.toLowerCase())) {
errors.push("This password is too common")
}

return errors
}

const updatePasswordStrength = (passwordField) => {
const passwordStrengthBar = passwordField.parentNode.querySelector(
".password_strength_bar",
Expand All @@ -56,29 +91,70 @@ const updatePasswordStrength = (passwordField) => {
passwordStrengthBar.style.width = "0%"
passwordStrengthBar.setAttribute("aria-valuenow", 0)
passwordStrengthInfo.classList.add("d-none")
return
}

const validationErrors = validatePasswordComplexity(passwordField.value);
passwordStrengthInfo.textContent = "";
Comment on lines +97 to +98
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider clearing passwordStrengthInfo content before checking validation errors to maintain consistent cleanup order. Move line 98 before line 97, or even better, clear it at the start of the function after the empty password check to ensure a clean slate for all code paths.

Suggested change
const validationErrors = validatePasswordComplexity(passwordField.value);
passwordStrengthInfo.textContent = "";
passwordStrengthInfo.textContent = "";
const validationErrors = validatePasswordComplexity(passwordField.value);

Copilot uses AI. Check for mistakes.

if (validationErrors.length > 0) {
passwordStrengthBar.classList.remove("bg-success", "bg-warning");
passwordStrengthBar.classList.add("bg-danger");
passwordStrengthBar.style.width = "20%";
passwordStrengthBar.setAttribute("aria-valuenow", 1);

const container = document.createElement("div");
container.className = "password-validation-errors";

validationErrors.forEach((err) => {
const span = document.createElement("span");
span.className = "d-block text-danger";

const icon = document.createElement("i");
icon.className = "fa fa-times-circle";
icon.setAttribute("aria-hidden", "true");

span.appendChild(icon);
span.append(` ${err}`);
container.appendChild(span);
});
passwordStrengthInfo.appendChild(container);
passwordStrengthInfo.classList.remove("d-none");
} else {
const result = zxcvbn(passwordField.value)
const crackTime =
result.crack_times_display.online_no_throttling_10_per_second
const result = zxcvbn(passwordField.value);
const crackTime = result.crack_times_display.online_no_throttling_10_per_second;

passwordStrengthBar.classList.remove("bg-danger", "bg-warning", "bg-success");
if (result.score < 1) {
passwordStrengthBar.classList.remove("bg-success")
passwordStrengthBar.classList.add("bg-danger")
passwordStrengthBar.classList.add("bg-danger");
} else if (result.score < 3) {
passwordStrengthBar.classList.remove("bg-danger")
passwordStrengthBar.classList.add("bg-warning")
passwordStrengthBar.classList.add("bg-warning");
} else {
passwordStrengthBar.classList.add("bg-success");
}

passwordStrengthBar.style.width = `${((result.score + 1) / 5) * 100}%`;
passwordStrengthBar.setAttribute("aria-valuenow", result.score + 1);
const p = document.createElement("p");
p.className = "text-muted mb-0";

const span = document.createElement("span");
if (result.score >= 3) {
span.className = "text-success";
const icon = document.createElement("i");
icon.className = "fa fa-check-circle";
icon.setAttribute("aria-hidden", "true");
span.appendChild(icon);
span.append(` This password would take ${crackTime} to crack.`);
} else {
passwordStrengthBar.classList.remove("bg-warning")
passwordStrengthBar.classList.add("bg-success")
span.textContent = `This password would take ${crackTime} to crack.`;
}

passwordStrengthBar.style.width = `${((result.score + 1) / 5) * 100}%`
passwordStrengthBar.setAttribute("aria-valuenow", result.score + 1)
passwordStrengthInfo.querySelector(
".password_strength_time",
).innerHTML = crackTime
passwordStrengthInfo.classList.remove("d-none")
p.appendChild(span);
passwordStrengthInfo.appendChild(p);
passwordStrengthInfo.classList.remove("d-none");
}

matchPasswords(passwordField)
}

Expand Down