Skip to content

Conversation

@Andrew-Ryer
Copy link

@Andrew-Ryer Andrew-Ryer commented Nov 28, 2025

Description

This PR implements the full User Password Change feature in OED, enabling any authenticated user to securely change their own password via the Options → Change Password menu, without admin intervention. The work spans backend API, frontend RTK Query integration, a new modal UI component, and integration into the main application with translations.

On the backend, it adds a new POST /api/users/changePassword endpoint protected by authentication middleware that verifies the user’s JWT, validates input (including length constraints), confirms the current password with bcrypt.compare, securely hashes the new password with bcrypt.hash, and updates the database via User.updateUserPassword. On the frontend, it introduces a changePassword RTK Query mutation and a ChangePasswordModal React component that displays the current username, collects current/new/confirm password, performs real-time validation, and shows success/error notifications. The feature is fully integrated into HeaderButtonsComponent via a “Change Password” entry in the Options dropdown and wired into the translations system so all UI text is available in English, French, and Spanish.

Fixes #1526 - Allow each user to change password

Type of change

(Check the ones that apply by placing an "x" instead of the space in the [ ] so it becomes [x])

  • Note merging this changes the database configuration.
  • This change requires a documentation update

Checklist

(Note what you have done by placing an "x" instead of the space in the [ ] so it becomes [x]. It is hoped you do all of them.)

  • I have followed the OED pull request ideas
  • I have removed text in ( ) from the issue request
  • You acknowledge that every person contributing to this work has signed the OED Contributing License Agreement and each author is listed in the Description section.

Limitations

  • Password policy is limited to length-based validation (8–128 characters); there is no password strength meter or enforcement of complexity (e.g., symbols, digits, upper/lowercase mix).
  • Password history / reuse prevention is not implemented; users can technically reuse previous passwords as long as they meet length requirements.
  • No rate-limiting or lockout mechanism is added specifically for the password change endpoint; it relies on existing global protections.
  • Automated tests (unit/integration/e2e) for the new endpoint, RTK Query mutation, and modal component are not included as part of this PR; verification has been described and performed manually and could be formalized in follow-up test work.
  • Documentation and user-facing help text about the new “Change Password” option in the OED UI are not included and can be added in a separate documentation update if desired.

Contributors:
Andrew Ryer — @Andrew-Ryer[email protected]
Nathan Towsley — @natetowsley[email protected]
Tyler Herndon — @GoKartMan89[email protected]
Caleb Stark — @caleb-stark[email protected]

natetowsley and others added 28 commits November 10, 2025 13:05
…ranslations for "change.password" to data.ts
…o allow for the showing of more than one kind of modal. Adjusted handleClose() and handleShow() accordingly. Added a modalType to login modal and created a copy with modalType 'changePassword' as placeholder.
…alType to allow for the showing of more than one kind of modal. Adjusted handleClose() and handleShow() accordingly. Added a modalType to login modal and created a copy with modalType 'changePassword' as placeholder."

This reverts commit c20bec1.
….ts, correct modal now shows when clicking change password.
Co-authored-by: GoKartMan89 <[email protected]>
Co-authored-by: caleb-stark <[email protected]>
Co-authored-by: Andrew-Ryer <[email protected]>
Copy link
Member

@huss huss left a comment

Choose a reason for hiding this comment

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

Thanks to @Andrew-Ryer, @natetowsley, @GoKartMan89 & @caleb-stark for this contribution to OED. I'm sorry it took about a week to get back to you. Overall, it works well. I have made some comments to consider. Please let me know if anything is not clear or I can help.

"calibration.save.database": "Save changes to database",
"calibration.submit.button": "Submit",
"cancel": "Cancel",
"change.password": "Change password",
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if these should start with password as do a number of related ones such as password.new.enter.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for changing change.password. I'm thinking that current.password and current.password.enter might also be best if they start with password so in that section and consistent.

}
};
if (!validate(req.body, validParams).valid) {
res.status(400).json({ message: 'Invalid params' });
Copy link
Member

Choose a reason for hiding this comment

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

I realize this is not done elsewhere in this file but the newer (and I think better) way is as in src/server/routes/conversions.js where it has:

	const validatorResult = validate(req.body, validConversion);
	if (!validatorResult.valid) {
		log.warn(`Got request to edit conversions with invalid conversion data, errors: ${validatorResult.errors}`);
		failure(res, 400, `Got request to edit conversions with invalid conversion data, errors: ${validatorResult.errors}`);
	} else {

This uses the standard way of returning the message and logs it which is a good idea for failed login attempts. What do you think?

Choose a reason for hiding this comment

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

Everything worked with the newer method except no sub-message would show on client side underneath "Failed to change password" on the error notification when using failure(). I also tried to edit all routes to use this new method for consistency (still have the copy if that's worth looking into) but ultimately couldn't get it to work so replaced failure() line with res.status(400).

Copy link
Member

Choose a reason for hiding this comment

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

I was curious why it did not work so I tried to do it. I should soon push a commit to make the change. It also address some other items involving security that were outside the work you were asked to do. I welcome any thoughts you have about my changes.

return (
<>
{/* Unsaved Warning Component */}
{showUnsavedWarning && (
Copy link
Member

Choose a reason for hiding this comment

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

I'm not seeing the warning even if I made changes. I have not looked into this.

Copy link
Member

Choose a reason for hiding this comment

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

I'm still seeing this issue. For example, on the edit user page for admins if you type any characters in either password field and then click off the modal it asks what to do in a popup. On this page it does not give the popup. Let me know if you don't see this or need any help.

@natetowsley
Copy link

@huss Changes addressed. Please let us know if there's anything else we should do.

- The messages are also displayed to the user.
- Changes information returned to mask info for security
reasons. Also logs more info for security.
- Minor changes in message wording for translation.
- Minor formatting changes.
Copy link
Member

@huss huss left a comment

Choose a reason for hiding this comment

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

Thanks to @Andrew-Ryer, @natetowsley, @GoKartMan89 & @caleb-stark for the updated code to address my previous comments. There are a couple of old comments that I thought should still be addressed and I added to the comment chain. I also pushed a commit to use success/failure since you indicated issues in getting it to work. It also includes other changes to address ongoing security work that was not part of what you were doing. I would welcome you looking at the code to see if you think it is correct/appropriate and verifying it works as desired. Please let me know if anything is not clear or I can help.

return (
<>
{/* Unsaved Warning Component */}
{showUnsavedWarning && (
Copy link
Member

Choose a reason for hiding this comment

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

I'm still seeing this issue. For example, on the edit user page for admins if you type any characters in either password field and then click off the modal it asks what to do in a popup. On this page it does not give the popup. Let me know if you don't see this or need any help.

"calibration.save.database": "Save changes to database",
"calibration.submit.button": "Submit",
"cancel": "Cancel",
"change.password": "Change password",
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for changing change.password. I'm thinking that current.password and current.password.enter might also be best if they start with password so in that section and consistent.

}
};
if (!validate(req.body, validParams).valid) {
res.status(400).json({ message: 'Invalid params' });
Copy link
Member

Choose a reason for hiding this comment

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

I was curious why it did not work so I tried to do it. I should soon push a commit to make the change. It also address some other items involving security that were outside the work you were asked to do. I welcome any thoughts you have about my changes.

@huss
Copy link
Member

huss commented Dec 12, 2025

For unknown reasons my recent comments are showing up twice. I'm just going to leave them to avoid any more issues. Sorry.

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.

Allow each user to change password

5 participants