GSoC 2018 - Reset Password via Email Project


(harisu fanyui) #21

Hi, @burke , @wyclif .

I Hope you are all doing fine. Below are the proposed endpoint i will be implimenting in the reset webservice with regards to the work done in core. I will be adding endpoint to the resource paswordreset /ws/rest/v1/passwordreset.

  1. A get request to this endpoint with a path variable /ws/rest/v1/passwordreset/{usernameOrEmail} will trigger the password reset for the said user with email or username. and

  2. A post to this endpoint will contain {“activationKey”:“xciYc36UIb12tdh”, “newPassword”:“Admin123”}. if succeeded, will reset the password for the said user. All actions on these endpoint get as response HttpStatus.OK whether successful or not.

Also I will like to say instead of sending a link in the email the token should be sent so as to permit endusers manipulate the api in their own way. as sending a link will tie the clients to a particular page for web apps and for standalone apps they wont be able to request for activationkey as core implimentation doesnot impliment the possiblity for get back and activation key.

My Initial line of thought was github pull request.

I await your feedback.


(Wyclif Luyima) #22

I talked to @Burke and he suggested that we can still use a link in the email since it’s an edge case that somebody will be writing a none browser based client application that doesn’t work with URLs, we will define a global property that allows the admin to specify the URL to be embedded in the email, the URL will have a place holder for the activation key that your code replaces with the actual key before sending out the email, this approach should still work for mobile clients too since they have a protocol that they use to open up an app that supports URLs.

Sometime in the future, in the event that there is clients that don’t work with URLs, we can improve the feature to let the admin configure a template and possibly a locale for the message to embed in the email that way we can use the place holder approach to insert the activation key in the template.

Please let me know if there something that’s still not clear.

Wyclif


(harisu fanyui) #23

Ok. In that case I think i need another endpoint /ws/rest/v1/passwordreset?activationkey=chd837Kdyd8. to accept get request with the activation key in it. which will only verify the token provided. And that means making a post to /ws/rest/v1/passwordreset. with {“activationKey”:“xciYc36UIb12tdh”, “newPassword”:“Admin123”} as i am suggesting. might be a handicap as there will be no way to get the activation key before posting since this resource will only respond with and ok. Unless the email sent will explicitely ask the user to copy the activation key which he or she will use in the reseting process. Or I make a response to the get request be the activation key if it is verified so that it can be implicitely used by any client when posting the new password.

@wyclif @burke.


(Wyclif Luyima) #24

Nothing else changes


(Burke Mamlin) #25

I’m assuming that the client will need to encode the email, since @ is in every email address and is a reserved character per RFC1738 (i.e., the @ character is not valid within a URL and therefor must be encoded to %40).

It might be preferable both (1) to make it a little harder to trigger a password reset by accident and (2) avoid having to put usernames or, more importantly, emails into URLs to make this a POST. That is:

POST /ws/reset/v1/passwordreset
{  username: "foobar'"}

or

POST /ws/reset/v1/passwordreset
{ email: "foobar@example.com" }

that always responds with 200 OK.

Returning only 200 OK for the GET where username or email is provided is appropriate (to avoid leaking usernames); however, I would expect the POST to return either 200 OK for success or 400 Bad Request for failure (since the activation key and username/email must match, returning 400 does not indicate whether the username/email or the activation key is the problem, so doesn’t leak usernames).

Remember, the link provided to the user will not go to the REST API; rather, it will be set by the application using the API and will send the user to the appropriate location within that application (e.g., a password reset page within the reference application). A sequence diagram might be helpful here.

So, the user gets an email with a link like https://example.com/openmrs/lostpassword?key= xciYc36UIb12tdh. That reference application web page would then interact with the REST API to (1) validate the activation key and then (2) reset the password.

And, again, I’d favor not exposing usernames, emails, passwords, or activation keys in URLs if they can be avoided (you obviously cannot avoid putting the key in the link emailed to the user).

How about this?

POST /ws/rest/v1/passwordreset
{ username: "foobar" }

Response: 200 OK

User receives email with link https://example.com/lostpassword?key=xciYc36UIb12tdh and clicks it to open up the web application’s lost password page, which uses the API to validate the activate key…

POST /ws/rest/v1/passwordreset
{ activationKey: "xciYc36UIb12tdh" }

Response:
  200 OK (if valid) or
  400 Bad Request (if invalid, with explanation in response body)

If the key is invalid, the user sees a page stating they have an invalid key and, perhaps, guiding them to generating a new password reset request. If the key is valid, then the user is presented with password and confirm password fields. When the form is submitted and passwords match, the web app calls the API…

POST /ws/rest/v1/passwordreset
{
  activationKey: "xciYc36UIb12tdh",
  newPassword: "..."
}

Response:
  200 OK (if password reset successful) or 
  400 Bad Request (with error message in response body explaining
      activation key is invalid or password does not meet criteria)

Note that a successful password request should not authenticate the user to the API. If a password is successfully reset, then the client (e.g., web application) would be expected to inform the user of success and redirect them to login).


(Burke Mamlin) #26

Here’s a sequence diagram of what I was trying to explain (avoids usernames or emails in URL, allows for informing user of errors):

image


(harisu fanyui) #27

@burke Thanks very much for the sequence diagram. Its’ all clear on how things happen. I wish to know all the posts are taking to the same resource won’t there be ambiguity in handling the requests


(Burke Mamlin) #28

This is a fair criticism. One reaction might be to make separate endpoints for each action, but that’s not the RESTful approach. Rather than thinking of verbs (e.g., “reset my password” method), we should think of REST endpoints as nouns.

So, let’s consider /passwordreset to represent a password reset request (a thing… a noun). By treating the activation key as a way of identifying the thing, we might get a better design:

We create a password reset request by posting username or email:

POST /passwordreset
{ username: "foobar"} or { email: "foobar@example.com" }

Response: 200 OK (always)

Instead of returning the “identifier” (aka activation key), it is emailed to the user. When the user uses the link to return to the web application, we can validate the password reset request is valid with a GET:

GET /passwordreset/xciYc36UIb12tdh

Response:
  200 OK (if valid)
  400 Bad Request (if invalid, with error message in body stating
      either "invalid token" or "expired token")

If we have a valid password reset request, then we can use it to submit new credentials:

POST /passwordreset/xciYc36UIb12tdh
{ newPassword: "..." }

So our updated sequence diagram becomes:

image

Thanks! That feels cleaner to me. What do you think?

The assumption I’m making is that activation tokens will only contain alphanumerics ([0-9a-zA-Z]) so they won’t need to be encoded in the URL.


(harisu fanyui) #29

The update is very much clearer now. Yes your assumption is correct i currently generate a random alphanumeric string of length 20 as activation key. hash of it is stored while the random one is sent to the user via email.

Really greatful for the clarifications.