I'm sure this is a novice question, but I've only been having a go at learning express/node/react for a month or so now.
I'm just trying to make a simple node signin REST API call. Here's a snippet of code, with some 'pseudo-izing' of the unimportant parts for brevity:
server.post('/signin', (request, response) => {
const {user_email, password} = request.body
// query db for user validation
db('user_login')
/* knex query building, blah blah blah */
.then(res => {
if (res.length == 0) {
// if res.length == 0, user not found
throw new Error("bad credentials")
} else if (res.length > 1) {
// if res.length > 1, duplicate user found - shouldn't ever happen
throw new Error("CRITICAL: database error")
} else {
// everything should be ok - pass res on to bcrypt
return res
}
})
.then(res => {
// bcrypt.compare doesn't return a promise because it is being given a cb
bcrypt.compare(password, res[0].pw_hash, (err, match) => {
if (match) {
// delete pw_hash from any possible response(),
// don't give client more info than it needs
delete res[0].pw_hash
// we have a match! inform the client
response.json(res[0])
} else {
// we don't have a match
throw new Error("bad credentials") // WHY DOES THIS THROW CRASH!??!?!!?!?
}
})
})
// WHY ISNT THIS REACHED WHEN THERE'S A PASSWORD MISMATCH?
.catch(err => {
console.error('signin error: ', err)
response.status(403).json({
name: err.message,
severity: 'auth error',
code: 403
})
})
})
Ok so:
- When a correct username and correct password is supplied, it functions as expected.
- When an incorrect username is supplied, the .catch is reached (ie. functions as expected).
- BUT: when a correct username and an incorrect password is supplied, the throw statement (with the comment // WHY DOES THIS THROW CRASH?)... crashes node.
Here's the call stack:
C:\.............\server.js:83
throw new Error("bad credentials") // WHY DOES THIS THROW CRASH!??!?!!?!?
^
Error: bad credentials
at C:\.............\server.js:83:23
at C:\.............\node_modules\bcrypt-nodejs\bCrypt.js:689:3
at processTicksAndRejections (internal/process/task_queues.js:75:11)
[nodemon] app crashed - waiting for file changes before starting...
I could "cheat" and just do a response.status(403).... instead of that throw. But in my mind, throwing that error ought to jump to the .catch, and handle any auth failure there.
Can anyone help me w/ what's going on here? It's very frustrating.
PS. This crash only seems to be happening when the throw is within the bcrypt.compare callback. I figure that has something to do with it. I've googled.. I've looked around on here.. I've wrapped things in try/catch blocks. I'm just beating my head against it at this point.
Thank you! :)
Do not throw an error from Node like this. You notice that the bcrypt callback has an err parameter. Send that back and handle it how you want to in the back or front.
Related
I am creating a NodeJS API using Express, PostgreSQL in which I created a controller function which will check the user from the database.
const checkUser = async (req, res) => {
try {
const user = await pool.query('select * from users where email = $1', [req.body.email]);
if (user.rows.length === 0) throw new Error('Error Found');
return res.json("User Found");
} catch (e) {
//======= Output at console ========
//Error: Error Found
console.log(e);
//======== Response Object Received From API ==========
// {
// "msg": "Error Found",
// "Error": {}
// }
res.status(400).send({ msg: 'Error Found', Error: e });
}
};
Consider the situation that the user is not found in the Database so from try block the control passes to the catch block.
1- I am not able to get why the Error thrown from the try block to catch block sends back an empty object as a response. Also, at the same time using console.log prints the correct output value in the console.
2- I want a fix so that I can send e as a response to the User.
The problem is that Error objects are not that easy to serialize (which is intentional). This...
try {
throw new Error('Catch me if you can');
} catch(e) {
console.log(JSON.stringify(e)); // {}
}
... just logs {}, the same as for empty object, because Error objects don't have enumerable properties. And in your case, you don't send that object directly - but make it a property of another object sent to client.
However, there are several ways out of this. If you always need your client to get all the details of an Error, cast that to string (as toString() is overridden):
try {
throw new Error('Catch me if you can');
} catch(e) {
console.log(JSON.stringify(e.toString())); // "Error: Catch me if you can"
}
... or use Error object properties - name and message - as separate fields of your response:
res.status(400).send({
name: e.name || 'Anonymous Error',
msg: e.message || 'no message provided'
});
As sub-approach, you might consider choosing your status code based on type of error you have (always sending 404 for 'not founds', for example).
So, in this piece of code I'm trying to use findOne to find and delete a particular dishId from my Favorites document. The code is working fine if I send a valid dishId but when I enter a wrong dishId the code does throw err; and the Node server stops with this error.
events.js:292
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'dishes' of null
And then I've to do npm start again. So how should I tackle this? I don't want server to stop. I want it to keep going so that I can do further requests. Here's my code.
favoriteRouter.route('/:dishId')
.delete(cors.corsWithOptions, authenticate.verifyUser, (req,res,next) => {
Favorites.findOne({user: req.user._id, dishes: req.params.dishId} ,(err, favdel) => {
if(err) {
throw err;
}
else {
favdel.dishes.pull({_id:req.params.dishId});
favdel.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favdel);
}
})
});
The error is pretty clear: you're blindly accessing a property without first checking whether it exists, so: check if favdel.dishes exists before you try to get data out of it. And if it doesn't, make error handling kick in, in a way that makes sure to send the correct HTTP error code.
...
if (!favdel || !favdel.dishes) {
// only you know which 4xx or even 5xx error this should be
return next(new Error("..."));
}
favdel.dishes.pull({_id:req.params.dishId});
favdel.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favdel);
...
the answer to this question: How to get node to exit when mongo connect fails contains async/wait code for a connection
however, my code (running on node v11.5.0 and mongodb v3.1.13) is failing to catch:
(async function() {
let db;
try {
db = await MongoClient.connect(uri, { useNewUrlParser: true });
console.log("RETURN", db);
} catch (err) {
console.log('EXITING');
process.exit(1);
}
}());
to prove the point I intentionally give a uri without credentials:
mongodb://undefined#cluster0-shard-00-00-z4j9e.azure.mongodb.net:27017,cluster0-shard-00-01-z4j9e.azure.mongodb.net:27017,cluster0-shard-00-02-z4j9e.azure.mongodb.net:27017/test?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true
and what I get is output like this:
/Users/ekkis/dev/mongo/node_modules/mongodb/lib/topologies/replset.js:346
throw err;
^
MongoError: password must be a string
at passwordDigest (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/auth/scram.js:63:43)
at ScramSHA1.ScramSHA.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/auth/scram.js:175:25)
at authenticate (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:232:17)
at authenticateLiveConnections (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:819:7)
at /Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:864:5
at waitForLogout (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:855:34)
at Pool.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:862:3)
at Server.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/topologies/server.js:931:20)
at auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/topologies/replset.js:1474:19)
at ReplSet.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/topologies/replset.js:1492:5)
so if the error had been caught, the console should have displayed the word 'EXITING', but does not. additionally, I contend an exception was thrown because otherwise the returned value would have been printed, which it was not
how can this be? what do I need to do to get it to work?
* Appendix I *
In fact, the promises version of this exhibits the same odd behaviour, it doesn't catch:
MongoClient
.connect(uri, { useNewUrlParser: true })
.then(dbc => {
console.log('SUCCESS');
})
.catch(err => {
console.log('EXITING');
process.exit(1);
});
and yes, I tested the callback version, which also suffers the same malady. Incidentally, passing an empty string for the uri works well. I don't get it
* Appendix II *
In fact, the problem seems to be particular to the credentials passed i.e. if I pass:
mongodb://x:y#cluster0-shard-[...]
I catch a "MongoError: authentication fail" as expected. passing:
mongodb://#cluster0-shard-[...]
interestingly returns a connection but credentials missing a ":" fail in this odd way, so:
mongodb://ekkis#cluster0-shard-[...]
fails to catch
Looks to me like it's a bug with however MongoClient is setting up its connections. You won't be able to use try & catch to handle asynchronously thrown errors within MongoClient code.
const {MongoClient} = require("mongodb");
process.on("uncaughtException", (err) => {
console.log("process err", err);
process.exit(1)
})
async function run () {
let db;
try {
// connection url will throw because password isn't provided
db = await MongoClient.connect("mongodb://myUsername:#localhost", { useNewUrlParser: true });
} catch (err) {
console.log('Exiting from thrown error', err);
process.exit(1);
}
}
run();
Here's a simplified example of what's happening -- the error will end up "uncaught" and caught by the uncaughtException handler
process.on("uncaughtException", (err) => console.log("uncaught", err));
try {
setTimeout(() => {
throw new Error("asynchronously thrown error");
})
} catch (err) {
console.log("Error will not be caught here")
}
When I was using mongo version 3.6.1, it was not an issue and i was able to handle the thrown exception using catch. But after a few days on another project this type of error occurred and was showing as the error thrown from
%project_folder%/node_modules/mongodb/lib/utils.js:668
(Don't mind about the slash in the path string.)
The mongodb version this time is 3.6.3. Upon checking the code in that file at the mentioned line I found the below piece of code. where the caught error is again being thrown.
fn(function(err, res) {
if (err != null) {
try {
callback(err);
} catch (error) {
return process.nextTick(() => {
throw error;
});
}
return;
}
callback(err, res);
});
I changed the throw error to console.error(error) and the problem got resolved. But still you need to be caught somewhere in our code where connect function is called.
I think this is because the above piece of code is checking for the presence of error and passing it to the callback function and then again throwing the same error again. I suppose it is the MongoDB driver developer community's responsibility to resolve this issue.
I have a special case for which I want to clear the possible reason for 503 Error. The following code snippet has a catch statement which runs when system is not able to find any results
app.post('/api/fetch/user', function(req, res){
var email = req.body.emailTxt;
db.one('SELECT * FROM users WHERE email=$1', [email])
.then(function(data){
console.log('DATA:', data);
var userCard = { id: data.user_id, name: data.user_name,
email: data.email, regDate: data.date_created };
res.status(200).json({ 'valid': true, '_payload': userCard });
})
.catch(function(error){
if(error.search(/No data returned from the query/im) > 0) // regex case insensitive search and search multiline as source string is multiline
res.status(500).send('Invalid Request Match');
else
res.status(500).send('ERROR: '+error);
})
});
When my API call is made to this API end point and when no result found the control moves in catch() which is fine but quite strangely it returns 503 - Request timeout error.
I have tried to to remove conditions in if() in order to debug but seems like no matter what but the if-else does not seem working in ExpressJs.
Note: Everything works well and also when control stays in .then(). If I remove if,else and keep simple error display/response return then everything works ok. There is nothing special included in my API; it is only one single page script I prepared to test API.
I think you got error in the .catch block.
Try to modify the code and see if this helps:
app.post('/api/fetch/user', function(req, res){
var email = req.body.emailTxt;
db.one('SELECT * FROM users WHERE email=$1', [email])
.then(function(data){
console.log('DATA:', data);
var userCard = { id: data.user_id, name: data.user_name,
email: data.email, regDate: data.date_created };
res.status(200).json({ 'valid': true, '_payload': userCard });
})
.catch(function(error){
console.error(error); //never ignore errors!
try {
//i think error is an Error Object here, so it doesn't have .search function
if(error.search(/No data returned from the query/im) > 0) // regex case insensitive search and search multiline as source string is multiline
res.status(500).send('Invalid Request Match');
else
res.status(500).send('ERROR: '+error);
} catch (err) {
console.error(err);
res.status(500).send('some unknown error');
};
});
});
Edit: Sorry, removed .finally because you may send response twice.
Edit, better approach to handle error in .catch block.
//make catch block error safe to make sure no error occurs
if (error && error.message == 'No data returned from the query.') {
res.status(500).send('Invalid Request Match');
} else {
res.status(500).send('ERROR: '+error);
}
We use response codes in ExpressJs quite intensively when creating an API service. So it seems a right way that we make use of them directly instead of doing if-else in catch().
Hence, the right way would be:
.catch(function(error){
console.log('ERROR:', error);
res.status(204).send('No Content; Invalid Request Match');
})
However, If you want to display/return detailed message you can do that as well. You can check: http://expressjs.com/en/api.html#res for details on ways you can response back. You can render an HTML template, or return JSON response or even multiline text.
I don't know why if-else creates problem but you can go other way around.
In your specific case the error does not have .search() method attached to it. So better to parse it to string first using .toString();.
I have the following function for creating an user in my app, I'm trying to detect if threre's already an admin user created and prevent for creating another one.
export async function createUser (ctx) {
if ( ctx.request.body.type == undefined ) {
ctx.throw(400, 'Bad Request')
}
if (ctx.request.body.type === 'admin') {
User.findOne({type:'admin'}, (err, usr) => {
if (err)
ctx.throw(422, err.message)
if (usr){
ctx.throw(400, 'Duplicate Admin')
}
})
}
....
The first ctx.throw(400, 'Bad Request') works, but if another admin user is found the ctx.throw(400, 'Duplicate Admin') will cause the following error, crashing the app:
events.js:160
throw er; // Unhandled 'error' event
^
BadRequestError: Duplicate Admin
I'm throwing the error in an inadequate manner? what causes the first throw to work without crashing but not the second?
Thanks in advance for any help
You're throwing asynchronously inside the User.findOne callback. This crashes.
Instead, look at the library you're using that gives you User.findOne and see if it can return a promise, or wrap it in bluebird's Promise.promisify if it doesn't.
This is the code you want to arrive at:
const user = await User.findOne({ type: 'admin' }) // returns a promise
if (user) ctx.throw(400, 'Duplicate admin')
At first, check the type of 'user' variable.If it an array or object, if(user){....} will always return true, even if it's empty. And try to add
app.on('error', function() {
console.log('yep this is an error');
});
event listener