However, what happens when the client tries to get the salt of a non-existent user? At the moment, since we are not handling the case where Elasticsearch comes back with zero search results, our API will respond with a 500 Internal Server error. But how should our API respond?
If we respond with a 404 Not Found error, then anyone with an API testing tool such as Postman will be able to determine whether a user with that email has an account on our platform. Imagine if our platform is not a public user directory, but a customer portal for personal/medical services such as plastic surgery centers, fertility clinics, or law firms; it'd be embarrassing for the clients if someone found out that he/she is registered with the service simply by typing in his/her email and not getting a "User not found" message.
Whether the consequences are potentially embarrassing or not, it is generally a good practice to expose as little information as possible. This is an extension of the principle of least privilege, where a system should only expose the minimal amount of information for an entity to carry out its functions.
Therefore, returning a 404 Not Found error is not appropriate.
So, what's the alternative? Since all our bcrypt salts have the same length (the sequence $2a$10$ followed by 22 characters) and a valid character range, we can simply generate a new salt using bcrypt.genSaltSync() and return this as the salt. For example, we can define the following catch block at the end of our getSalt engine module:
.catch(err => {
if (err.status === 404) {
return bcrypt.genSaltSync(10);
}
return Promise.reject(new Error('Internal Server Error'));
});
However, someone looking to exploit our API can send multiple requests, observe that each salt that is returned is different, and deduce that this is not a real user (because a user is likely to have the same salt within a short space of time). So, even though generating a new random string for non-existent users will slow down such an attacker, our API would still be leaking too much information.
Instead, we can use a pseudorandom number generator (a PRNG, which is a type of deterministic random bit generator (DRBG)). PRNGs generate a number sequence that appears to be random, but is actually determined based on an initial value (called the seed). Therefore, we can use the user's email address as the seed, and use it to generate a seemingly random number sequence, somehow transform it into a 22-character string, prepend the sequence with $2a$10$, and send it back to the client as the salt value for that user. This way, a persistent, non-changing salt is returned, regardless of whether the user exists or not.