Below I register a user after authenticating through FB/Google. The issue here is in the try block. When I throw an error, for example when displayName already taken or user is already registered, the catch block is triggered but it appears to continue executing code in the try block because after throwing the error I am always getting redirected in the browser to '/' (last line of code in try block).
One solution would be to navigate the user programmatically on the client side but I don't see why this existing code doesn't work as expected.
app.post("/auth/register", requireAuth, async (req, res) => {
try {
if (req.user.registered === true) {
throw new Error("You have already registered.")
}
const existingUser = await User.findOne({
displayName_lower: req.body.displayName
});
if (existingUser) {
throw new Error("Display name already is use! Try another.");
}
await User.findByIdAndUpdate(
{ _id: req.user.id },
{
displayName: req.body.displayName,
displayName_lower: req.body.displayName,
registered: true,
joined: Date.now()
}
);
// Create and save new faves, follows, followers, messageBox doc for every newly reg'd user
await new Faves({
_owner: req.user.id
}).save();
await new Follows({
_owner: req.user.id
}).save();
await new Followers({
_owner: req.user.id
}).save();
await new MessageBox({
_owner: req.user.id
}).save();
res.redirect("/");
} catch (e) {
res.status(400).send({ error: e.message });
}
});
Related
I am having an issue using Mongoose's update function. Basically the document I am trying to modify is not getting updated.
Here is the code:
const user = 'Joe Bloggs'
const salt = await bcrypt.genSalt()
const pwStr = 'simplepassword'
const hashedPassword = await bcrypt.hash(pwStr, salt)
User.update({user_name: user }, { $set: { password: hashedPassword }}, function(err, doc) {
if (err) console.log('Error ', err);
console.log('Updated Doc -> ', doc); // returns un-updated doc
});
As you can see there is not much to it. The only thing I thought could be causing an issue was the bcrypt functions, but they seem to be working and hashedPassword logs out fine.
The callback logs the document, but it is not updated and when I check it in the Mongo shell it is indeed not updated.
I previously tried findOneAndUpdate but it appears that has been deprecated.
So, I tried findOne, but this also failed to update the document. Here is the basic code which uses save on the found user instead.
User.findOne({user_name: user}).then(async function(user) {
user.password = 'easypassword';
await user.save();
}
});
I tried using update in the shell using the same { $set: {...}} syntax and it works.
If anyone can tell me why this operation isn't working when I try to do it using the Mongoose functions I'd much appreciate it. Thanks in advance!
EDIT
I have tried the suggested code below:
const res = await User.updateOne([filter], [query]);
This returns the following when res is logged out:
{ acknowledged: false }
This appears in MongoDB documentation to relate to a "write concern" setting, but I have no idea where to go with it from there.
update is what is actually deprecated.
findOneAndUpdate, like other mongoose update methods, it returns the un-updated object by default. Setting the option new to true will return the updated doc.
Another option would updateOne;
User.findOneAndUpdate(
{ user_name: user },
{ $set: { password: hashedPassword } },
{ new: true },
function (err, doc) {
if (err) console.log("Error ", err);
console.log("Updated Doc -> ", doc);
}
);
I am trying to use passport-jwt strategy for authentication.
Here is my code :-
router.post('/register', async (req, res) => {
const { username, email } = req.body;
try {
const user = await User.findOne({ username });
if (user) {
throw new Error('User with same username already exists !!');
}
const newUser = new User({
username,
email
})
const salt = await bcrypt.genSalt(10);
newUser.password = await bcrypt.hash(req.body.password, salt);
const savedUser = await newUser.save();
res.json({
status: 200,
'Content-Type': 'Application/Json',
'message': `You have successfully regirstered yourself, ${savedUser.username}!`
})
} catch (err) {
err.statusCode = 500;
console.log(err.message);
res.header({
status: '200',
'Content-Type': 'Application/Json',
});
res.json(err);
}
});
Now this route is working just fine, it's doing all the things till now. The only problem is, When i find an existing user, I want to throw a new error with a custom message. Javascript has this Error class which I can use to throw these errors.
The problem occurs when it catches error. When I console.log(err.message), I can see my custom message perfectly. But the err object that I am returning in response via res.json(err) does not have any message but only statusCode.
I want to know why this is happening and what's the solution for this ? Right now, I am doing this by doing something like this :-
res.json({
statusCode: 500,
message : err.message
});
But I would like to return the err object with both statusCode and message fields populated.
You can create your own Error class which can take more than one parameter in the constructor. This class has to extend base Error JavaScript class. For example:
class MyCustomError extends Error {
constructor(msg, statusCode) {
super(msg);
this.statusCode = statusCode;
this.name = MyCustomError.name;
}
}
function throwCustomError() {
throw new MyCustomError('Some custom message', 404);
}
try {
throwCustomError();
} catch (error) {
console.log(error.message);
console.log(error.statusCode);
console.dir(error);
}
Remember that you have to call super on the beginning of the constructor if you are extending another class
You are passing the error object to the json method of the response object. But this only takes JSON parseable string as a parameter. What you can do is use -
res.json(JSON.stringify(err))
and at the place where you are using this response, you need to parse this string as JSON and then convert it into an Error object. You can use the following syntax assuming your front-end also uses javascript
err = new Error(JSON.parse(response.data))
Replace the entire catch block with the following line of code.
res.status(500).json({
message: err.message
})
From the documentation of res.json() : This method sends a response (with the correct content-type) that is the parameter converted to a JSON string using JSON.stringify().
Now running JSON.stringify(new Error('custom msg')) we get "{}"
The purpose of the following code is to check whether an email already exists in MongoDB, using express-validator:
app.post('/registerPage',[check('email').custom((email) => {
// connect to database
let MongoClient = require('mongodb').MongoClient;
let url = 'mongodb://localhost';
MongoClient.connect(url, function(err, client) {
if (err) throw err;
let db = client.db('Mydatabase');
// search database
return db.collection('users').findOne({
email: email
}).then(user => {
if (user) {
console.log(user); // here console shows correct record in database
return Promise.reject('E-mail already in use');
}
// otherwise, it returns null
});
})
}).withMessage('Error Message Example')], (req, res) => {
// Handle the request
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
});
When email already exists, console shows Promise.reject('E-mail already in use');.
The problem is when email does not exist, although it doesn't show Promise.reject, the code cannot process any further, validationResult(req) is not empty, so it still prints out the error message 'Error Message Example'. But there isn't any problem with non-custom validators which can successfully pass the checks.
I tried to add an else statement where !user, it doesn't work.
The question is how to pass the custom validation check, or why the array validationResult(req) is not empty even it should be? How do I make sure validationResult is empty after all checks were passed.
The issue is you are returning the promise in the callback of MongoClient.connect and not the validator function. Try using Promise wrapper like:
app.post('/registerPage',[check('email').custom((email) => {
return new Promise((resolve, reject) => {
// connect to database
let MongoClient = require('mongodb').MongoClient;
let url = 'mongodb://localhost';
MongoClient.connect(url, function(err, client) {
if (err) throw err;
let db = client.db('Mydatabase');
// search database
return db.collection('users').findOne({
email: email
}).then(user => {
if (user) {
console.log(user); // here console shows correct record in database
return reject('E-mail already in use');
}
return resolve();
});
})
});
}).withMessage('Error Message Example')], (req, res) => {
// Handle the request
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
});
Hope this helps!
I am currently trying to add some authentication to my node API.
Right now I'm using PassportJS (pretty new to this so sorry for my incompetents).
I am trying to add a local strategy and check if the users password is legit when loggin in:
// Local Strategy
passport.use(
new LocalStrategy(async (username, password, done) => {
try {
// Find user by username
const user = await User.findOne({ username })
// No user found
if (!user) {
return done(null, false)
}
console.log('user', user) // Getting output
// Check if password correct
const isMatch = await user.isValidPassword(password)
// Handle if password is not correct
if (!isMatch) {
return done(null, false)
}
// Return user
done(null, user)
} catch (err) {
done(err, false)
}
})
)
Something I've noticed is when using await on const isMatch = await user.isValidPassword(password) Postman is saying: Error: ReferenceError: user is not defined. And when I remove await it works fine, but I can type in the wrong password but I still can login. And I can see my user object when I console.log it.
{
"username": "martinnord3",
"password": "this_is_the_wrong_password"
}
Here's the isValidPassword function:
UserSchema.methods.isValidPassword = async function(newPassword) {
try {
return await bcrypt.compare(newPassword, user.password)
} catch (err) {
throw new Error(err)
}
}
I guess there's something obvious I'm missing, but I can't manage to solve this.
Thanks for taking your time to read this!
Well this is a bit awkward, but I guess it's my duty to answer my own dumb question... My function isValidPassword has this: ...user.password and I don't specify what user is in that function.. It expects this.
try {
const user = await User.create({
username,
password: hash,
email,
});
return res.json({
status: 'success',
object: {
username: user.username,
email: user.email,
},
});
} catch (error2) {
return res.status(500).send('Username already in use');
}
I'm testing to make sure you can't add the same two usernames to the database, and you can't it throws an error, but the error isnt being caught and the client never gets any type of response from the server after it makes the request. does anyone know why this is happening?