Mongoose Schema Instance Methods is Not a Function - node.js

I've have had a working version of mongoose instance methods working before. I'm not sure what is different about it this time around. The only thing I have done differently this time is that I separated the mongoose connection function outside of the server.js file into a config file that will be imported and call the connect() function.
I will mostly use this instance method in passport with the local strategy to log in the user. When I go to call my instance method on the user instance that was found by the previous UserModel.findOne({ email }) the verify(password) instance method is not called and does not throw any errors.
For testing purposes, I've tried to hard code a UserModel.findOne() right into connection field and I do get a user back. I then decided to call my instance method right off of the returned user instance named verify().
I have also attempted changing the name of the method to comparePassword, I've tried testing with statics to check if it is even called at all (which it wasn't), I've also tried to research other ways to import my schemas and models, and that does not seem to work. I have experimented with Async/Await hasn't changed the output
File: mongo.db.js
const connect = () => {
return new Promise((resolve, reject) => {
mongoose.connect(
config.get('DB.STRING'),
{ useCreateIndex: true, useNewUrlParser: true },
async (err) => {
if (err) reject(err)
resolve()
// TESTING INSTANCE METHODS
await mongoose.connection
.collection('users')
// HARD CODED TEST EMAIL
.findOne({ email: 'braden_feeney#hotmail.com' }, (err, result) => {
if (err) reject(err)
console.log(result)
console.log(result.verify('test1234'))
})
},
)
})
}
const close = () => {
return mongoose.disconnect()
}
export default { connect, close }
File: passport.config.js
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
// Find the user given the email
const user = await User.findOne({ email })
// If not found
if (!user) return done(null, false)
// Check if the password is correct
const isValididated = await user.verify(password)
// If not matched
if (!isValididated) return done(null, false)
// Return the user
done(null, user)
} catch (error) {
done(error, false)
}
},
),
)
File: users.model.js
const UserSchema = new Schema(
// HIDDEN FOR SECURITY
{ ... },
{ versionKey: false, timestamps: true },
)
// HIDDEN FOR SECURITY - PRE SAVE WORKS AS EXPECTED
UserSchema.pre('save', async function(next) { ... })
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = function(password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
export default model('User', UserSchema)
When I call user.verify(password) I expect to see a Boolean value either get returned from the function.
The actual result is an Error thrown stating
TypeError: user.verify is not a function

This section does not make sense:
async (email, password, done) => {
try {
const user = await User.findOne({ email })
if (user) // <-- if you have a user record
return done(null, false) // <-- you return!
// There was no valid user ... how would there be user.verify?
const isValididated = await user.verify(password)
if (!isValididated) return done(null, false)
done(null, user)
} catch (error) {
done(error, false)
}
}
You seem to return on valid user but calling user.verify when you do not have a user. So it seems your flow is somewhat problematic.

After tinkering around with how mongoose creates instance methods, I tried using another way to make the method work. I had to wrap the bcrypt.compare() in a promise because my code wasn't waiting for a response.
File: users.model.js
UserSchema.method('verify', function(password) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, this.password, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
})
I still prefer the way that is mentioned in my question because I believe it looks cleaner and is not relying on a string to be the method name.

All you have to do is just mark this function with "async".
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = async function (password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
& Another thing that you've to keep in mind is that when you're retrieving user from your DB make sure to use the "await" keyword there as well.
I was getting the same error and then I noticed that I forgot to use the "await" keyword and my code goes to the next line without even waiting for the "user" to arrive from DB and we don't have a user or instance and we're calling "compare" method on that instance which hasn't arrived from DB.
That's why you were getting error that comparePassword is not a function.

Related

How can I get data from passportjs Node js

I'm using passport js for validate users. Here is my strategy.
passport.use(
new Strategy(opts, async ( payload , done) => {
try {
let user = await User.findById(payload.id);
if (!user) throw new Error("User not found");
if(user.role !== payload.role) throw new Error("Hacker");
return done(null, user.getUserInfo());
} catch (e) {
console.log(e);
done(null, false);
}
})
);
And if all OK, I'm returning a user.getUserInfo(). Now, here is my route:
router.get("url", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
} catch (e) {
console.log(e);
}
});
And now, when user pass the validator, how can I get this data from return done() statement, or it is unrealistic to take data from there.
In routes, try - {session: true}
and inside req.user, you will get the user data.
Otherwise, a work around can be to set user data in request just before done callback function.
req.userData = user.getUserInfo();

