In my main express.js config file, I use two custom error middleware functions:
const error = require('../middlewares/error');
// catch 404 and forward to error handler
app.use(error.notFound);
// if error is not an instanceOf APIError, convert it.
app.use(error.converter);
I use boom to unify error messages. this is my error middleware:
module.exports = {
/**
* Error responder. Send stacktrace only during development
* #public
*/
responder: (err, req, res, next) => {
res.status(err.output.payload.statusCode);
res.json(err.output.payload);
res.end();
},
/**
* If error is not a Boom error, convert it.
* #public
*/
converter: (err, req, res, next) => {
if (env !== 'development') {
delete err.stack;
}
if (err.isBoom) {
return module.exports.responder(err, req, res);
}
if (err.name === 'MongoError' && err.code === 11000) {
const boomedError = boom.conflict('This email already exists');
boomedError.output.payload.stack = err ? err.stack : undefined;
return module.exports.responder(boomedError, req, res);
}
const boomedError = boom.boomify(err, { statusCode: 422 });
return module.exports.responder(boomedError, req, res);
},
/**
* Catch 404 and forward to error responder
* #public
*/
notFound: (req, res) => {
const err = boom.notFound('Not Found');
return module.exports.responder(err, req, res);
},
};
My problem is, when I make a "register" action with an existing email, the responder() is executed three times. One for my boom.conflict error, but then also one for "not found". (even though I've done res.end().
This is the register logic:
exports.register = async (req, res, next) => {
try {
validationResult(req).throw();
const user = new User(req.body);
const token = generateTokenResponse(user, user.token());
const userTransformed = user.transform();
user.tokens.push({ kind: 'jwt', token });
user.activationId = uuidv1();
await user.save();
res.status(httpStatus.CREATED);
sendValidationEmail(user.activationId);
return res.json({ user: userTransformed });
} catch (err) {
return next(converter(err, req, res, next));
}
};
user.save() triggers this by the way:
userSchema.pre('save', async function save(next) {
try {
if (!this.isModified('password')) return next();
const rounds = env === 'test' ? 1 : 10;
const hash = await bcrypt.hash(this.password, rounds);
this.password = hash;
return next();
} catch (err) {
return next(converter(err));
}
});
Calling res.end() just tells the response stream that you're done sending data. You don't need that in this case because calling res.json() will do it for you.
However, that isn't the same as telling Express that you're done with handling the request. Just because you've sent a response doesn't necessarily mean you've no work left to do.
The way you tell Express that you've finished is by not calling next(). Express assumes you've finished by default and will only continue executing the middleware/routing chain if you call next().
So this line:
return next(converter(err, req, res, next));
should just be:
converter(err, req, res, next);
Likewise your other call to converter() shouldn't be calling next() either.
Related
I a trying to implement a rest API for our project then I go for node js and express. I have built all the models and controllers. I faced an issue while trying to handle an error. Errorhandler function doesn't receive all the properties of error that caught in try/catch block. I can not read its name in a handler but I can use its name in the controller. Could you please help me?
const errorHandler = (err, req, res, next) => {
console.log(`Error in method:${req.method}: ${err.stack}`.bgRed);
let error = { ...err };
console.log(`Error handler: ${err.name}`);
res.status(error.statusCode || 500).json({
success: false,
data: error.message || 'Server Error',
});
};
module.exports = errorHandler;
controller
const mongoose = require('mongoose');
const Product = require('../models/Product');
const ErrorResponse = require('../utils/error');
const routeName = 'PRODUCT';
// #desc getting single product via id
// #route GET api/v1/products
// #acces public
exports.getProdcut = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return next(
new ErrorResponse(`Product not found with id:${req.params.id}`, 404)
);
}
res.status(200).json({
success: true,
data: product,
});
} catch (err) {
console.log(err.name);
console.log('ERRO APPEND');
next(new ErrorResponse(`Product not found with id:${req.params.id}`, 404));
}
};
Assuming that errorHandler is part of your middleware that is somewhere after getProdcut, you can try just throwing the error and Express will automatically detect that for you, because error handling middleware such as yours accepts 4 parameters. So the following would work:
const getProdcut = async (req, res, next) => {
try {
// ...
} catch (err) {
throw err;
}
};
const errorHandler = (err, req, res, next) => {
if (err) {
console.log('hello from the error middleware');
console.log(err.name);
}
else {
// next() or some other logic here
}
}
app.use('/yourRoute', getProdcut, errorHandler);
And inside of your errorHandler you should have access to the error object.
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors.
https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
I am trying to pass data from one middleware to another. The data is then returned to the client in the next middleware. I, however, am unable to catch it in the send.call.
How can I catch the data and send it?
Thank you all in advance.
const myPreMiddleware = async (req, res, next) => {
req.myData = myData;
next();
};
const myPostMiddleware = (req, res, next) => {
let send = res.send;
res.send = async function (body) {
send.call(this, req.myData); // The data i NOT accessible here
};
console.log("req.myData : " + JSON.stringify(req.myData)); // The data is accessible here
next();
};
app.use(myPreMiddleware);
app.use(myPostMiddleware);
Try pass the variable that you want to pass to the next() callback
here some example that will hopefully help
function notFound(req, res, next) {
res.status(404);
const error = new Error(`Not Found - ${req.originalUrl}`);
next(error);
}
function errorHandler(err, req, res, next) {
const statusCode = res.statusCode !== 200 ? res.statusCode : 500;
res.status(statusCode);
res.json({
message: err.message
});
}
app.use(notFound);
app.use(errorHandler);
Test
it('should fail trying to GET bookmarks with false user id',async () => {
try {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
} catch (e) {
console.log(e); //it doesn't reach this point
expect(e.httpStatus).to.equal(HttpStatus.UNAUTHORIZED);
}
});
The relevant part of the method under test:
/* GET bookmark of user */
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
userIdTokenValidator.validateUserIdInToken(request);
...
}));
where wrapAsync makes sure the error is passed to the custom error handler:
let wrapAsync = function (fn) {
return function(req, res, next) {
// Make sure to `.catch()` any errors and pass them along to the `next()`
// middleware in the chain, in this case the error handler.
fn(req, res, next).catch(next);
};
}
The validateUserIdInToken method which causes the method under test to throw an exception:
const AppError = require('../models/error');
const HttpStatus = require('http-status-codes');
let validateUserIdInToken = function (request) {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
throw new AppError(HttpStatus.UNAUTHORIZED, 'Unauthorized', ['the userId does not match the subject in the access token']);
}
}
module.exports.validateUserIdInToken = validateUserIdInToken;
and the custom error handler in the root middleware:
app.use(function(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
if(err instanceof AppError) { //execution lands here as expected and the test stops...
res.status(err.httpStatus);
return res.send(err);
} else {
res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR);
res.send({
message: err.message,
error: {}
});
}
});
I think you may be approaching this incorrectly. Invalid auth should not raise errors in the app - it's not an error really, is a validation issue.
If the auth fails, simply send the relevant http error code - 401 back to the client.
res.send(HttpStatus.UNAUTHORIZED, 'a message if you want'); // 401
In your route handler:
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
return response.send(HttpStatus.UNAUTHORIZED);
}
...
}));
In your test, check the for status 401:
chai.request(server)
.get('/false_user_id/bookmarks')
.end((err, result) => {
if (err) {
return callback(err);
}
result.should.have.status(401);
});
Thanks to #laggingreflex's comment I missed debugging that the response actually returned with the expected status and error message
The adjusted test case now looks like this:
it('should fail trying to GET bookmarks with false user id',async () => {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
expect(response.status).to.equal(HttpStatus.UNAUTHORIZED);
});
I need help with comparing the returned value in async function. I always have "Promise { }". I already tried different approaches with different results, but never what I need. This is the code that I have so far.
async function isType(username) {
const result = await db.query('select * from users where username=?', [username])
return await result[0].type;
}
module.exports = {
isLoggedIn(req, res, next) {
if (req.isAuthenticated() && isType(req.user.username)==0) {
return next();
}
return res.redirect('/');
}
};
I also tried instead something like this:
isLoggedInAdmin(req, res, next) {
isType(req.user.username).then(result => {
if (req.isAuthenticated() && result==0) {
return next();
}
})
return res.redirect('/');
}
But in this case, the error is "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client"
Any help is much appreciated. Thanks.
You could move the return res.redirect('/'); inside the then callback
isLoggedInAdmin(req, res, next) {
isType(req.user.username).then(result => {
if (req.isAuthenticated() && result == 0) {
return next();
}
return res.redirect("/");
});
}
I'm trying to setup a simple system for rendering user-input-errors and stopping propogation in express, this is what I've got so far:
routingFunction= (req, res, next) {
//setting up a test in express-validator
var test = req.assert('access_token', 'required').notEmpty();
isValid(test, next);
//non error stuff
}
isValid = (tests, next) => {
//some more code here, that checks if any errors were found and save tem to the array errors.
if(errors.length > 0){
return next(new Error());
}
};
//a middleware that catches errors:
app.use((err, req, res, next) => {
res.json('error').end();
});
My problem with this is that it doesn't stop the propagation when I'm calling Next(new Error());, I could return true/false from isValid and then return next(new Error()), but that would add a lot of bloat to my controllers, is there some better way to do it from within the helper function?
In main route file, e.g. routes/index.js
// Set of func(req, res, next)
let v = require('validator'); // middleware-validators
let user = require('user'); // routes for user
...
app.get('/smth-route-of-user', v.isTokenSet, v.isEmail, ..., user.get)
In middlewares/validator.js
let v = require('validator-func-list');
...
exports.isTokenSet = function (req, res, next) {
if (v.isValid(req.body.token))
next(); // Forwarding to next middleware. In our route next() called v.isEmail
else
next(new Error('Token is empty')); // Stop route chain and call error-middleware;
}
exports.isEmail = function (req, res, next) {
...
You can join validators to one, e.g. checkUser(), and use only one in route.
In middlewares/errorHandler.js
module.exports = function (err, req, res, next) {
let msg = err.message; // Here we see 'Token is empty';
if (req.xhr)
res.json(msg);
else
res.render('error_page.html', {message: msg, ...});
// Here we can call next(err) to forwarding error to next errorHandler. In example below it was errorHandler2.
}
In app.js don't forget to attach error-middleware to application.
app.use(require('middlewares/errorHandler'));
app.use(require('middlewares/errorHandler2'));
If you need collect errors then validator must push error to req.errors (or another field as you want) and call next() without error. In render middleware you simple check req.errors.length and show normal or error page.
Code after isValid(test, next); always execute. Code below block it, but imho is dirty.
routingFunction = (req, res, next) {
var test = req.assert('access_token', 'required').notEmpty();
if (!isValid(test, next))
return; // callback called by isValid. It's dust.
//non error stuff
...
next(); // callback must be called
}
isValid = (tests, next) => {
if(errors.length > 0){
next(new Error());
return false;
}
return true;
};
More better use like this
routingFunction = (req, res, next) {
var test = req.assert('access_token', 'required').notEmpty();
if (!isValid(test))
return next (new Error('error description'));
//non error stuff
...
next();
}