-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forgot Password #103
Forgot Password #103
Conversation
- user can request a temporary password be sent to their email - temporary password can be used to sign in normally and change the user's password - user must have their seed stored in the browser for the call to forgotPassword() to work
- initially returned success even if incorrect so that someone calling forgotPassword with the incorrect seed would not know the difference between a successful or failed call. Thus they would not know if their seed is incorrect or not - However, if client can decrypt the encryptedForgotPasswordToken in the first place, then they would already know they have the correct seed. Server's success response would not prove anything beyond what the person would already know
clever workaround! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥳 Looks perfect.
This is intriguing 🤔 Although, I’m trying to follow how a user regains access to their encrypted data with this method. The server generates a random token, encrypts it with an otherwise non-vital key that the client would also know/be able to generate, and proves to the server it has the correct master key. The server then emails the user a random temporary password:
If the user then signs in with this password on another device (not the one they initiated the forgot request with), how will they decrypt all the incoming data that was not encrypted with this reset token? Or does this method only allow one to regain access to an account, but not regain access to data? (Admittedly I don’t know the full role of shared secrets with Userbase as I only studied the change log for this particular PR, so perhaps I’ve overlooked important context) |
This method allows one to regain full access to the account (including access to all data) so long as the user signs in from a device they have used before with
The user cannot sign in with the temporary password on another device. The user must sign in with the temporary password from a device they have used before with First, upon sign up, the userbase-js client generates a random encryption key (technically this is referred to as the user's "seed" in the code). This random encryption key is what's used to encrypt user's data. Therefore if the user has the encryption key, they can decrypt their data. The Also on sign up, the password is used to derive a key that encrypts the user's randomly generated encryption key from above. The resulting cipher text (the encryption key from above encrypted with the user's password) also gets stored on the server on sign up. When the user provides their password on sign in, the server sends the password-encrypted encryption key to the client, and the client decrypts it using the user's password. Note the server never sees a user's password, the password is hashed client-side before being sent to the server. This way a user can sign in to their account from anywhere without needing the key in local storage. So again, with this approach, if the user loses their password, but still has their encryption key in local storage, they can regain access to their data. When the user changes their password, the client will re-encrypt the encryption key with the new password, and send it to the server. The server then overwrites the password-encrypted encryption key. The underlying plaintext encryption key remains constant. The above is still a simplification of the approach. A more detailed explanation complete with diagrams will be provided in the not-so-distant future. If this is still unclear, happy to provide some rough diagrams that may be helpful. |
Ah, gotcha, so the key is signing in on the same device the reset request originated from. But this brings two questions to mind:
|
rememberMe='local' means the key is saved even when they sign out. So unlike with the change password flow, the user does not need to be signed in to call forgotPassword(), the user just needs to have the key saved in local storage (edit: and an email address already saved on their account for the server to send the temporary password to). Note that even if someone finds the user’s encryption key in local storage in the above circumstance, they would still need the user’s password (or the user’s email) to access the user’s account/data. We are also considering adding a forgetMe param to signUp and signIn (or to just signOut) that clears local storage in case the developer wants to delete their user’s key and any trace that the user logged in when the user explicitly signs out. |
Oh I see. Interesting! |
Forgot Password
Our top feature request so far is to let users reset their password. Userbase didn’t initially allow this because the user’s encryption key is stored on the Userbase server encrypted with a key derived from the user’s password. When the user signed in, the original password was needed to first retrieve and then decrypt the encryption key.
However, Userbase also allows you to let users automatically sign in after they close their browser by setting the
rememberMe
option to'local'
in the signUp and signIn APIs. In this case, the user’s encryption key gets saved in the browser’s local storage.Now, with the addition of the forgotPassword API, so long as the user still has access to a device that was previously used to log in with
rememberMe
set to'local'
, the user will be able to regain full access to their account using a temporary password sent to their email.Recovery will not be possible if the user forgets their password AND loses access to all previously used devices (!!!)
Implementation Details
forgotPassword({ username })
is called, the client establishes a WebSocket connection with the server to prove it has the user's encryption key (or more accurately, the user's seed).forgotPassword
returns successfully.updateUser({ currentPassword: tempPassword, newPassword })
.Notes from this PR
usedTempPassword
if the user signed in with the temporary password.forgotPassword
do not make sure that the temporary password sends successfully to a user's email and they do not make sure that the temporary password can be used to sign in. They simply test the API's failure modes or if it returns successfully. This is because it seemed receiving the email in a test would require setting up a test email server or likely involve some other time-intensive workaround. I tested that the temporary passwords sends successfully and can be used to sign in and change a user's password manually.