How to get data from passportjs validator in node js?

I'm using passport js for validate users. Here is my strategy.
passport.use(
new Strategy(opts, async ( payload , done) => {
try {
let user = await User.findById(payload.id);
if (!user) throw new Error("User not found");
if(user.role !== payload.role) throw new Error("Hacker");
return done(null, user.getUserInfo());
} catch (e) {
console.log(e);
done(null, false);
}
})
);
And if all OK, I'm returning a user.getUserInfo(). Now, here is my route:
router.get("url", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
} catch (e) {
console.log(e);
}
});
And now, when user pass the validator, how can I get this data from return done() statement, or it is unrealistic to take data from there. I'm a new in node js, so I don't know how to do that, or Is real to take data from passport.authenticate()?
In routes, Do:
{session: true}
and inside req.user, you will get the user data.
Otherwise, a work around can be to set user data in request just before done callback function.
req.userData = user.getUserInfo();

Get data from findOne mongoose inside another findOne function

I am trying to get the candidate or HR (user roles) object using mongoose and nodejs. I have a user and both roles are derived from it.
when trying to connect using a UNIQUE username and a password. A user object will be sent as a result. I want to also send candidate/ or HR that are linked to that user.
I am passing the user object by reference to the candidate/HR schema:
const candidateSchema = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
index: true,
},
fullName: String,
profilePhoto: String,
birthday: Date,
I need to get the candidate object of the user that i get inside the exec() function. save it in a variable and send it as a res to signin function
app.post("/api/auth/signin", (req, res) => {
User.findOne({
username: req.body.username,
})
.populate("roles", "-__v")
.exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
const candi = candidat.findOne({ user: user }).exec((err, candidate) => {
//I want to save the candidate var
}));
//console.log("res",candi);
.....
});
A simple solution will be to wrap your code inside a promise and resolve whatever you want to store to variable while reject when you want to send error.
But its recommended to break down your code to multiple async functions and await them instead of using callback exec functions.
app.post("/api/auth/signin", async (req, res) => {
try{
let response = await new Promise((resolve,reject)=>{
User.findOne({
username: req.body.username,
})
.populate("roles", "-__v")
.exec((err, user) => {
if (err) {
//REJECT ERROR
reject(err);
return;
}
const candi = candidat.findOne({ user: user }).exec((err, candidate) => {
//RESOLVE YOUR CANDIDATE
resolve(canditate);
}));
//console.log("res",candi);
.....
});
.... your rest of code
})
res.send(response) // or required json
}catch(err){
res.status(500).send({ message: err });
}
}

Deleting the model data through lodash and save() not persisting model in mongodb

