Skip to content

Add user password change and administrator-initiated password reset - #2187

Open
tsande16 wants to merge 20 commits into
TexasDigitalLibrary:mainfrom
jhu-sheridan-libraries:VIR-73-user-password-reset-upstream
Open

tsande16 wants to merge 20 commits into
TexasDigitalLibrary:mainfrom
jhu-sheridan-libraries:VIR-73-user-password-reset-upstream

Conversation

@tsande16

@tsande16 tsande16 commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Description

Resolves:

Changes:

[ ] Major
[X] Minor
[ ] Patch

Vireo currently has no way for a locally registered user to change their password, and no way for an administrator to help a user who has forgotten one. This PR adds both, behind settings that default to off, so existing installations see no behavior change until an administrator opts in.

User-facing

  • My Profile: Change Password. A signed-in user with a Vireo-managed password (a stored password and no NetID) can change it by supplying the current password and a new one. Shown only when the capability is enabled and the account is eligible.
  • Users admin page: Reset Password. A manager or admin can send a single-use reset link to an eligible user. A confirmation modal guards the send. The link opens a public /reset-password page where the user chooses a new password and is then sent to sign in.
  • Settings > Application > Password Management. Four new managed settings: auth.pass.enabled (master, default true), auth.pass.change.enabled (default false), auth.pass.reset.enabled (default false), and auth.pass.reset.expiryHours (default 24). The expiry field is validated to a positive integer on both client and server.
  • Email. Two new system email templates, SYSTEM Password Reset Request and SYSTEM Password Changed, written in the same house style as the existing templates. A password-changed notification is sent after any successful change or reset.

Approach

  • PasswordController exposes POST /password/change (ROLE_STUDENT, acts only on the authenticated user), POST /password/reset (ROLE_MANAGER), and POST /password/reset/redeem (anonymous, like /auth/register). Every handler checks availability, then eligibility, then input, and writes nothing if any check fails. No response ever carries a password, hash, or token.
  • Reset tokens are self-contained and signed, so nothing new is persisted. ResetTokenService builds base64url(email|binding|issuedAt).base64url(HMAC-SHA256) using the existing app.security.secret, where binding is an HMAC of the stored password hash. Changing the password re-salts the hash, which invalidates that token and every sibling token for the account, giving single use without a token table. Expiry is read from settings at verification time.
  • PasswordValidator centralizes the new-password rules and is also used by the existing registration path in AuthController; every existing registration outcome is preserved and covered by tests.
  • PasswordEligibilityRule is the single definition of "Vireo-managed account" used by the controller, the User model (@Transient isPasswordEligible), and the /user/credentials response the client reads.
  • Send failures are logged at WARN and surfaced to the administrator as an error response; a failed notification after a change never rolls back the change.

Compatibility

  • No schema changes. Settings seed through SYSTEM_Defaults.json; templates load through SystemDataLoader.
  • Shibboleth and other NetID-backed accounts are never eligible and see none of the new UI.
  • With the defaults, nothing is visible to users until an administrator enables change and/or reset in Password Management.

Testing

  • New JUnit coverage for the controller, token service, availability service, notification service, eligibility rule, and validator, plus additions to the existing auth, user, managed-configuration, and data-loader tests.
  • Manually smoke tested end to end on a staging deployment: change password, admin-initiated reset, expired and reused links, disabled-feature paths, and the settings validation.

Type of Change

  • Bug fix
  • New feature
  • Breaking change

AI Usage Disclosure

AI Tool(s) Used Including Version:

  • Kiro 1.4.86 (Claude Sonnet 5.5, Opus 5.5, Auto mode): Used for spec-driven development, code generation, tests, and documentation
  • Claude Code 2.2553.0 (Claude Fable 5.1): Used for testing and review

Nature of Use:

  • Code generation
  • Code suggestions/refactoring
  • Documentation
  • Tests
  • Other (please describe)

Additional Description:
The pattern we use for feature development is using spec-driven development where a set of requirements, design, and tasks MD files are generated. These ground the agent in these steering files and are use throughout the development.

- Add auth.pass.change.enabled, auth.pass.reset.enabled and auth.pass.reset.expiryHours to ConfigurationName, reusing the existing auth.pass.enabled constant as-is.

-Seed all four settings under the application key in SYSTEM_Defaults.json.

- Add PasswordEligibilityRule, a static predicate over stored password and netid only.

- Add PasswordValidator

- Add CredentialNotificationService.notifyPasswordChanged

- Add the packaged SYSTEM_Password_Changed.email template
Add reset token service, reset initiation and redemption endpoints, reset request email template, and reset-password client surfaces.
@tsande16 tsande16 self-assigned this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant