Express Flash Message over multiple redirects - node.js

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");
}
})

Related

Express JS - Redirecting If MongoDB id doesn't exist / Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Within my Posts routes, i'm making it so that the user can go to posts/:id to retrieve information about the post with that ID, however i'm trying to make sure that if the user goes to a post that doesn't exist, he gets redirected back to the posts index route. My issue is that the server is going through the entire router.get function instead of redirecting the user and stopping.
router.get("/:id", async (req, res) => {
if (!mongoose.isValidObjectId(req.params.id)) {
console.log("Test1")
res.redirect("/")
res.end()
}
console.log("Test2")
try {
console.log("Test3")
const post = await Post.findById(req.params.id)
if (post == null) res.redirect("/")
res.render("posts/show", { post: post })
}
catch {
console.log("Test4")
res.redirect("posts/index")
res.end()
}
})
If I go to the route posts/weiqeiqwie (which isn't valid) it prints all of the test console logs. What can I do for the code to return after the error?
I'm still a bit new to using express, so sorry for the dumb question.
you cant redirect AND render at the same time.
this is what this part of the code does when post is null :
if (post == null) res.redirect("/")
res.render("posts/show", { post: post })
adapt it like this, so that now when post is null it only does the redirect part.
(never forget the return)
if (post == null) return res.redirect("/");
res.render("posts/show", { post: post })
the same goes for the redirect line 3 :
it should be :
if (!mongoose.isValidObjectId(req.params.id)) {
console.log("Test1")
return res.redirect("/")
}
also res.end() is not required. you can remove it from everywhere.

res.sendFile() not sending file (no errors)

I have a simple login page using passport.js.
$("#loginUser").on("click", function () { //login button handler
var User = {
username: $('#username').val().trim().toLowerCase(),
password: $("#password").val().trim()
};
//$("#password").val("");
$.post("/login", User);
});
The code for the post route for "/login" is below (taken from passport docs):
app.post('/login', function (req, res, next) {
passport.authenticate('local', function (err, user) {
if (err) { return next(err); }
if (!user) { return res.redirect('/'); }
req.logIn(user, function (err) {
if (err) { return next(err); }
return res.redirect('/home/' + user.user_id);
});
})(req, res, next);
});
Below is the code for the "home" route:
app.get("/home/:user?", function (req, res) { //home page
console.log(req.params.user)
if (req.user) {
console.log(path.join(__dirname, "../public/home.html"))
res.sendFile(path.join(__dirname, "../public/home.html"), (err) => {
if (err) {
console.log(err);
} else {
console.log('file sent!');
}
});
}
else {
console.log("no user");
res.sendFile(path.join(__dirname, "../public/login.html"));
}
});
The issue I'm having is when the page is supposed to redirect to the home page, it just does nothing. The console.log("file sent") will trigger with no errors, even though nothing happens and the page stays on the login screen. However, if I physically type in localhost://8080/home the page will then load perfectly fine.
It's not a matter of your server not sending the file - it's a matter of your web page isn't redirecting properly.
Ajax calls which you are using with this code:
$.post("/login", User);
do not redirect automatically. All they do is return the 302 response to your Ajax completion handler Javascript (which you don't have any code for). So, your request is delivered to the server. Your server sends a 302 redirect response, that 302 response comes back to your Javascript and gets dropped on the floor by your Javascript.
Only URLs put in the browser bar or URLS clicked on by the user or browser-only form posts (not done from Javascript) will redirect automatically. Ajax calls do not redirect automatically - they just deliver the redirect status and location back to your Javascript where your Javascript has to then process it in order to do anything.
You have a couple of choices to fix:
Switch to a plain browser form post with no Javascript and then the browser itself will see and follow the redirect.
Use a completion handler for your Ajax call, check the status and if it's a 3xx response, then get the Location header and set window.location = xxx to the location in that header to cause the redirect to happen.
I haven't tested this, but here's the general concept for checking the status and manually redirecting based on a 3xx status.
$.post("/login", User).then((data, textStatus, jqXHR ) => {
if (jqXHR.status >= 300 && jqXHR.status <= 399) {
window.location = jqXHR.getResponseHeader("Location");
} else {
console.log("not a redirect status of:", jqXHR.status);
}
});

Bypassed authentication in route handling

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"));

Express redirect error: can't set headers after they are sent

When this code hits the redirect line it throws the 'Can't set headers after they are sent error' and doesn't redirect. I'm guilty of not fully understanding headers and how express works with them. This link about this error is confusing me a bit, probably because I don't have a basic enough understanding of what's going on. Also, I know this is a bit of a naive approach to authenticating, but I'm just trying to get basic things to work.
app.post('/api/login', function(req, res) {
if (req.body.password === auth.password) {
auth.date = new Date()
res.redirect('/admin')
} else {
console.log("wrong pw")
}
})
UPDATE : thank you to #Brendan Ashworth I missed an obvious else, which I've added now and no longer get the error.
However this line doesn't change the contents of my page
res.sendfile('./public/admin/views/tunes.html')
It worked before I wrapped it with the auth check
var auth = require('../config/auth')
module.exports = function(app) {
/*
* CONTENT API
*/
//...
/*
* Admin Routes
*/
app.get('/admin/login', function(req, res) {
res.sendfile('./public/admin/views/login.html')
})
app.post('/api/login', function(req, res) {
if (req.body.password === auth.password) {
auth.date = new Date()
res.redirect('/admin')
} else {
res.json({message: 'Wrong password!'})
}
})
app.get('/admin', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/tunes.html')
console.log("test") //
} else { //added else
res.redirect('/admin/login')
}
})
app.get('/admin/:url', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/' + req.params.url + '.html')
} else { //added else
res.redirect('/admin/login')
}
})
// frontend routes
// route to handle all angular requests
app.get('*', function(req, res) {
res.sendfile('./public/views/index.html')
})
}
FINAL UPDATE!! The last thing I needed was to handle the redirect client side after sending the file. Simple authentication works perfectly now!
$http.post('/api/login', $scope.auth).success(function() {
window.location.href = '/admin'
})
An explanation of the error Can't set headers after they are sent error:
All HTTP responses follow this basic structure:
.. Response Line ..
.. Headers ..
.. Body ..
If you want to redirect a user, first the Response Line will be sent with a redirect code (lets say 300), then the Headers will be sent with a Location: xxx header.
Then, we can finally send a body (not in the case of a redirect, but in general). However - in the case with your code - you are sending a Body response then trying to redirect the user. Since the headers (and response line) have both already been sent (because you sent the body), it can't send more headers after the body.
An example of this in your code would be:
app.get('/admin', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/tunes.html')
}
res.redirect('/admin/login')
})
If I'm assuming right, you actually want to return after the res.sendfile() call. If auth.date is truthy, then you'll be sending a file (i.e. body response) and then giving a redirect code - that doesn't work.
after redirect just call res.stop();

Express redirecting doesn't change req.url

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.

Resources