Jul 2nd - Optimized code 2

This commit is contained in:
2026-07-02 15:48:24 -04:00
parent c633960e3d
commit 19c719ea98
8 changed files with 29 additions and 17 deletions
+1 -1
View File
@@ -354,7 +354,7 @@ def edit_user(user_id):
new_pw = request.form.get('new_password', '') new_pw = request.form.get('new_password', '')
if new_pw: if new_pw:
confirm_pw = request.form.get('confirm_password', '') confirm_pw = request.form.get('confirm_password', '')
pw_error = validate_password(new_pw, confirm_pw) pw_error = validate_password(new_pw, confirm_pw, user.password_hash)
if pw_error: if pw_error:
flash(pw_error, 'danger') flash(pw_error, 'danger')
return render_template('admin/edit_user.html', user=user, roles=_roles()) return render_template('admin/edit_user.html', user=user, roles=_roles())
+3 -3
View File
@@ -173,7 +173,7 @@ def profile():
logger.info(f'[AUTH AVATAR UPLOAD] user_id={current_user.id} file={stored_name}') logger.info(f'[AUTH AVATAR UPLOAD] user_id={current_user.id} file={stored_name}')
if new_pw: if new_pw:
pw_error = validate_password(new_pw, confirm_pw) pw_error = validate_password(new_pw, confirm_pw, current_user.password_hash)
if pw_error: if pw_error:
flash(pw_error, 'danger') flash(pw_error, 'danger')
return render_template('auth/profile.html') return render_template('auth/profile.html')
@@ -236,12 +236,12 @@ def reset_password(token):
if request.method == 'POST': if request.method == 'POST':
password = request.form.get('password', '') password = request.form.get('password', '')
confirm = request.form.get('confirm_password', '') confirm = request.form.get('confirm_password', '')
pw_error = validate_password(password, confirm) user = token_row.user
pw_error = validate_password(password, confirm, user.password_hash)
if pw_error: if pw_error:
flash(pw_error, 'danger') flash(pw_error, 'danger')
return render_template('auth/reset_password.html', token=token) return render_template('auth/reset_password.html', token=token)
user = token_row.user
user.set_password(password) user.set_password(password)
db.session.delete(token_row) # single-use — delete immediately db.session.delete(token_row) # single-use — delete immediately
log_action(user.id, 'password_reset', 'user', user.id) log_action(user.id, 'password_reset', 'user', user.id)
+13 -1
View File
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
# ─── Password Validation ────────────────────────────────────────────────────── # ─── Password Validation ──────────────────────────────────────────────────────
def validate_password(password: str, confirm: str) -> str | None: def validate_password(password: str, confirm: str, current_hash: str | None = None) -> str | None:
"""Validate a new password and its confirmation field. """Validate a new password and its confirmation field.
Returns an error message string if validation fails, or None if the Returns an error message string if validation fails, or None if the
@@ -27,11 +27,17 @@ def validate_password(password: str, confirm: str) -> str | None:
- Must contain at least one uppercase letter (A-Z). - Must contain at least one uppercase letter (A-Z).
- Must contain at least one digit (0-9). - Must contain at least one digit (0-9).
- Must contain at least one special character (!@#$%^&* etc.). - Must contain at least one special character (!@#$%^&* etc.).
- If current_hash is given, the new password must differ from it.
Parameters Parameters
---------- ----------
password : str the candidate password (plain text) password : str the candidate password (plain text)
confirm : str the confirmation field value confirm : str the confirmation field value
current_hash : str|None the user's existing password_hash, if this is
a password *change* (profile, admin edit,
reset) rather than new-account creation.
When given, re-using the current password is
rejected.
""" """
import re import re
if password != confirm: if password != confirm:
@@ -48,6 +54,12 @@ def validate_password(password: str, confirm: str) -> str | None:
return 'Password must contain at least one number.' return 'Password must contain at least one number.'
if not re.search(r'[!@#$%^&*()\-_=+\[\]{};:\'",.<>?/\\|`~]', password): if not re.search(r'[!@#$%^&*()\-_=+\[\]{};:\'",.<>?/\\|`~]', password):
return 'Password must contain at least one special character.' return 'Password must contain at least one special character.'
# Hash comparison is the most expensive check, so it runs last —
# only after every cheap format check has already passed.
if current_hash:
from werkzeug.security import check_password_hash
if check_password_hash(current_hash, password):
return 'New password must be different from your current password.'
return None return None
+2 -2
View File
@@ -124,7 +124,7 @@
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </button>
</div> </div>
<div class="pw-match" id="pw-match-msg"></div> <div class="pw-match" id="pw-match-msg" aria-live="polite" aria-atomic="true"></div>
</div> </div>
<div class="col-12"> <div class="col-12">
<!-- Strength bar --> <!-- Strength bar -->
@@ -136,7 +136,7 @@
</div> </div>
<!-- Rule checklist — mirrors server-side validate_password() rules --> <!-- Rule checklist — mirrors server-side validate_password() rules -->
<div class="pw-rules" id="pw-rules-box" style="display:none;"> <div class="pw-rules" id="pw-rules-box" style="display:none;" aria-live="polite" aria-atomic="true">
<div class="pw-rule unmet" id="pw-rule-len"> <div class="pw-rule unmet" id="pw-rule-len">
<span class="ri"><i class="bi bi-circle"></i></span> <span class="ri"><i class="bi bi-circle"></i></span>
<span>8+ characters</span> <span>8+ characters</span>
+2 -2
View File
@@ -70,7 +70,7 @@
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </button>
</div> </div>
<div class="pw-match" id="pw-match-msg"></div> <div class="pw-match" id="pw-match-msg" aria-live="polite" aria-atomic="true"></div>
</div> </div>
<div class="col-12"> <div class="col-12">
<!-- Strength bar --> <!-- Strength bar -->
@@ -82,7 +82,7 @@
</div> </div>
<!-- Rule checklist — mirrors server-side validate_password() rules --> <!-- Rule checklist — mirrors server-side validate_password() rules -->
<div class="pw-rules" id="pw-rules-box" style="display:none;"> <div class="pw-rules" id="pw-rules-box" style="display:none;" aria-live="polite" aria-atomic="true">
<div class="pw-rule unmet" id="pw-rule-len"> <div class="pw-rule unmet" id="pw-rule-len">
<span class="ri"><i class="bi bi-circle"></i></span> <span class="ri"><i class="bi bi-circle"></i></span>
<span>8+ characters</span> <span>8+ characters</span>
+2 -2
View File
@@ -94,7 +94,7 @@
<div class="pw-strength-label" id="prof-strength-label"></div> <div class="pw-strength-label" id="prof-strength-label"></div>
</div> </div>
<!-- Rule checklist --> <!-- Rule checklist -->
<div class="pw-rules" id="prof-rules" style="display:none;"> <div class="pw-rules" id="prof-rules" style="display:none;" aria-live="polite" aria-atomic="true">
<div class="pw-rule unmet" id="prof-rule-len"><span class="ri"><i class="bi bi-circle"></i></span> 8+ characters</div> <div class="pw-rule unmet" id="prof-rule-len"><span class="ri"><i class="bi bi-circle"></i></span> 8+ characters</div>
<div class="pw-rule unmet" id="prof-rule-upper"><span class="ri"><i class="bi bi-circle"></i></span> Uppercase letter</div> <div class="pw-rule unmet" id="prof-rule-upper"><span class="ri"><i class="bi bi-circle"></i></span> Uppercase letter</div>
<div class="pw-rule unmet" id="prof-rule-digit"><span class="ri"><i class="bi bi-circle"></i></span> Number</div> <div class="pw-rule unmet" id="prof-rule-digit"><span class="ri"><i class="bi bi-circle"></i></span> Number</div>
@@ -112,7 +112,7 @@
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </button>
</div> </div>
<div class="pw-match" id="prof-match-msg"></div> <div class="pw-match" id="prof-match-msg" aria-live="polite" aria-atomic="true"></div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -121,7 +121,7 @@
</div> </div>
<!-- Rule checklist — mirrors server-side validate_password() rules --> <!-- Rule checklist — mirrors server-side validate_password() rules -->
<div class="pw-rules" id="rules-box" style="display:none;"> <div class="pw-rules" id="rules-box" style="display:none;" aria-live="polite" aria-atomic="true">
<div class="pw-rule unmet" id="rule-len"> <div class="pw-rule unmet" id="rule-len">
<span class="ri"><i class="bi bi-circle"></i></span> <span class="ri"><i class="bi bi-circle"></i></span>
<span>8+ characters</span> <span>8+ characters</span>
@@ -155,7 +155,7 @@
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </button>
</div> </div>
<div class="pw-match" id="match-msg"></div> <div class="pw-match" id="match-msg" aria-live="polite" aria-atomic="true"></div>
</div> </div>
<button type="submit" class="btn" id="submit-btn"> <button type="submit" class="btn" id="submit-btn">
+2 -2
View File
@@ -79,7 +79,7 @@
</div> </div>
<!-- Rule checklist — mirrors server-side validate_password() rules --> <!-- Rule checklist — mirrors server-side validate_password() rules -->
<div class="pw-rules" id="rules-box" style="display:none;"> <div class="pw-rules" id="rules-box" style="display:none;" aria-live="polite" aria-atomic="true">
<div class="pw-rule unmet" id="rule-len"> <div class="pw-rule unmet" id="rule-len">
<span class="ri"><i class="bi bi-circle"></i></span> <span class="ri"><i class="bi bi-circle"></i></span>
<span>8+ characters</span> <span>8+ characters</span>
@@ -109,7 +109,7 @@
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </button>
</div> </div>
<div class="pw-match" id="match-msg"></div> <div class="pw-match" id="match-msg" aria-live="polite" aria-atomic="true"></div>
</div> </div>
<button type="submit" class="btn" id="submit-btn"><i class="bi bi-lock me-2"></i>Update Password</button> <button type="submit" class="btn" id="submit-btn"><i class="bi bi-lock me-2"></i>Update Password</button>
</form> </form>