I have set up an application with a registration homepage and a few internal pages requiring a login.
I used Node with Express.js to set up the server and controlling the routes and authentication works fine: if I try to access localhost:port/clientPage I get the desired page if I previously logged in and an error message otherwise.
The problem is that if I try to access localhost:port/clientPage.html I get the clientPage even when I have no active session. How can I ensure the same - desired - behaviour previously described also in this case? I attach the code of my GET route to clientPage:
router.get('/clientPage', function (req, res, next) {
User.findById(req.session.userId)
.exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error('Not authorized! Go back!');
err.status = 400;
return next(err);
} else {
return res.sendFile(path.join(__dirname + '/../views/clientPage.html'));
}
}
});
});
Since the problem is caused by adding .html to the end of the route that somehow bypassed the authentication route. I think it is highly possible that you have
express.static(path.join(__dirname, "views") at the beginning of your application publicly serving your folder.
Why is it overriding your route?
Express is running middleware sequentially through app.use(...). The statement app.use(express.static...) is placed before app.use(// your router) and the response was resolved to client early.
Using this knowledge you can easily restrict other route by placing an authentication middleware before your route instead of embedding your database call inside each specific route.
app.use(require("./middleware/auth"));
app.use("/homepage", require("./routes/homepage"));
app.use("/clientPage", require("./routes/clientPage"));
Related
I have been using express-flash for a project and it has been working fine with redirects and page renders. However, I have a route /dashboard which redirects further into /dashboard/admin & /dashboard/staff as shown in the code below. Passing a message using req.flash and redirecting to /dashboard does not show up on the page. Other pages with single redirects are able to display the messages without any issue. I am guessing this problem is because of the second redirect from /dashboard to /dashboard/.*
req.flash('success_msg','Successful');
res.redirect("/dashboard");
in router.js:
app.get('/dashboard', (req, res) => {
if (req.user.role === "ADMIN") {
res.redirect("/dashboard/admin");
}
if (req.user.role === "STAFF") {
res.redirect("/dashboard/staff");
}
})
Is there a way to work around this issue, adding any statement in my router file to forward messages further into the redirects?
The flash message with value Successful is only available inside the req object in the middleware that handles the /dashboard endpoint. If you want to further redirect, you have to assign again the flash message to the next middleware.
app.get('/dashboard', (req, res) => {
// If there is any flash message with key success_msg,
// give it to the next middleware
if (res.locals.success_msg) {
req.flash('success_msg', res.locals.success_msg)
}
if (req.user.role === "ADMIN") {
res.redirect("/dashboard/admin");
}
if (req.user.role === "STAFF") {
res.redirect("/dashboard/staff");
}
})
this is a authentication sequence question. I currently have a website that is hosted with an Express back-end, which manages the routing for both my main website and the API points. For any of my routes, the request has to come with a posted OAuth token for the website, which corresponds to a specific GMail account.
I have a function which checks this token/corresponding email to be authorized to access my website (although auth function is not done, this is not the problem).
function validateUser(token) {
return new Promise(
(resolve, reject) => {
if (!token)
reject("Empty token");
request(
'https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + token,
(error, response, body) => {
console.log(body);
if (error) {
return reject("Error");
} else if (JSON.parse(body)['error_description']) {
return reject(JSON.parse(body)['error_description']);
} else if (body) {
let theDict = JSON.parse(body);
theDict['oauth_token'] = token;
return resolve(theDict);
}
return reject("some other error");
}
);
}
);
}
Then I have a route that manages get requests at any location (which then redirects to the login page, and once user is logged in it posts to the route with the token to get the content).
router.post(
'/*',
(req, res, next) => {
var token = req.body['token'];
validateUser(token).then(
(data) => {
request(
'http://localhost:4200',
(err, remoteResponse, remoteBody) => {
if (err) {
return res.status(500).end('Error');
}
res.send(remoteBody);
}
);
},
(err) => {
res.redirect('/login');
}
);
}
);
Now, the problem here is, whenever I go to the main page of my site (which reverse proxies back to the dev server), loading the index.html file works fine, but obviously loading everything else like "/assets/js" or default Angular 2 files does not work because they are not similarly authenticated.
Is there a way in Angular 2 to send these requests using the same authentication method? I have the oauth token being stored in my Angular 2 application once the index.html is loaded, so maybe I can use that somehow?
Alternatively, I can also setup another API route in Express to load the files from there, but when Angular 2 generates my index.html with the extra script tag for loading vendor.js or other files, can I tell it this new location instead?
Appreciate the help.
In express, you can use serve-static to serve static files, which would take precedence over your /* route. See https://expressjs.com/en/starter/static-files.html for more details.
Note: this assumes your static assets don't require authentication (which they usually don't).
app.use(express.static('public'))
Note: this should be above the block declaring the /* route in order for it to take precendence.
If you script.js file lives in the /public folder, it will be served at the root of the express server.
I am new to Express and Node, and when testing a protected REST endpoint in my app using Advanced REST Client the data is returned from the endpoint to the Client as expected, however the console logs
"Error: Can't set headers after they are sent"
which stops the server. Searching here on SO, this seems to occur when sending more than one response but I don't see that in my code:
router.get('/books', userAuthenticated, function (req, res, next) {
Book.find({}, function(err, docs){
if(err) {
res.send(err)
} else {
res.send(docs);
// next();
// return;
}
})
});
Is this error expected when sending a request/response to/from the Client or am I missing something in handling the error on the server?
Express is a node.js server that features a concept called "middleware", where multiple layers of code get a chance to operate on a request.
In that case, not only do you need to check that your function is not sending back a response twice, but you have to check that other middleware are not sending back a response as well.
In your case, the code indicates that middleware called "userAuthenticated" is being invoked before this function. You should check if that middleware is already sending a response.
I don't think the problem was in the middleware. It looks like I was calling done() twice in passport deserialize. I commented out the first instance and the problem disappeared in all my routes. Although, I am not sure if I should comment out the first or second instance but I'll work on that next.
passport.deserializeUser(function(obj, done) {
console.log(obj);
Account.findById(obj, function(err, user) {
console.log(user);
//done(err, user);
});
return done(null, obj);
});
I can't seem to wrap my head around how to properly handle errors.
The basic 404, is no problem (simply set header 404 and render 'not found' page). But let's say for example:
You find a user by id, but the user doesn't exist. I suppose for this you set the header-status to 500. But how do you redirect the page back (or simply assign a redirect page) and set a flashmessage?
In most tutorials I usually find the following:
model.SignUp.forge({id: req.params.id}).fetch({withRelated: ['usermeta']}).then(function(user) {
res.render('admin/pages/users/details', {title: 'Signups', error: false, details: user});
}).catch(function(err) {
res.status(500).json({error: true, data: {message: err.message}});
});
You simply catch the problem whenever an error occurs. I also come across this sometimes:
transporter.sendMail(mailOptions, function(err) {
if(err) {
req.flash('error', 'blablabla');
res.redirect('back');
}
});
In the first case you return a json file but no redirect or render. In the second part no status has been provided.
What practices do you guys implement?
I'm a huge fan of central error handling in my express apps. How does this work? Well, I have a library of HTTP error objects that all have a 'status' property on them. All my route handlers and middeware return a callback with one of those error objects depending on what happened, and do not call res.send (or any other res.* method) if there was an error. I then have an error handling middleware (or more than one, if I it's getting to be complex) that decides if I want to do a redirect or just send the response code, or whatever depending on the needs of the app.
Taking your example:
app.post('/signup', function(req, res, next){
model.SignUp.forge({id: req.params.id}).fetch({withRelated: ['usermeta']}).then(function(user) {
res.render('admin/pages/users/details', {title: 'Signups', error: false, details: user});
}).catch(function(err) {
return next(new HttpServerError(err));
});
}
an HttpServerError has a status of 500, and so I have at least a 'catch all' error handling middleware that looks like this (in the case of a json api):
app.use(function(err, req, res, next){
console.log(err.stack);
res.status(err.status).send({message: err.clientMessage});
});
You can also do multiple handlers, and render or redirect based on the state of the request (e.g. accepts headers or type of error).
For example, in a traditional web app, I might use the name of the error to figure out what template to render, and I might redirect to a login page if it's a 403 error.
For sake of completeness, here's an example HttpServerError:
'use strict';
const util = require('util');
function HttpServerError(message){
this.message = message;
this.clientMessage = 'Dangit! Something went wrong on the server!';
this.status = 500;
Error.captureStackTrace(this, NotFoundError);
}
util.inherits(HttpServerError, Error);
HttpServerError.prototype.name = 'HttpServerError';
module.exports = HttpServerError;
I have a route that redirects upon successful login
app.post('/login', function(req, res){
if(req.body.password === Server.cfg.auth.userPass) {
req.session.user = {nick: req.body.username, pass: req.body.password}
res.redirect('/chat')
} else {
res.render('user/login', { locals: { error: 'Invalid password' } })
}
})
The redirect seems to work as the page is refreshed with the correctly rendered jade file. However, the url still says /login and my pageTitle variable (being set through template vars) does not change either. If I refresh the page after the redirect, everything changes to the way it should be. It is only after the redirect that it does not change.
This has got to be a pretty common mix up for folks trying to deal with ajax redirects coming from a server controlled development background. My example shows what happens if authorization fails, slightly different; but you can use the same concept of intercepting the response and checking status, etc., and let the client JavaScript do the redirect.
My client code is actually a backbone model but in turn is calling jquery's ajax like:
model.save({ error:function...
Server
function requiresLogin(req, res, next) {
if(req.session.user) {
next();
} else {
//res.redirect('/sessions/new?redir=' + req.url); // won't work
res.send("Unauthorized", 403); // send 403 http status
}
}
Client
// Assumes you can get http status from response
error: function(resp, status, xhr)...
if(resp.status === 403) {
window.location = '/sessions/new'; // optionally put a redirLastPage=somewhere
}
This works as desired for me. I'd also suggest googling ajax post redirects to see why this
Looks like this is a jQuery problem. At least it was for me. You can override it with rel=external. More info at http://jquerymobile.com/demos/1.1.0/docs/pages/page-navmodel.html.