Im trying to update the req.conversation before its handled by another function called read. I know the middleware is being called before read but when I log the req.conversation object in read it doesnt reflect the updates made in the middleware.
/**
* Conversation middleware
*/
exports.conversationByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Conversation is invalid'
});
}
Conversation.findById(id).populate('user', 'displayName').populate('readers', 'displayName').exec(function(err, conversation) {
if (err) return next(err);
if (!conversation) {
return res.status(404).send({
message: 'Conversation not available'
});
}
req.conversation = conversation;
next();
});
};
Where is the id parameter in your middleware callback coming from? If it's a url param (e.g. /conversations/:id) it should be req.params.id.
Related
I have a route that goes to the index page. I have a secret token that allows access to this page. I want to compare the requested URL with a custom string. The current URL in use is http://localhost:3000/?token=secret but if I enter http://localhost:3000/as?token=secret it doesn't render the 404 error page that I created, instead says Cannot GET /as. I wondering how to validate this and render out the error page correctly
app.get('/', (req, res) => {
console.log(req.url); // /?token=secret
if (req.url !== `/?token=${websocket_token}`) {
res.render('error', {
title: '404 Not Found',
errorMessage: '404 Not Found'
});
return;
}
});
In Express each app.get or other related method handles it's own route. So when you do app.get('/' you are only matching routes that are / not /as.
You could change it to * to match all routes. Maybe like the following?
app.get('*', (req, res) => {
console.log(req.url); // /?token=secret
if (req.url !== `/?token=${websocket_token}`) {
res.render('error', {
title: '404 Not Found',
errorMessage: '404 Not Found'
});
return;
}
});
Or of course you could have a dedicated section for your 404 messages.
app.get('/', (req, res, next) => {
console.log(req.url); // /?token=secret
if (req.url !== `/?token=${websocket_token}`) {
return next();
}
// Valid request
});
app.get('*', (req, res) => {
res.render('error', {
title: '404 Not Found',
errorMessage: '404 Not Found'
});
});
At the end of the day there are so many ways you can handle Express routing. It's very powerful and flexible.
I'd suggest you look here under the How do I handle 404 responses? section for another idea as well.
Also, remember, having secrets in a URL like that, probably isn't the most secure thing. So there are a lot of reasons I wouldn't suggest this for security reasons. But just answering your question, the above should work.
The idiomatic way to handle 404s in Express is to register your final route handler with the use method rather than using one of the HTTP-specific methods.
app.use((req, res) => {
res.render('error', {
title: '404 not found',
errorMessage: '404 not found'
})
})
I emphasize the word final because use registers a catch-all handler, so this will override any route that it precedes in your code. If all your other routes are registered before this, then this will catch any request that has not matched any other route – regardless of the HTTP method that was used. So this will apply to any GET, POST, PUT, DELETE request.
An even more idiomatic way in Express to handle 404s (and all HTTP error responses) is to use the next argument that comes with all route handlers. This will re-route the request to the next handler that specifically takes an error as it's first argument:
app.use((req, res, next) => {
const error = new Error('404 not found')
error.statusCode = 404
next(error)
})
app.use((error, req, res, next) => {
res.status(error.status || 500)
res.render('error', {
title: error.message,
errorMessage: error.message
})
})
This is great because you now have a generic error handler, which you can access from inside any other route. So this will handle not only 404s, but also 401s, 403s, 503s, anything you want that doesn't render successfully for the user. And you can access this route simply by calling next with an error as the first argument from inside any other route handler.
I suggest you to use passport-auth-token for validating the token & display success or error pages.
Configure Strategy
The token authentication strategy authenticates users using a token. The strategy requires a verify callback, which accepts these credentials and calls done providing a user.
passport.use('authtoken', new AuthTokenStrategy(
function(token, done) {
AccessToken.findOne({
id: token
}, function(error, accessToken) {
if (error) {
return done(error);
}
if (accessToken) {
if (!token.isValid(accessToken)) {
return done(null, false);
}
User.findOne({
id: accessToken.userId
}, function(error, user) {
if (error) {
return done(error);
}
if (!user) {
return done(null, false);
}
return done(null, user);
});
} else {
return done(null);
}
});
}
));
Authenticate Requests
Use passport.authenticate(), specifying the 'authtoken' strategy, to authenticate requests.
For example, as route middleware in an Express application:
app.post('/login',
passport.authenticate(
'authtoken',
{
session: false,
optional: false
}
),
function(req, res) {
res.redirect('/');
}
);
I know the difference between a callback and a middleware next() function.
If a write a custom remote-method in Loopback, I can easily send errors in callback e.g callback(error,null) but in remote hooks or observers whenever I send error in next() function e.g
var err = new Error('This is error');
next(err)
it always says that Internal server error but it does not tell me what error is.
In order to view error I have to login to server and view logs.
Please tell me how can I send error as a response in next() function so that the on frontend I know what error has occurred.
Maybe use a middleware to hook in:
app.use( function(err,req,res){
res.json(err);
});
(This needs to he the last middleware defined...)
basically you can define callbacks with err and result.
For example in loopback,
if I have a model call "Action" you can simply send err or result to front end using json.
app.get('/your/api/call', function (req, res, next) {
var getTeam = function (cb) {
app.models.Team.find({}, function (err, teams) {
if (err) {
cb(err);
} else {
cb(null, teams);
}
});
};
async.waterfall([
getTeam
], function (err, team, role) {
if (err){
res.send(err); //send error to front end
} else {
res.send(team); //send result to front end
}
});
});
This approach can use with "app.use" function in root level also.
I'm using Node.js/Express.js to install data to my MySQL DB.
Inserting data works fine, but returning success / fail gives me an error.
TypeError: Cannot read property 'status' of undefined
This is my code:
var crud = {
newProject: function (req, res, callback) {
db.query('INSERT INTO projects SET ?', req.body, function(err, res) {
// This is where it fails
if(err){
return res.status(500).json({error: err});
} else {
return res.status(200).json({success: 'Insert row success'});
}
});
},
}
// Express routing
app.post('/project/*', crud.newProject);
What am I not getting right here?
Solution
So this is what I used to make it work (after changing 'res' to 'resp' as suggested):
if (err) throw err;
res.end(JSON.stringify({response: 'Success'}));
Your defining res twice. The express response object is getting overwritten by the data param in your node callback.
Try the following (see comment)
var crud = {
newProject: function (req, res, callback) {
// changed 'res' to 'resp' to avoid collision with Express' 'res' object
db.query('INSERT INTO projects SET ?', req.body, function(err, resp) { // here's your error
// This is where it fails
if(err){
return res.status(500).json({error: err});
} else {
return res.status(200).json({success: 'Insert row success'});
}
});
},
}
// Express routing
app.post('/project/*', crud.newProject);
If you define error-handling middleware functions after the last app.use() in your main configuration
app.use(function (err, req, res, next) {
res.status(500).send(err.message || 'Internal server error.')
})
You can use the next callback as a catchall error handler, so the above would then become
var crud = {
newProject: function (req, res, callback) {
db.query('INSERT INTO projects SET ?', req.body, function(err, resp) {
if (err) return callback(err);
return res.json({success: 'Insert row success'});
});
},
}
// Express routing
app.post('/project/*', crud.newProject);
res.json() by default should add a 200 Success code to the response header. Ideally you would want to inspect the resp data param from the node callback after checking the state of err to properly handle the response and proceed accordingly, especially if you are dealing with last evaluated records associated with a continuation token usually provided in the response which some DBALs and APIs do for you and some don't. Either way you will want to be sure additional recursion isn't necessary to fetch remaining records before responding successfully.
Looks like the res object is undefined as it is not returning any response after the insert. You may return a new object like:
return {
status: 200,
json: {success: 'Insert row success'}
}
I'm building an express js api with passport js, and in order to be able to return custom error messsages formatted as json I'm using custom callbacks.
When I provide an unknown email the custom callback I wrote is called 3 times, resulting in Unhandled rejection Error: Can't set headers after they are sent.. Which makes sense.
Any help is appreciated.
Here is my implementation:
Strategy:
const localLoginStrategy = new LocalStrategy({
usernameField: "emailAddress"
}, (emailAddress, password, done) => {
// Called once
User.findOne({
where: { emailAddress }
}).then((existingUser) => {
// Called once
if (!existingUser) { return done(null, false, { message: "Invalid email/password combination", status: 401 }); }
return existingUser.comparePassword(password);
}).then((userData) => {
return done(null, userData);
}).catch((err) => {
return done(null, false, { message: "Invalid email/password combination", status: 401 });
});
});
passport.use(localLoginStrategy);
Express middleware for authentication using custom callback:
const requireUsernamePassword = (req, res, next) => {
if(!req.body.emailAddress || !req.body.password) {
return res.status(400).json({ message: "No emailAddress and/or password provided" });
}
// Called once
passport.authenticate("local", { session: false }, (err, user, info) => {
// Called three times!
console.log("authenticate callback")
if (!user || err) {
return res
.status(info.status || 400)
.json({ message: info.message || "Authentication error" });
}
req.user = user;
return next();
})(req, res, next);
};
To check your mandatory request body fields create one generic middleware that will check required field and return appropriate return code. Just like below.
module.exports = function checkParams(params) {
params = params || [];
return function(req, res, next) {
var valid = true;
if(Array.isArray(params)) {
params.forEach(function(_param) {
valid = valid && !!req.body[_param];
});
}
if (valid) { next() } else {return res.status(400).end();} //this is for missing required parameters
};
};
Now lets say for example you have two APIs. Login and CreateUser. API routes should looks like below
app.post('/Login', checkParams(['emailAddress', 'password']), passport.authenticate('local', { failureRedirect: '/login' }), actualLoginMethod);
app.post('/CreateUser', checkParams(['userName', 'Phone']), passport.authenticate('local', { failureRedirect: '/login' }), actualCreateUserMethod);
If either of these parameter (userName and Phone in /CreateUser + emailAddress and password in /Login) is missing then it will return 400 status and stop execution from that point, you may change the checkParams's logic as per your need.
If required parameters are available then it will check JWT local strategy. Once request is through to both the check points then it will call actual method.
Hope this might help you.
You are calling the done function multiple times.
I believe when you call return done(...) in the then method, the next then will call done again.
So that's why your callback function from requireUsernamePassword has been called more then one time.
Hope it helps.
In my mean js app i have an account model and corresponding routes and controllers. To remove a specific account i need to have authorization and I need to be logged in.
All users can however list all accounts, I only wont to list the accounts created by the specific user. So i need to add autorization to the list part of the code.
I update the routes for app.route('/accounts') with users.requiresLoginandaccounts.hasAuthorization as shown below:
module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller');
var accounts = require('../../app/controllers/accounts.server.controller');
// Accounts Routes
app.route('/accounts')
.get(users.requiresLogin,accounts.hasAuthorization,accounts.list)
.post(users.requiresLogin, accounts.create);
app.route('/accounts/:accountId')
.get(users.requiresLogin, accounts.hasAuthorization,accounts.read)
.put(users.requiresLogin, accounts.hasAuthorization, accounts.update)
.delete(users.requiresLogin, accounts.hasAuthorization, accounts.delete);
// Finish by binding the Account middleware
app.param('accountId', accounts.accountByID);
};
Now I get an errror since req is not provided with user.
GET /modules/accounts/views/list-accounts.client.view.html 304 8.266
ms - - TypeError: Cannot read property 'user' of undefined
at exports.hasAuthorization (/Users/david/Repositories/budget/app/controllers/accounts.server.controller.js:103:17)
So I imagine i need to update the accounts.server.controller somehow. The delete account does provide an account in the req, so that only the creator can delete as I mentioned earlier. How do I update the code so that the "List of accounts" part work and list only the accounts belonging to that specific user?
/**
* Delete an Account
*/
exports.delete = function(req, res) {
var account = req.account ;
account.remove(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(account);
}
});
};
/**
* List of Accounts
*/
exports.list = function(req, res) {
Account.find().sort('-created').populate('user', 'displayName').exec(function(err, accounts) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(accounts);
}
});
};
/**
* Account middleware
*/
exports.accountByID = function(req, res, next, id) {
Account.findById(id).populate('user', 'displayName').exec(function(err, account) {
if (err) return next(err);
if (! account) return next(new Error('Failed to load Account ' + id));
req.account = account ;
next();
});
};
/**
* Account authorization middleware
*/
exports.hasAuthorization = function(req, res, next) {
if (req.account.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
The account client service only contains the basic stuff:
//Accounts service used to communicate Accounts REST endpoints
angular.module('accounts').factory('Accounts', ['$resource',
function($resource) {
return $resource('accounts/:accountId', { accountId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
And the user object is not mentioned in the controller.
The accounts.hasAuthorization assume it get executed after the accounts.accountById ,on your current configuration req.account will be undefined.
I'm assumming that somewhere in your account model you have:
user: {
type: Schema.ObjectId,
ref: 'User'
}
If you want the user only have access only to the accounts he/she owns :
Change accounts.list route only to requires Login and this gives us access to the req.user :
app.route('/accounts')
.get(users.requiresLogin,accounts.list)
Change the exports.list in the accounts controller:
exports.list = function(req, res) {
Account.find({user: req.user._id}).sort('-created')
.... //
};