GSoC 2018 - Reset Password via Email Project

@harisu,

OpenMRS uses a REST API. By design, this means your are interacting with resources via HTTP calls. It can be a little tricky to switch your thinking from the method-based approach of languages like Java to a resource-based API. In general, everything in the API is a “thing.” For resources like users, patients, encounters, locations, etc. it’s fairly obvious that each thing is a resource. When you want to take an action (like resetting a password), it’s tempting to add a method name (i.e., action) like /password/forgot; however, that’s not the RESTful approach. In REST, we turn these actions (verbs) into resources (nouns) using reification. In this use case, we could make a resource like a PasswordResetRequest (e.g., /ws/rest/v1/passwordresetrequest), where you would POST a password request request with appropriate details.

We would probably want to support a request by username or email address.

For security, it is best practice not to “leak” information about users in this manner. For example, with your proposed approach, an anonymous attacker could send password reset requests with random usernames and, based on the response, infer which usernames existed in the system. Basically, any response to a non-authenticated request should be the same regardless of whether or not the user exists.

So, for this use case, the response to an anonymous POST to /passwordresetrequest would always be a HTTP 200 OK with a “request received” or “Check your email for further instructions” response, regardless of whether or not the username or email exists in the system. If the POST is authenticated (i.e., an admin… see comment below), then you could return a status (success or failure).

The link will probably need to be configurable and can start out with our default password reset page (and obviously would need to include the reset token as you suggest). It’s worth noting that the code responding to the link (the web page request) is not going to be performing the password reset itself; rather, it is going to be invoking the REST API to perform the password reset. While we will want to create a password reset page for the Reference Application, different distributions will likely have their own password reset page and will depend on having the REST API to perform the actual reset (e.g., perform a GET to /passwordresetrequest/{token} and, if that succeeds, then POST a new password).

We should not be performing the password reset directly via web pages / servlets as in a simple web application; rather, the password reset process should be exposed via the REST API and the web page(s) will assist the user in using the REST API to reset their password.

So, the user’s browser makes a request to a URL like /resetpassword?token={token}, a servlet returns a web page (e.g., React app) that fetches the token from the page request and uses it to perform a GET /passwordresetrequest/{token}. If not successful, it tells the user their request is invalid and provides suggestions for what to do. If the GET is successful, then it prompts the user for their new password twice and, if both entries match, would POST the new password to /resetpasswordrequest/{token} and, based on the response, tell the user whether or not the password reset request was successful.

Yes. An admin should be able to trigger a password reset for a user. In this case, the username will already be known and used in the REST API call. If a user doesn’t already have an email address defined in the database, then the request would fail.

One of the goals of implementing this feature is to eliminate the current feature of setting a temporary password. In the best design, nobody except the user should ever know their password. Even an admin should not be able to set the user’s password (and thereby know it… even temporarily).

Good thoughts. Some additional thoughts…

  • We want one request per user. If two reset requests are made back-to-back, only the latest one is valid.
  • If we use user_properties, do we clean up after ourselves (purge an unused reset token)? My initial impression is I wouldn’t want to leave a trail when password reset tokens are unused.