Secure Password Change & UI Enhancement (closes #4939)#5777
Secure Password Change & UI Enhancement (closes #4939)#5777dineshyr29-04 wants to merge 12 commits into
Conversation
…nts and ProgramModule field validations
…constraint handling
… validation logic
…r29-04/ESP-Website into class-constraints-module
There was a problem hiding this comment.
Pull request overview
This PR tightens password-change validation in the MyESP account workflow and adds UI affordances for password field visibility. It also introduces a new “Class Constraints” management module (plus supporting schedule-token model/migration/admin wiring) for program scheduling constraints.
Changes:
- Add server-side password rules: reject identical current/new password and enforce complexity requirements.
- Update the change-password template to add per-field “show/hide password” toggles with inline styling/script.
- Add a new Class Constraints module (templates/handler/tests) and a new
ScheduleTestSubjecttoken type to support subject-based schedule constraints.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| esp/templates/users/passwd.html | Adds password visibility toggles and styling to the change-password page. |
| esp/esp/users/forms/password_reset.py | Adds new-password validation (same-as-current + complexity). |
| esp/esp/program/modules/handlers/classconstraintsmodule.py | New admin-only module to create/delete schedule constraints using subject tokens. |
| esp/templates/program/modules/classconstraintsmodule/main.html | UI for listing/creating/deleting class constraints. |
| esp/esp/program/modules/handlers/tests/test_classconstraintsmodule.py | Tests for the new module’s constraint creation/evaluation/deletion behaviors. |
| esp/esp/program/models/init.py | Adds ScheduleTestSubject token model and cache invalidation dependencies. |
| esp/esp/program/migrations/0038_scheduletestsubject.py | Creates DB table for ScheduleTestSubject. |
| esp/esp/program/admin.py | Registers ScheduleTestSubject in Django admin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| outline: none !important; | ||
| } | ||
| .toggle-password:hover { | ||
| color: #444; | ||
| } |
There was a problem hiding this comment.
.toggle-password forces outline: none !important;, which removes the focus indicator and is an accessibility regression. If you need custom styling, replace it with a visible focus style instead of disabling outlines.
| outline: none !important; | |
| } | |
| .toggle-password:hover { | |
| color: #444; | |
| } | |
| } | |
| .toggle-password:hover { | |
| color: #444; | |
| } | |
| .toggle-password:focus-visible { | |
| outline: 2px solid #444; | |
| outline-offset: 2px; | |
| border-radius: 2px; | |
| } |
| {% if form.password.errors %} | ||
| <span class="form_error">{{ form.password.errors|join:", " }}</span> | ||
| {% endif %} |
There was a problem hiding this comment.
The password field error rendering dropped role="alert"/live region behavior from the previous markup, so screen readers may not announce validation errors. Consider restoring role="alert" (or aria-live="polite") on the error container spans.
| subject_exps = ScheduleTestSubject.objects.values_list('exp_id', flat=True) | ||
| context['constraints'] = ScheduleConstraint.objects.filter(program=prog, condition_id__in=subject_exps) | ||
| return render_to_response(self.baseDir() + 'main.html', request, context) |
There was a problem hiding this comment.
subject_exps = ScheduleTestSubject.objects.values_list('exp_id', flat=True) is unscoped and may pull IDs for all programs; combined with condition_id__in=... this can produce a very large IN (...) list. Filter by the current program (e.g., subject__parent_program=prog) and distinct() the IDs, or use a join-based filter instead.
| try: | ||
| subject_exps = ScheduleTestSubject.objects.values_list('exp_id', flat=True) | ||
| constraint = ScheduleConstraint.objects.get(id=constraint_id, program=prog, condition_id__in=subject_exps) | ||
| except ScheduleConstraint.DoesNotExist: |
There was a problem hiding this comment.
Same unscoped values_list('exp_id') issue in deletion: it may scan/return exp IDs across all programs. Filter ScheduleTestSubject by subject__parent_program=prog (and ideally distinct()) to keep the lookup small and predictable.
| elif 'delete' in request.POST: | ||
| raw_id = request.POST.get('constraint_id', '') | ||
| # Fix: validate/cast before querying to avoid ValueError 500s. | ||
| try: | ||
| constraint_id = int(raw_id) | ||
| except (ValueError, TypeError): | ||
| return HttpResponseBadRequest("Invalid constraint ID.") | ||
| self._delete_constraint(constraint_id, prog) |
There was a problem hiding this comment.
The module adds explicit handling for non-integer constraint_id values (returning 400), but the test suite in this PR doesn’t cover that branch. Add a request-level test (or a unit test around the POST handler) that posts a non-integer constraint_id and asserts a 400 response.
| def clean_newpasswd(self): | ||
| new_passwd = self.cleaned_data.get('newpasswd') | ||
| current_passwd = self.cleaned_data.get('password') | ||
|
|
||
| if new_passwd == current_passwd: | ||
| raise forms.ValidationError("New password cannot be the same as the current password.") | ||
|
|
||
| if len(new_passwd) < 8: | ||
| raise forms.ValidationError("Password must be at least 8 characters long.") |
There was a problem hiding this comment.
clean_newpasswd() assumes new_passwd is always present; if the field fails its own validation (e.g., missing/too long), Django will omit it from cleaned_data and len(new_passwd) will raise TypeError, turning a normal validation error into a 500. Guard for None (and return early) before running length/regex checks.
| context['form'] = ClassConstraintsForm(prog) | ||
| subject_exps = ScheduleTestSubject.objects.values_list('exp_id', flat=True) | ||
| context['constraints'] = ScheduleConstraint.objects.filter(program=prog, condition_id__in=subject_exps) | ||
| return render_to_response(self.baseDir() + 'main.html', request, context) |
There was a problem hiding this comment.
Same as above: this unfiltered values_list('exp_id') can generate an unnecessarily large IN (...) for constraints listing. Scope the ScheduleTestSubject query to prog and distinct() it, or use an ORM join to avoid materializing IDs.
| class ClassConstraintsModule(ProgramModuleObj, CoreModule): | ||
| """ | ||
| Module for managing complex class constraints like prerequisites and | ||
| mutual exclusions. Constraints are represented as ScheduleConstraints | ||
| using BooleanExpressions composed of ScheduleTestSubject tokens. | ||
| """ |
There was a problem hiding this comment.
The PR description/linked issue is about password change security & UI, but this file adds an entire new Class Constraints management module plus related model/migration changes. Please either update the PR description to cover this scope or split these changes into a separate PR to keep review and risk manageable.
| - Fix ClassSubject.objects.create() — requires category, grade_min, grade_max. | ||
| - Use ScheduleMap for evaluation tests instead of a raw dict. | ||
| - Add test for class_a == class_b form validation. | ||
| - Add test for invalid constraint_id (non-integer) handling. |
There was a problem hiding this comment.
This test module docstring claims a test was added for invalid (non-integer) constraint_id handling, but no such test exists in the file. Either add the test or update/remove the docstring bullet to match what’s actually covered.
| - Add test for invalid constraint_id (non-integer) handling. |
| <button type="button" class="toggle-password" tabindex="-1" aria-label="Toggle password visibility"> | ||
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="eye-icon"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg> | ||
| </button> |
There was a problem hiding this comment.
The visibility toggle button is removed from the tab order (tabindex="-1"), which makes it unusable for keyboard-only users. Since this directly affects password entry UX, keep it focusable (default) and ensure it can be triggered via keyboard.
|
Hi @dineshyr29-04 |
|
ok i will see once and i will get it done. |
Closes #4939
Overview
This PR implements critical security enhancements for the password change functionality and adds a premium visibility toggler for better UX.
Changes
🛡️ Security Fixes
✨ UI Enhancements
Verification