GSoC 2018 - Reset Password via Email Project


(harisu fanyui) #1

Post for Project Discurssion Design ,communication and difficulties.

Greetings Everyone.

Short Description

Adding a self service password reset support through email Designed to fully function and be managed through RESTful API.

Project Page

Reset Password via Email Project

Mentor

@wyclif (backup: @burke)


Password Reset Via email Project -gsoc midterm presentation
GSoC 2018 - Password Reset Via Email Project - Final Presentation
(harisu fanyui) #2

The Work Flow of how i plan to proceed with the reset process after setting up Required database tables are as follows.

1. User Submit a Post request from a url /password/forgot with the email address in the body of the request.

I Extract the email address from the request,.

Do a check against the database for the presence of that email,

If it is present I then Go further to get the user by email,

if the email does not exist i send a custom Mail message telling the user that the address was not found and that the user should contact the administrator. with the email having information on how to contact Administrator or ask for help.

For the case where user with given email exist I then Generate a password reset_token object ( set the user, token(hashed) and expiry date) and save it to the database.

I Then Create a Mail message send it to the user with provided email and as a link /password/reset?token={$token_generated_hashed) to be used for reseting. The content of the mail which will have the expiry time which i suggest 30 minutes from time of innitialization of the reset email process.

2. Now when a get request is made on endpoint /password/forget?token={$token_generated_hashed}

I extract the token do a check if the token exist in the reset_token table and the date expired has not yet reached.

If it does exist then i return the reset_token Object as the response Else if it expired or doesnot exist I delete the record with the reset_token and i response with the errors

3. I then listens for a post request on /password/reset

I get the token which would be a hidden field, user_id, new Password from the request check again if the token is valid then i call the reset method on the user and reset the password for that user . I Delete the reset_token.

Question

Should the admin be able to trigger password reset via email for a user?. My own point of view. I think since the admin already has means of resetting a password(setting a temporal password) for a another user. Reseting via email should only be left to the user. Hence the botleneck on the admin is reduced. the admin should not trigger reseting for another user via email. Either way it is still possible everyone can do it if they have your email.


(Wyclif Luyima) #3

Hi @harisu,

I’m a little confused by a few things in the workflow, could be the way you’ve phrased some areas, in step one I don’t think you should be be including the email address instead the user should provide it on the initial form you show them when they visit the forgot password page.

I think we don’t need a new table just to store the token and timestamp, we could use the user_properties table to save them, have you considered looking into JWT?


(harisu fanyui) #4

@wyclif I only quoted from when the form was submitted skipped the form filling part. form is submitted with email as body of the request to the url /password/forgot.

Looking into JWT


(Burke Mamlin) #5

@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.

(harisu fanyui) #6

Thanks for the clarification.

In point 1, I meant creating two email templates one that is sent out when the email provided for reseting password is found in the database( this email will contain instructions on how to proceed with the reset process with link) and another mail which is sent out when the email provided was not found but the response to the request is always HTTP 200 OK.


(harisu fanyui) #7

Hello @wyclif @burke I added a not null constraint to the email field in core and some tests failure resulted i wish to know if i should fix the tests by providing email address or i go ahead to remove the not null constraint. Example situations are When creating a default admin user on fresh install and even a deamon user also some
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column “EMAIL”; SQL statement: insert into USERS (USER_ID, PERSON_ID, SYSTEM_ID, USERNAME, PASSWORD, SALT, SECRET_QUESTION, CREATOR, DATE_CREATED, CHANGED_BY, DATE_CHANGED, RETIRED, RETIRE_REASON, UUID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)


(harisu fanyui) #8

