Skip to content

Edward/password expiration - OP-3113 - #434

Open
acquaronedward wants to merge 5 commits into
openimis:developfrom
nlgfc2024:edward/password-expiration
Open

Edward/password expiration - OP-3113#434
acquaronedward wants to merge 5 commits into
openimis:developfrom
nlgfc2024:edward/password-expiration

Conversation

@acquaronedward

@acquaronedward acquaronedward commented Jul 18, 2026

Copy link
Copy Markdown

Implement expired-password backend flow

Summary

Implemented backend support for expired-password handling during login and password reset.

When a user logs in with correct credentials but their password has expired, the backend now returns an explicit password-expired response instead of treating it as normal incorrect credentials. No JWT token is issued for expired-password logins.

Backend changes

  • Added expired-password support to the authentication flow.
  • Updated user_authentication to support allow_expired=True.
  • Login now checks InteractiveUser.password_validity / is_password_expired.
  • If credentials are valid but the password is expired:
    • normal login is rejected,
    • no JWT token is issued,
    • backend returns passwordExpired=true,
    • backend attempts to send/request a reset email.
  • Added password reset response fields to the login mutation:
    • password_expired
    • reset_email_sent
    • username
  • Updated reset-password mutation to avoid leaking whether the user exists.
  • Updated set-password mutation to return structured success/error responses.
  • Added/updated backend tests for expired-password authentication and reset flow.

Important behavior

Expired-password handling only happens after the username and password are valid.

If the user enters the wrong password, they still see the normal incorrect credentials message. This avoids exposing account state for invalid login attempts.

Testing / verification

Verified locally that:

  • an expired password returns the expired-password state,
  • incorrect credentials still return the normal incorrect-credentials flow,
  • reset-password request does not disclose whether an account exists,
  • set-password mutation returns structured success/error responses.

@gitguardian

gitguardian Bot commented Jul 18, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
- - Generic Password 1dd11d0 core/tests/test_services.py View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Implements backend support for password-expiration handling in authentication, including explicit expired-password responses, password reset hardening, and scheduled password-expiry reminder emails.

Changes:

  • Extend authentication/login GraphQL mutation to surface password-expired and password-expiry warning fields, and trigger reset email flow for expired-password logins.
  • Update password reset and set-password flows (rate limiting hook, reduced account-existence leakage, structured set-password responses).
  • Add password-expiry reminder emails (templates + scheduler integration + reminder send/logging support) and expand test coverage.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
README.md Updates scheduler guidance for production deployments.
core/tests/test_services.py Adds tests for password validity updates, expiry reminders, and expired-password authentication behavior.
core/tests/test_graphql.py Adds GraphQL tests for expired-password login reset email and expiry-warning fields.
core/templates/password_reset.txt Refines reset-password plaintext email content.
core/templates/password_reset.html Adds HTML reset-password email template.
core/templates/password_expiry_reminder.txt Adds plaintext password-expiry reminder template.
core/templates/password_expiry_reminder.html Adds HTML password-expiry reminder template.
core/services/userServices.py Adds allow-expired authentication path, password reset rate-limit helper, reset email changes, and expiry reminder selection/sending.
core/services/init.py Exposes new reminder/rate-limit functions from the services package.
core/schema.py Updates reset/set-password mutations and extends tokenAuth with expiry/expired-password response fields.
core/scheduler.py Hooks core scheduled tasks into the scheduler setup.
core/scheduled_tasks.py Registers the password-expiry reminder cron job.
core/models/user.py Adds password reuse checks, password validity tracking, and reminder log model.
core/models/init.py Exports PasswordExpiryReminderLog.
core/migrations/0036_passwordexpiryreminderlog.py Creates DB table for password expiry reminder logs.
core/apps.py Adds/loads new password-expiry configuration defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 476 to 477
self.assertTrue(len(mail.outbox) == 1)
self.assertTrue(mail.outbox[0].subject == "[OpenIMIS] Reset Password")
Comment on lines +351 to +375
def is_password_reset_rate_limited(request, username):
window = settings.PASSWORD_RESET_RATE_LIMIT_WINDOW
ip_address = (
getattr(request, "axes_ip_address", None)
or request.META.get("REMOTE_ADDR", "unknown")
)

normalized_username = (username or "").strip().lower()
account_hash = hashlib.sha256(
normalized_username.encode("utf-8")
).hexdigest()

ip_count = _increment_reset_counter(
f"password-reset:ip:{ip_address}",
window,
)
account_count = _increment_reset_counter(
f"password-reset:account:{account_hash}",
window,
)

return (
ip_count > settings.PASSWORD_RESET_RATE_LIMIT_PER_IP
or account_count > settings.PASSWORD_RESET_RATE_LIMIT_PER_ACCOUNT
)
Comment thread core/schema.py
Comment on lines +2113 to +2114
except ValidationError as validation_error:
logger.exception(validation_error)
@zikani03 zikani03 changed the title Edward/password expiration Edward/password expiration - OP-3113 Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants