Custom reset password implementation
Proposed Changes
- Implemented a custom password reset token system using our own
PasswordResetTokenmodel - Updated related views to work with our custom model
- Ensured compatibility with existing signal handlers for email notifications
Architecture changes
- Used custom
PasswordResetTokenmodel with appropriate related names to prevent field clashes - Maintained signal compatibility to use existing email sending infrastructure
Merge Checklist
- [ ] Tests added/fixed
- [ ] Update docs in docs
- [ ] Linting Complete
- [ ] Any other necessary step
Only PR's with test cases included and passing lint and test pipelines will be reviewed
@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins
Implementation Details
The custom password reset implementation:
- Uses our own
PasswordResetTokenmodel with proper relationship naming - Implements standard security features:
- Cryptographically secure token generation
- Token expiration (configurable via settings)
- Protection against user enumeration attacks
- Rate limiting to prevent brute force attempts
- Maintains compatibility with existing email sending mechanisms through Django signals
- Follows best practices for password reset flows:
- Clear expired tokens automatically
- Invalidate previous tokens when requesting new ones
- Track token usage to prevent reuse
- Store user agent and IP address for security auditing 5.Tested the Apis with local server logging on the results.
Summary by CodeRabbit
-
New Features
- Introduced improved password reset functionality with enhanced token validation and structured responses for expired or invalid tokens.
- Added new request and response formats for password reset operations.
-
Bug Fixes
- Password reset email links are now generated using a new, more secure token system.
-
Refactor
- Password reset and email sending logic has been centralized and streamlined for consistency and security.
- Legacy signal-based and model-based password reset mechanisms have been removed.
- Updated password creation email sending to use the new centralized utility with support for different email types.
-
Chores
- Removed obsolete utility files and signal handlers related to password reset.
- Updated templates to align with the new password reset process.
- Removed the third-party password reset package and related dependencies.
๐ Walkthrough
Walkthrough
Replaced django-rest-passwordreset token model/signals with a JWT-based token flow and new utilities; reset endpoints now use Pydantic request/response schemas from care.emr.resources.user.spec, call care.emr.utils.reset_password for token generation/verification/email sending, removed legacy signals and token-creation utilities, added comprehensive tests and config for TTL and information-leakage behavior.
Changes
| Cohort / File(s) | Change Summary |
|---|---|
Viewscare/users/reset_password_views.py |
Endpoints switched from django-rest-passwordreset models/serializers/signals to Pydantic request/response schemas (ResetPasswordCheckRequest, ResetPasswordConfirmRequest, ResetPasswordRequestTokenRequest, ResetPasswordResponse) and call verify_password_reset_token / send_password_reset_email; removed serializer_class attributes and legacy token model logic; added rate-limit and error-path mappings. |
Signalscare/users/signals.py |
Removed password_reset_token_created handler and its email/template imports. |
User APIcare/emr/api/viewsets/user.py |
Replaced old email utility call with send_password_reset_email(user, mail_type) using MailTypeChoices.create; updated imports and exception chaining on failures. |
Schemascare/emr/resources/user/spec.py |
Added Pydantic models: ResetPasswordCheckRequest, ResetPasswordConfirmRequest, ResetPasswordResponse, ResetPasswordRequestTokenRequest and imported BaseModel. |
New utilscare/emr/utils/reset_password.py |
New JWT-based utilities: generate_password_reset_token(user), verify_password_reset_token(token) (returns (user, None) or (None, error_msg)), and send_password_reset_email(user, mail_type) (renders template, sends email). Uses TTL setting, SECRET_KEY, MailTypeChoices and domain/template settings. |
Removed utilscare/emr/utils/send_password_reset_mail.py |
Deleted legacy send_password_creation_email which relied on django-rest-passwordreset token models. |
Templatescare/templates/email/user_create_password.html |
Button link changed to use reset_password_url while plain-text URL still uses create_password_url โ variables inconsistent. |
Deps & configPipfile, config/settings/base.py |
Removed django-rest-passwordreset dependency and removed it from THIRD_PARTY_APPS. |
Mail typescare/emr/resources/common/mail_type.py |
Added MailTypeChoices enum (create โ "create_password", reset โ "reset_password"). |
Config limitsconfig/settings/custom_limits.py |
Added PASSWORD_RESET_TOKEN_TTL_HOURS (env-backed, default 24). |
Test suitecare/emr/tests/test_reset_password_api.py |
Added comprehensive tests covering request, check, confirm flows, edge cases (expired/invalid/malformed tokens, user mismatch, password validators) and rate-limiting; uses mail.outbox and override_settings; added helper to extract tokens. |
Test settingsconfig/settings/test.py |
Added DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE = True. |
Sequence Diagram(s)
sequenceDiagram
participant User
participant API
participant Utils as ResetPasswordUtils
participant Email
rect rgb(250,250,255)
Note over User,API: Request reset token
User->>API: POST /reset-password-request { username }
API->>Utils: find user by username
alt active user
API->>Utils: send_password_reset_email(user, mail_type)
Utils->>Utils: generate_password_reset_token(user)
Utils->>Email: render & send HTML email (reset link with token)
Email-->>User: receives reset email
API-->>User: 200 OK
else no user or inactive (info-leakage config considered)
API-->>User: 200 OK or 400 based on settings
end
end
rect rgb(245,255,250)
Note over User,API: Check token validity
User->>API: POST /reset-password-check { token }
API->>Utils: verify_password_reset_token(token)
alt token valid
Utils-->>API: (user)
API-->>User: ResetPasswordResponse { detail: "OK" } (200)
else token invalid/expired/malformed
Utils-->>API: (None, error)
API-->>User: ResetPasswordResponse { detail: error } (400/404/429)
end
end
rect rgb(255,250,245)
Note over User,API: Confirm new password
User->>API: POST /reset-password-confirm { token, password }
API->>Utils: verify_password_reset_token(token)
alt token valid
Utils-->>API: (user)
API->>API: validate password (validators)
alt password valid
API->>API: set_password & save user
API-->>User: ResetPasswordResponse { detail: "OK" } (200)
else password invalid
API-->>User: 400 with validation details
end
else token invalid/expired
API-->>User: ResetPasswordResponse { detail: error } (400/404/429)
end
end
Estimated code review effort
๐ฏ 5 (Critical) | โฑ๏ธ ~120 minutes
Possibly related PRs
- ohcnetwork/care#2697 โ overlaps on password-reset email/template plumbing and changes to email-sending flows; likely touches the same templates and utilities.
Suggested reviewers
- vigneshhari
- sainak
Poem
A JWT tiptoes in, the old signals bowed out,
Schemas tidy the forms while tests give a shout.
Emails take flight with a token so neat,
One mismatched URL โ an awkward little feat.
Ship it? Sure โ but please fix that link, without a doubt.
Pre-merge checks and finishing touches
โ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Description Check | โ ๏ธ Warning | The PR description follows the repository template structure but is inaccurate and incomplete: it claims a new PasswordResetToken model and preserved signal handlers while the changeset actually removes django-rest-passwordreset and its signal handler, adds a JWT-based reset_password utility and new Pydantic request/response schemas, deletes the old send_password_reset_mail utility, and includes new tests; additionally the "Associated Issue" section is missing and the Merge Checklist items are left unchecked, preventing reviewers from quickly verifying test/docs/lint status. | Please update the PR description to accurately reflect the code (remove references to a persisted PasswordResetToken model and kept signals; state that django-rest-passwordreset was removed, a JWT-based reset flow and new schemas were added, which utility/template files changed, and that tests were added), add or link the Associated Issue if one exists, and complete the Merge Checklist with test/lint/docs status so reviewers don't have to guess. |
โ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title Check | โ Passed | The title "Custom reset password implementation" is succinct, readable, and aligned with the primary intent of the changeset (introducing an in-project password reset flow), so it correctly summarizes the main change while remaining brief; it is slightly generic about implementation details but still adequate for a quick history scan. |
| Docstring Coverage | โ Passed | Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%. |
โจ Finishing touches
- [ ] ๐ Generate Docstrings
๐งช Generate unit tests
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
Codecov Report
:x: Patch coverage is 42.66667% with 43 lines in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 53.87%. Comparing base (32d9612) to head (e934df2).
:warning: Report is 7 commits behind head on develop.
| Files with missing lines | Patch % | Lines |
|---|---|---|
| care/emr/utils/reset_password.py | 25.45% | 41 Missing :warning: |
| care/emr/api/viewsets/user.py | 50.00% | 2 Missing :warning: |
Additional details and impacted files
@@ Coverage Diff @@
## develop #3059 +/- ##
===========================================
- Coverage 53.91% 53.87% -0.05%
===========================================
Files 417 418 +1
Lines 18747 18804 +57
Branches 2025 2029 +4
===========================================
+ Hits 10108 10131 +23
- Misses 8611 8645 +34
Partials 28 28
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
@sainak Since we use JWT's for everything now, does it make sense to use JWT's for reset passwords as well ?
@sainak Since we use JWT's for everything now, does it make sense to use JWT's for reset passwords as well ?
Revoking the token will require a table anyway, so using JWT won't be that beneficial
Revoking the token will require a table anyway, so using JWT won't be that beneficial
The table is not the problem, the auth mechanism is the problem, using JWT's everywhere for the sake of consistency