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
Related
I'm trying to make a simple login system using express sessions, but I keep getting a "Invalid status code" when the user is not found in the database.
I want to make it so when the user could not be found in the database, it redirects the client to a specific route. But instead of redirecting the client, it throws an error.
It works perfectly fine if it finds the user, no bugs.
If someone could help guide me to the solution of the problem, that would be greatly appreciated!
Log in route
//#ROUTE: Log in route
//#DESCRIPTION: Renders the login page
app.get('/login', (req, res)=> {
res.render('login.ejs', {title: "Login :: Clipit", curSession: req.session})
})
Log in post
//#ROUTE: Log in post
//#DESCRIPTION: Finds the user in the database, and logs him in (creates a new session)
app.post('/api/login', (req, res)=> {
const username = req.body.inputUsername
const password = req.body.inputPassword
userDB.findOne({username: username, password: password}, (err, result)=> {
if(err) {
throw err;
} else if(!result) {
res.redirect('/login-error', {curSession: req.session})
} else {
req.session.username = result.username
req.session.loggedIn = true
console.log(`${req.session.username} has logged in.`)
res.redirect('/')
}
})
})
Bug:
express deprecated res.redirect(url, status): Use res.redirect(status, url) instead server.js:185:17
node:events:346
throw er; // Unhandled 'error' event
^
RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: { curSession: [Session] }
at new NodeError (node:internal/errors:329:5)
at ServerResponse.writeHead (node:_http_server:282:11)
at ServerResponse.writeHead (C:\Users\gabri\Desktop\clipit\node_modules\on-headers\index.js:44:26)
at ServerResponse._implicitHeader (node:_http_server:273:8)
at writetop (C:\Users\gabri\Desktop\clipit\node_modules\express-session\index.js:276:15)
at ServerResponse.end (C:\Users\gabri\Desktop\clipit\node_modules\express-session\index.js:356:16)
at ServerResponse.redirect (C:\Users\gabri\Desktop\clipit\node_modules\express\lib\response.js:951:10)
at C:\Users\gabri\Desktop\clipit\server.js:185:17
at C:\Users\gabri\Desktop\clipit\node_modules\mongoose\lib\model.js:4870:18
at processTicksAndRejections (node:internal/process/task_queues:76:11)
Emitted 'error' event on Function instance at:
at C:\Users\gabri\Desktop\clipit\node_modules\mongoose\lib\model.js:4872:15
at processTicksAndRejections (node:internal/process/task_queues:76:11) {
code: 'ERR_HTTP_INVALID_STATUS_CODE'
}
PS C:\Users\gabri\Desktop\clipit>
There is one error and one warning in your console:
The Warning : express deprecated res.redirect(url, status): Use res.redirect(status, url) instead server.js:185:17.
This is about the order of the params. RIght now they would work in any order. But this also gives you a hint as to what is causing the error. The params for res.redirect() are supposed to be status and url.
You cannot add an object to the params. It has to be a status code and a path.
You might want to do res.render('/login-error', {curSession: req.session}) (depending on your views) or simply res.redirect('/login-error').
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);
...
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.
using nodejs v. 7.5.0 I'm getting
UnhandledPromiseRejectionWarning && DeprecationWarning
I know its part of new features since node 6.6 but the thing I don't understand is that I'm catching that promises just after caching it into a variable. If I don't cache it no warning is thrown.
this is the code that throws the error:
let verifyPromise = verifyToken(id_token);
verifyPromise.catch((err) => {
log(err);
});
let verifyOkPromise = verifyPromise.then((login) => {
return DB_API.getTokenById(id_token);;
});
verifyOkPromise.catch((err) => {
log('error in finding token: ', err);
});
verifyOkPromise.then((dbRes) => {
log('loggin res in finding token: ', dbRes);
});
where verifyToken() is a function that checks google auth token and returns a promise.
node output is the following:
error in finding token: { CouchbaseError message: 'The key does not exist on the server', code: 13 }
(node:10961) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CouchbaseError: The key does not exist on the server
(node:10961) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
as you can see the promise error branch is regularly catched as it logs as expected but I still get the warning!!!!
while if I just append the catch like this:
verifyPromise.then((login) => {
return DB_API.getTokenById(id_token);;
}).catch((err) => {
log('error in finding token: ', err);
});
NO Warning is given!!!
I think I'm missing something subtle but I don't understand what could be.
Some one have any clues?
thanks in advance
This issue is happening because it is not the original promise that ultimately fails, it is the then()! You are not storing the result of
verifyOkPromise.then((dbRes) => {
log('loggin res in finding token: ', dbRes);
});
It is the call to then() here that ultimately triggers the resolution of the Promise chain. You're trying to handle the catch from the original call, but it's the chain that fails. Here are three simplified versions of what you're trying to do. The first matches your workflow. The second matches how these are normally written (but a pattern you didn't want to use). The third rewrites your pattern to work properly but using results assigned to variables.
let Promise = require('bluebird');
function forceReject() {
return Promise.reject('deliberately failed');
}
let p = forceReject('12345');
// This will produce an Unhandled Rejection error
/*
p.then(res => { console.log('succeeded: ', res); });
p.catch(err => { console.log('failed: ', err); });
*/
// This will work but it's not the pattern you prefer
/*
p.then(res => {
console.log('succeeded: ', res);
}).catch(err => {
console.log('failed: ', err);
});
*/
// This will also work! Note the assignment of the result of p.then()...
let q = p.then(res => { console.log('succeeded: ', res); });
q.catch(err => { console.log('failed: ', err); });
You're using Node 7.5, you should consider async/await. Chad's answer is well written and correct, but here's how I'd write the code in question:
async function whateverYoureDoing(id_token) {
const token = await verifyToken(id_token);
// catch will automatically propagate.
//You can use regular try/catch and if rethrow your own custom TokenInvalidError object
const dbRes = await DB_API.getTokenById(id_token);
console.log('loggin res in finding token: ', dbRes);
}
Note: You no longer need to add .catch and log error with promises - that's the point of the unhandledRejection tracking feature :) Logging should be generally done at a top level since promises work great with exceptions anyway.
As part of an Node.js/Express API I am developing, I have built a custom error handler that lets me throw specific errors when needed e.g. BadRequest or NotFound etc.
The problem occurs when I wish to throw within a promise chain. For example:
db.doSomethingAsync().then(data => {
if(data.length === 0){
throw new errors.ResourceNotFound({ resource: "foo" });
}
});
After much reading on the topic, I see that this error will be swallowed by the promise and hence causes an unhandled Promise rejection.
I am aware I can reject however im not sure how I can then handle the specific error (rather than a catchall on reject which I dont want).
Also, to reject, would I not need to create a new promise inside of my promise chain? That feels messy.
Can anyone advise how to handle throwing a specific exception within a promise chain?
In addition to the first callback to then() which is resolve callback and recieves data, you could also provide a second callback to it which is reject callback and it recieves errors. so you could catch this specific error on the second callback of the next then:
db
.doSomethingAsync()
.then(data => {
if(data.length === 0){
throw new errors.ResourceNotFound({ resource: "foo" });
}
})
.then(massagedData => {
// work with massagedData here
}, err => {
// handle err here which is previous thrown error
// assert.ok( err instanceof errors.ResourceNotFound() )
})
.catch(err => {
// this will catch unhandled errors if any
});
then() by default returns a promise and if any of its callbacks ( whether the first or the second one ) throws an error then it returns a rejected promise and its reason will caught by next reject callback if any or by catch at the end. So you don't need to create a new promise.
See Promise.prototype.then()