mongoose instance methods are showing different results - javascript

i'm currently making a server with javascript, koa, and mongoose.
i made two different mongoose instance methods, which receives password from JSON body
and hashes it with bcrypt, saves it on a 'hashedPassword' on user.
Initially, i wrote the second setPassword function (which uses promise, 'then' methods.),
but when the user document saved, the 'hashedPassword' property didn't showed up.
so i tried with first one, it worked well and user document has saved with hashedPassword.
although these two instance methods looks similar, i wonder why these methods leads to different result.. please help
import mongoose from "mongoose";
import bcrypt from "bcrypt";
const UserSchema = mongoose.Schema({
username: String,
hashedPassword: String,
});
UserSchema.methods.setPassword = async function (password) {
const hash = await bcrypt.hash(password, 10);
this.hashedPassword = hash;
};
// UserSchema.methods.setPassword = async function (password) {
// bcrypt.hash(password, 10).then((hash) => {
// this.hashedPassword = hash;
// });
// };
// this code didn't save the hashed
const User = mongoose.model("user", UserSchema);
export default User;
user controller would be like this
const register = async (ctx) => {
const { username, password } = ctx.request.body;
try {
const user = new User({
username,
});
await user.setPassword(password);
await user.save();
catch(e) { ctx.throw(500,e) }
}

This is not a mongoose-specific issue, you need to revise your understanding of how promises work.
Adding await before bcrypt.hash in the commented-out code should make it work, but there is no reason for you here to prefer .then over await.
In the second example, setPassword only fires the hashing process and doesn't ask the caller to wait until the hashing is done, and then adds the hashed password to the document, while the first one does.

You can try this approach using schema built-in methods:
UserSchema.pre('save', async function (next) {
if (this.isModified('hashedPassword')) {
this.hashedPassword= await bcrypt.hash(this.hashedPassword, 10)
}
next()
})

Use this pre-save function inside the schema like that :
UserSchema.pre('save', async function (next) {
// Only if password was moddified
if (!this.isModified('hashedPassword')) return next();
// then Hash password
this.hashedPassword = await bcrypt.hash(this.hashedPassword, 10);
next();
});

Related

how do i call a method from another controller node

I have 2 models and 2 controllers.
UserModel.js, UserNameModel.js, and userController.js, userNameController.js
After I successfully register a user, I want to record & store that new username. the goal is to not reuse an already-used username
How do I design this function without calling a controller in the other controller,
userController.js
const register = async (req, res) => {
const { userName, email } = req.body
const newUser = await createNewUser(name, email)
res.status(StatusCodes.OK).json({ newUser })
}
userNameController.js
......
The module.exports is a special object which is included in every JavaScript file in the Node.js application by default. The module is a variable that represents the current module, and exports is an object that will be exposed as a module. So, whatever you assign to module.exports will be exposed as a module.
so, you can use the separator file to write all functions you need for each model like users, contacts, and messages....
users.js
module.exports.createNewUser = async (data) => {
write your code here.
return user;
};
module.exports.checkUserExist = async (email) => {
write your code here.
return user;
};
module.exports.deleteUser = async (userId) => {
write your code here.
return true;
};
and if you want to use the file on any controller, should call it like.
userController.js
const {createNewUser, checkUserExist, deleteUser} = require(../users.js);
router.get('create-user', async (req, res) => {
const newUser = await createNewUser(req.body);
res.status(200).json({ newUser });
}

Mongoose: save() is not a function when using find() and atributing value to variable

This is the basic structure of the Schema I am working with using mongoose:
const User = {
uid: {
type: String
},
routes: {
type: Array
}
}
In my application there is a POST to /route, in which uid and a new route are provided as "body parameters". In order to add to the routes array, I wrote a code similar to this (the only diference is that I check if the route already exists):
var user = await User.find({uid: uid}) // user is found, as expected
user[0].routes.push(route //parameter)
user.save()
When a POST request is made, though, it throws an error:
TypeError: user.save is not a function
What am I doing wrong?
user in your code is an array of documents
so you'll have mongo documents inside that array
you can't do array.save, you've to do document.save
await user[0].save()
var user = await User.find({uid: uid}) // user is found, as expected
if (user && user.length) {
user[0].routes.push(route //parameter)
await user[0].save(); // save the 1st element of the object
}
if your query returns only 1 record better use https://mongoosejs.com/docs/api.html#model_Model.findOne
var user = await User.findOne({uid: uid}) // user is found, as expected
if (user) {
user.routes.push(route //parameter)
await user.save(); // save the 1st element of the object
}
if you need to find only one specific user you should use findOne function instead
User.findOne({uid: uid})
.then(
(user) => {
user[0].routes.push(route //parameter);
user.save();
},
(err) => {
console.error(err);
}
)
I think bulkSave() can be what you're looking for:
var user = await User.find({uid: uid}
enter code user[0].routes.push(route //parameter)
await User.bulkSave(user)

ORM: Updating all models of a given instance

I'm having an conceptual issue on how to update all instances of a model once I have updated it.
Imagine the following method renameUser (could be any ORM):
async function renameUser(userId: number, newUsername: string) {
const user = await User.findByPk(userId);
await user.update({ username: newUsername });
}
And the following usage:
const user = await User.create({ username: "old" });
// user.username === "old"
this.renameUser(user.id, "new");
// still, user.username === "old"
Obviously this problem wouldn't exist if I would pass the user object directly into the update method, but that only works in this simple example - In my case it is actually not possible to share the same instance, since it can be modified via hooks in an entirely different context.
So, one simple solution would be to call user.reload() after the call, which will pull the latest user data from the database:
const user = await User.create({ username: "old" });
// user.username === "old"
this.renameUser(user.id, "new");
user.reload();
// now: user.username === "new"
However, this requires me to know that the renameUser method will change my user object. In this case it is obvious, but if the method is called that's not always possible.
Is there any pattern I can use to work around this? One thing which came in my mind was to create a UserFactory which ensures that I only have one instance of a user (indexed by its primary key) at any time, and then update that instance. But I was wondering how others solve it? Is it a common problem?
Why you dont use .save() in updateUser function?
const renameUser = async (userId, newUsername) => {
const user = await User.findByPk(userId);
user.username = newUsername;
await user.save();
return user;
}
and use it like this
const user = await User.create({ username: "old" });
// user.username === "old"
user = await this.renameUser(user.id, "new");
// now, user.username === "new"
You can run your queries more efficiently by just running an update using the Model.update() function instead of querying for an Instance and then updating it.
async function renameUser(userId: number, newUsername: string) {
const [ updatedRowCount, updateRowOnPostgresOnly ] = await User.update({
username: newUsername,
}, {
where: {
id: userId,
},
});
// you can use the updated row count to see if the user changed.
const isUpdated = updatedRowCount > 0;
return isUpdated;
}
Now you can await the result to see if the row changed, even without loading it.
const user = await User.create({ username: "old" });
// user.username === "old"
const isUpdated = await this.renameUser(user.id, "new");
if (isUpdated) {
user.reload();
// now: user.username === "new"
}
Note that in your example you are not using await on the async runameUser() function.
This varies from your question a bit, but if you are already working with Model instances then you can use Instance.changed() to get the changed fields.
const user = await User.create({ username: "old" });
user.username = "old";
const changed = user.changed(); // == [ "username" ]
if (changed.length) {
await user.save();
}
*Note that in that example it checks for changed.length but really Sequelize won't do anything if you await instance.save() and nothing is in instance.changed() - change awareness of save.

MongoDB stores "Promise" object instead of hashed password

I am using bcrypt to hash passwords and MongoDB as my database.
Here is the code:
export default function buildMakeUser({pwdHasher})
{
return function makeUser({
username,
email,
password,
password_hash = pwdHasher(password), // the important part
favoriteColor
} = {})
{
// ...
return Object.freeze({
getUsername: () => username,
getEmail: () => email,
getHashedPassword: () => password_hash,
getFavoriteColor: () => favoriteColor
});
}
And here is pwdHasher's definition:
import bcrypt from "bcrypt";
import buildMakeUser from "./entity/user.js";
async function pwdHasher(password){
let hashed;
hashed = await bcrypt.hash(password, 10);
return hashed;
}
However, when I store the user in the database, here's the result:
ops: [
{
username: 'kibe',
email: 'blabla#gmail.com',
password_hash: [Promise],
_id: 5ecc8b752e0aa53e87d5b62a
}
],
It seems like makeUser's object does not wait for pwdHasher(password). I have tried wrapping pwdHasher in a Promise and it also did not work.
Does anyone know why?
Thank you.
There are two solutions I see based on your code snippet:
First way:(Recommended)
Use await when calling the async function. The code will be:
export default function buildMakeUser({pwdHasher})
{
return function makeUser({
...
password_hash = await pwdHasher(password), // the important part
...
})
}
Alternative way:
Use hashSync method of bcrypt instead of using async-await.
You have defined pwdHasher as:
async function pwdHasher(password) { ... }
But you call it as:
password_hash = pwdHasher(password),
By definition, an async function RETURNS A PROMISE. If you'd like to get the value from the promise, you must either await the result or use pwdHasher(password).then(...)

How to get data passed to mongoose schema constructor

I am testing my application and need to verify that mongoose schema constructor is called with correct data.
let's say I do this:
const UserData = new User(user)
console.log(UserData.contructor.args)
I would expect log of the user object.
Probably the data is passed to constructor of mongoose schema?
Can some one please advise me how to access it?
Here is specific case I am trying to solve.
export const signup = async (req, res, next) => {
try {
//if user object is missing return error
if (!req.body.user)
return next(boom.unauthorized('No user data received.'))
//get user data
const user = req.body.user,
{ auth: { local: { password, password_2 } } } = user
//check if both passwords match
if (password !== password_2)
return next(boom.unauthorized('Passwords do not match.'))
//check if password is valid
if (!Password.validate(password)) {
const errorData = Password.validate(password, { list: true })
return next(boom.notAcceptable('Invalid password.', errorData))
}
//creates new mongo user
const UserData = new User(user)
//sets user password hash
UserData.setPassword(password)
//saves user to database
await UserData.save()
//returns new users authorization data
return res.json({ user: UserData.toAuthJSON() })
} catch(err) {
//if mongo validation error return callback with error
if(err.name === 'ValidationError') {
return next(boom.unauthorized(err.message))
}
// all other server errors
return next(boom.badImplementation('Something went wrong', err))
}
}
And part of test:
describe('Success', () => {
it('Should create new instance of User with request data', async () => {
const req = { body },
res = {},
local = { password: '1aaaBB', password_2: '1aaaBB'},
constructorStub = sandbox.stub(User.prototype, 'constructor')
req.body.user.auth.local = {...local}
await signup(req, res, next)
expect(constructorStub.calledOnceWith({...req.body.user})).to.be.true
})
})
EDIT: I can verify that is is called with expect(constructorStub.calledOnce).to.be.true
Just can't get to verify data passed.
Edit: After talking for some time sounds like what you need is to validate that you are creating a new user correctly.
My suggestion here is to create a new function createUserFromRequest that would take in request and return a new User.
You can then test this function easily as it's pure (no side effects, just input and output).
At this point, most of the logic in your handler is in this function so it would be probably not worth testing the handler itself, but you could still do it, for example by mocking the function above.
Example:
function createUserFromRequest(request) {
//get user data
const user = req.body.user,
{ auth: { local: { password, password_2 } } } = user
//check if both passwords match
if (password !== password_2)
return next(boom.unauthorized('Passwords do not match.'))
//check if password is valid
if (!Password.validate(password)) {
const errorData = Password.validate(password, { list: true })
return next(boom.notAcceptable('Invalid password.', errorData))
}
//creates new mongo user
const UserData = new User(user)
//sets user password hash
UserData.setPassword(password)
return UserData;
}
Please note: stubs and mocking are usually a code smell: there could either be a better way of testing, or it could be a sign of a need to refactor the code into something more easily testable. They usually point to tightly coupled or cluttered code.
Check out this great article on that topic: https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a

Categories

Resources