care icon indicating copy to clipboard operation
care copied to clipboard

Custom reset password implementation

Open nandkishorr opened this issue 6 months ago โ€ข 5 comments

Proposed Changes

  • Implemented a custom password reset token system using our own PasswordResetToken model
  • Updated related views to work with our custom model
  • Ensured compatibility with existing signal handlers for email notifications

Architecture changes

  • Used custom PasswordResetToken model 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:

  1. Uses our own PasswordResetToken model with proper relationship naming
  2. 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
  3. Maintains compatibility with existing email sending mechanisms through Django signals
  4. 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.

nandkishorr avatar Jun 03 '25 05:06 nandkishorr

๐Ÿ“ 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
Views
care/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.
Signals
care/users/signals.py
Removed password_reset_token_created handler and its email/template imports.
User API
care/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.
Schemas
care/emr/resources/user/spec.py
Added Pydantic models: ResetPasswordCheckRequest, ResetPasswordConfirmRequest, ResetPasswordResponse, ResetPasswordRequestTokenRequest and imported BaseModel.
New utils
care/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 utils
care/emr/utils/send_password_reset_mail.py
Deleted legacy send_password_creation_email which relied on django-rest-passwordreset token models.
Templates
care/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 & config
Pipfile, config/settings/base.py
Removed django-rest-passwordreset dependency and removed it from THIRD_PARTY_APPS.
Mail types
care/emr/resources/common/mail_type.py
Added MailTypeChoices enum (create โ†’ "create_password", reset โ†’ "reset_password").
Config limits
config/settings/custom_limits.py
Added PASSWORD_RESET_TOKEN_TTL_HOURS (env-backed, default 24).
Test suite
care/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 settings
config/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.

โค๏ธ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Jun 03 '25 05:06 coderabbitai[bot]

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.

codecov[bot] avatar Jun 03 '25 05:06 codecov[bot]

@sainak Since we use JWT's for everything now, does it make sense to use JWT's for reset passwords as well ?

vigneshhari avatar Jun 03 '25 07:06 vigneshhari

@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

sainak avatar Jun 03 '25 08:06 sainak

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

vigneshhari avatar Jun 04 '25 05:06 vigneshhari