I am trying to remove one object from the User collection like this
router.post('/accept-trades', function (req, res, next) {
const {senderName, receiverName, senderId} = req.body;
const user = req.user;
console.log(senderName, receiverName);
if (senderName) {
User.findOne({ name: senderName })
.then(sender => {
_.remove(user.receivedTradeRequest, {username: senderName});
_.remove(sender.sentTradeRequest, {username: receiverName});
console.log('user.receivedTradeRequest', user.receivedTradeRequest);
console.log('\n\nuser.sentTradeRequest', user.sentTradeRequest);
async.parallel([
function (cb) {
user.save()
.then(isSave => {
cb(null, true);
})
.catch(err => {
cb(err, null);
});
},
function (cb) {
sender.save()
.then(isSave => {
cb(null, true);
})
.catch(err => {
cb(err, null);
});
}
], (err, results) => {
if (err) {
return res.status(500).json({
message: 'Error: Trade is invalid as Card is already traded!',
});
}
res.send('done');
//res.redirect('/trade');
});
})
.catch(err => {
throw err;
});
} else {
return res.status(500).json({
message: 'Only accessible to logged in users!',
});
}
});
Here, user is accessed by req.user (i'm using passport).
When i log the user after removal, user.receivedTradeRequest and sender.sentTradeRequest printing empty array which is the correct behaviour.
But when i see the mongodb the array still present for the username.
Could you please suggest what is wrong with the code ?
PS: I know about the mongodb $pull for removal. I am doing some other computation on the user data so had to do with above approach.
I was able to solve it by re-assigning the array after removing the element. Used _.filter instead of _.remove solves the problem.
One thing i don;t understand is the lodash _.remove update the original array after deletion but that clearly is not the case here.

how to chain statements together using promises

I'm new to node, and learning all about promises, and pg-promise specifically. This is what I want to do using Express and pg-promise:
check email,
if not found check username,
if not found create a new user.
return user id
I've got my repo set up (db.users) which does the sql which is working great.
In my authorization handler I'm not sure how to make the repo calls follow one another. The syntax seems clunky to me. Here's what I have so far:
exports.signup = function( req, res, next ) {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
// See if a user with the given email exists
db.users.byEmail({email: email})
.then(user => {
if (user) {
return res.status(422).send({ error: 'Email is in use'});
} else {
return null; <-- must I return something here?
}
})
.then(() => {
db.users.getUsername({username: username})
.then(user => {
if (user) {
return res.status(422).send({ error: 'Email is in use'});
} else {
return null; <-- must I return something here?
}
...etc
})
Maybe pg-promises don't chain together like this? Should they be nested within one another or maybe be completely separate blocks? Also, not quite sure where the catch goes. I've tried every way I can think of, following various tutorials, but I get errors like 'can't set headers that already set' and 'promises aren't being returned'. If any kind person can guide me here, I'd really appreciate it.
You must use a task when executing multiple queries, so they can share one connection, or else the connection management will suffer from performance issues.
db.task(t => {
return t.users.byEmail({email})
.then(user => {
return user || t.users.getUsername({username});
});
})
.then(user => {
if (user) {
res.status(422).send({error: 'Email is in use'});
} else {
// do something else
}
})
.catch(error => {
// process the error
});
It will work well, presuming that your repositories were set up as shown in pg-promise-demo.
The previous 2 answers were really bad advise, and completely against what pg-promise says you should do. See Tasks versus root/direct queries.
And when doing multiple changes, you would usually use a transaction - use tx instead of task.
With guidance from vitaly-t I altered his answer to include separate error messages depending on which thing failed. I also moved up the next "then" into the transaction block to use the "t" of the transaction for the next step which creates user. Much thanks for vitaly-t!
db.task(t => {
return t.users.byEmail({email})
.then(user => {
if (user) {
throw new Error('Email is in use');
} else {
return t.users.byUsername({username});
}
})
.then((user) => {
if (user) {
throw new Error('Username is taken');
} else {
return t.users.addNew({username, email, password});
}
})
})
.then(user => {
res.json({token: tokenForUser(user), username: user.username, aCheck: user.is_admin});
})
.catch(error => {
res.status(422).json({ 'error': error.message});
});
Checking username and email existence is independent of each other. I think Promise.all() will suit your need more than sequentially chaining promise.
exports.signup = function (req, res, next) {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
Promise.all([
db.users.byEmail({
email: email
}),
db.users.getUsername({
username: username
})
]).then((results)=> {
if (results[0] || results[1]) {
return res.status(422).send({
error: 'Email is in use' // same error msg as per your snippet
});
}
// here, code for creating a new user
});
};

Resources