have you considered looking into JWT1?` JWT

Hello @burke

Please what is your own point on json web token do we go along it or we continue with our traditional method by storing and managing the token by ourselves. I have looked into some of the libraries on it here JJWT. and here.


(Burke Mamlin) #9

@harisu, do you have a diagram of the proposed workflow and a list of functional requirements? It would be great to add these to your project’s wiki page or in a child page. I fear we may be each working with different requirements or workflow in mind.

I have no problem with your using JWT, but it’s hard for me to give an informed opinion without knowing the requirements.

In general, I think of JWT as a tool for encrypting information into a token. This works great for things like auth tokens, where the username or other info can safely be stored in a cookie.

For this project, I’m assuming you are considering JWT to persist (in encrypted form) everything needed to reset a password. That could work, but are these part of your requirements?

  • A reset token should be between 20 to 80 characters in length.
  • Only one reset token can be valid for a user at any tine.
  • Like a password, nobody, including an admin, should have access to the secret needed to reset a password that is sent in a reset email (it should not be logged, should not be guessable, and can be validated by the server just like a salted password).
  • Administrator should be able to invalidate all outstanding password reset tokens.

I’m not saying that those have to be in your requirements, but they seem like reasonable requirements and it’s not clear to me if they could be met without storing something (i.e., a salted & hashed token) on the server.


(Wyclif Luyima) #10

Why can’t it be longer? Along these lines, I think we need to know the maximum length though to reliably be able to store it in a database column


(Burke Mamlin) #11

Those are arbitrary constraints. I figured you’d want something long enough to not to be guessable and not so long that it would cause broken links in email using plain text.

My point was that having a list of requirements would help answer @harisu’s question. So, I came up with some random example requirements off the top of my head to demonstrate how, if they were requirements, might influence the choice of using JWT.


(Burke Mamlin) #12

FWIW, glancing out password reset in Wordpress, they:

  • generate a random key 20 characters long
  • hash it eight times
  • store that hash in the database as a “user_activation_key”
  • email that key inside a link to the user

Just for the fun of it, comparing WordPress’ approach to my random list of requirements:

  • :heavy_check_mark: The key is 20 characters
  • :heavy_check_mark: Only one reset token is valid at a time,
  • :heavy_check_mark: Only the strongly hashed key is stored in the database so nobody could guess it without the key
  • :heavy_check_mark: It’s possible to disable all outstanding activation keys by clearing them from the database.

(harisu fanyui) #13

Hello @wyclif burke from the sample usecase diagram i presented along this comment,

  1. Aside from the fact that it will generate a token with long characters sometimes more than 100 which is contrary to the between 20 and 80 char which you suggested.

  2. I Hash the UUID of the user into the JWT using a suitable suported hashing algorithm then make it a one time usable link by added time stamp also i will set the expiration time on the token payload.

  3. Hence by so doing I only decode the token sent to the use when the link with the token is clickec, Then i verify if the expiration time for the token has reached if not i query the user with the decrypted uuid if it authentic i can now proceed to the next stage allowing access for a post of new password.

  4. Password reset tokens are never stored, Spares the overhead of haven to clean up expired tokens or already used tokens from the db I await your feed back and modifications should incase there are loop hole in my approach Thanks .


(Burke Mamlin) #14

Correct me if I’m wrong, but it seems the JWT approach has a few disadvantages:

  1. Token could be 10x longer than needed (over 200 chars vs. 20-50).

    Users would get a link in an email like this:

     https://demo.openmrs.org/openrms/resetPassword?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIU
     zI1NiJ9.eyJ1dWlkIjoiOGU3ZDNhNjgtODI1MS00M2IzLTg5NTYtZGRiNTgxYWZiZDA1IiwiZXhwIj
     oxNTI4MDc5OTA5LCJqdGkiOiJjYTEwMTIxYi1iYmQwLTRmN2UtYTZhZi1iZjI2NTY3YzRhMDEiL
     CJpYXQiOjE1MjgwNzYzMDl9.zkkcZ2pP7xj9XUBjABDr0S0S4UXTFcRj5PhBgE7noDQ
    

    Instead of this:

     https://demo.openmrs.org/openrms/resetPassword?token=eyJ0eXAiOiJKV1QiLCJh
    
  2. I’m assuming all password resets would use the same secret (either secret phrase or key). If someone gets a hold of that secret, they could take over any account (including administrator).

  3. Tokens are re-usable. Given they will expire in 10-15 minutes (though, assuming you make this configurable, an admin might increase the lifespan), this may not be a big deal. But I’ve never seen a password reset feature that allowed a reset link to be used more than once.

#1 is more of an annoyance than a show-stopper. #2 is particular concerning as a security risk. #3 isn’t a deal breaker, but could represent a security issue.

Alternatively, if you followed the example of WordPress, you could add users.activation_key to the users table (you’re already there, adding users.email) and mimic WordPress’ approach. Tokens could be 20-30 characters long, each token generated would have a unique secret only known long enough to create the hash for the activation key and send the email, and you could reduce the concern about the need to clean up expired tokens (an unused key would be harmless).


(harisu fanyui) #15

I see reasons with you. I will be adopting the 20 to maximum 80 character token aproach. Thanks for scrutinizing my proposal and backing up.


(Wyclif Luyima) #16

I would think the token details should be stored in the user_property table.


(Burke Mamlin) #17

If we were constrained to the existing data model, I’d agree; however, we are changing the model (adding email), so can add this attribute as well. To me, an activation key is closest to password in terms of model (it’s effectively a “pre-password”), so I guess I picture it behaving like password – i.e., a database attribute on users that isn’t included in the User object. In the end, either way will work.

If you imagine storing an activation token with a ttl, it could be used not only for password reset but also (with a longer ttl) for initial account activation (as WordPress is doing).


(Wyclif Luyima) #18

Fair! Well, there is 3 fields we need to store i.e. the token, secret and timestamp, seems like we have to add these 3 new columns to the user table.


(harisu fanyui) #19

@wyclif The wordpress method concatenate the timestamp with the hashed key to make the activation key, which is then stored and is split into the various parts when requested. @burke Are we going to emulate the same approache?


(Burke Mamlin) #20

Having the ttl will be important. The WordPress approach tightly binds the ttl with the hashed token and only requires one attribute in exchange for using a proprietary data format for the attribute. I can see advantages/disadvantages on both sides. Given any access of the attributes value should be constrained to your code (other parts of the API would not be expected to interact with this attribute), I can see why WordPress chose their approach. But, I think this is a design decision you can make with @wyclif’s help.