Open Swarnendu0123 opened 4 months ago
@raclim @lindapaiste give me some feedback I have almost made the changes, should any improvement is needed? or I should raise a PR?
Thanks for finding this! I wonder if it's possible to use the hash()
function with promises rather than callbacks? Then we could just await Promise.all
the array. Feel free to look into the bcypt
docs and to raise a PR. This is a low priority though because the entire API key feature has never actually been released.
https://www.npmjs.com/package/bcryptjs#gensaltrounds-seed_length-callback
I'm picturing something along the lines of
Promise.all(user.apiKeys.map(async (k) => {
if (k.isNew) {
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(k.hashedKey, salt);
k.hashedKey = hash;
}
}))
.then(() => next())
.catch((err) => next(err));
BTW it's possible that the scenario you are describing wouldn't come up if there can only be one new API key at a time, but I don't know if that's true. And I agree with your sentiment that we shouldn't have code that could potentially behave in unexpected ways.
Now I'm wondering where/when we set the k.isNew
flag to false
. But I'm resisting the urge to fall down that rabbit hole.
Cool! I have almost figured out the implement :)
Now I'm wondering where/when we set the
k.isNew
flag tofalse
. But I'm resisting the urge to fall down that rabbit hole.
As for the concern about setting the isNew
flag to false, it's important to reset this flag after hashing each API key to prevent rehashing it unnecessarily in subsequent save operations. With the provided code, the isNew
flag is set to false within the map
function after hashing each API key. This ensures that each new API key is hashed only once, even if the save operation is called multiple times.
userSchema.pre('save', async function checkApiKey(next) {
const user = this;
if (!user.isModified('apiKeys')) {
next();
return;
}
try {
await Promise.all(user.apiKeys.map(async (k) => {
if (k.isNew) {
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(k.hashedKey, salt);
k.hashedKey = hash;
k.isNew = false; // Set isNew flag to false after hashing
}
}));
next(); // Call next if all operations are successful
} catch (err) {
next(err); // Pass any error to the next middleware
}
});
This implementation maintains the flow of the middleware and ensures that the next()
function is called appropriately based on the completion or failure of the asynchronous operations.
Now I'm wondering where/when we set the
k.isNew
flag tofalse
. But I'm resisting the urge to fall down that rabbit hole.As for the concern about setting the
isNew
flag to false, it's important to reset this flag after hashing each API key to prevent rehashing it unnecessarily in subsequent save operations. With the provided code, theisNew
flag is set to false within themap
function after hashing each API key. This ensures that each new API key is hashed only once, even if the save operation is called multiple times.
I looked into it a bit more. I couldn't find anywhere else that we are setting or using the isNew
flag. It turns out that it's an automatic property of the Mongoose Document
object: https://mongoosejs.com/docs/api/document.html#Document.prototype.$isNew The API key would be a "subdocument" of the User object. So I think that we do not need to set it to false
anywhere, and Mongoose does that for us during the save operation.
Yes, Mongoose automatically handles the isNew
flag for documents during the save operation. When you create a new document instance, Mongoose sets the isNew
flag to true
. After you save the document to the database for the first time, Mongoose updates the isNew
flag to false
.
Let me just modify my PR.
What is your operating system?
Windows
Actual Behavior
In the user model, asynchronously hashes API keys within a loop during the
checkApiKey
middleware. However, due to the asynchronous nature of thebcrypt
operations, thenext()
function may be called multiple times before all API keys are hashed. This can lead to unexpected behavior, such as callingnext()
prematurely or multiple times.Implementation Now:
Expected Behavior
The
checkApiKey
middleware should correctly hash API keys usingbcrypt
and callnext()
only after all API keys have been hashed. This ensures that the middleware behaves as intended, handling each API key properly and advancing to the next middleware in the chain only when all asynchronous operations are complete.Correct implementation: