Nodejs- Cannot set headers after they are sent to the client - node.js

I'm currently designing a oauth login system and I've encountered the following problem. I'm trying to redirect users back to the homepage once they have been logged in and session data has been set, but the res.redirect('/') throws NodeError: Cannot set headers after they are sent to the client. I cannot seem to get it to work. Below is the code which is causing the fault:
app.post(
"/auth/openid/return",
passport.authenticate("azuread-openidconnect", {
failureRedirect: "/login"
}),
function(req, res) {
let userProperties = req.user;
new Promise((resolve, reject) => {
MongoClient.connect(url, function(err, db) {
if (err) next(err);
let dbo = db.db("Lektier");
let query = {
oid: req.user.oid
};
dbo.collection("users").findOne(query, function(err, result) {
db.close();
if (result) {
let type = result.type;
resolve(type);
}
});
});
}).then(type => {
req.session.regenerate(function() {
req.session.user = userProperties.upn;
if (type == "teacher") {
req.session.teacheruser = userProperties.upn;
}
let names = userProperties.displayName.toString().split(" ");
req.session.FirstName = names[0];
req.session.LastName = names[1];
res.redirect("/");
});
});
}
);
Some help on the matter would be appreciated.

Whenever you see a message like this Cannot set headers after they are sent to the client, it means that the logic of your endpoint tried to send a response to the client and failed, because it actually already responded. res.redirect() of express actually sends some 3xx http status to the client and if you are saying that this method throws the error you're facing, something before it already sent a response.
I didn't find anything that could respond in the snippet you provided (besides the very res.redirect()), so I suggest you to look into your middleware. For example into passport authentication, since it is mentioned here.

Related

Cannot set headers after they are sent to the client NodeJS Redirect

I have some IF/Else stataments, in IF statament I could make an redirect, but in else statament I couldn't.
Code that I use:
app.use(express.static(__dirname + '/public'));
router.get('/',function(req,res) {
res.sendFile(path.join(__dirname + '/public/html/index.html'));
var email = req.query.email;
conn.query("SELECT * FROM users WHERE email = ?", [email], function(err, result){
if (err) throw err;
var resultLength = result.length;
if (email != null && resultLength == 0) {
conn.query('INSERT INTO users (email) VALUES (?)', [email], function(err, result) {
if (err) throw err;
console.log('1 record inserted')
res.write('<p>You are now subscribed to our newsletter!</p>');
})
res.redirect('/succes')
}else{
console.log("You are already subscribed to our newsletter!")
res.redirect('/error')
}
})
});
You can send ONE and only one response to a given request. When you do:
res.sendFile(...)
that sends a response to the incoming request. Attempting to send ANY other request will cause the error about "cannot send headers after they are already sent".
Yet, in your code, you then try to do both res.write(...) and res.redirect(...). You simply can't do that. You get to send one and only one response to a given incoming request. You already sent one with res.sendFile(...).
Then, even further, you can't do a res.write(...), then followed by res.redirect(...). Doing a res.write(...) means that the http library has to send the headers so that it can now start sending the body. So, this forces the headers to get sent. But, now when you do a res.redirect(...), you're telling the Express library that you want to set a 302 status code and you want to set the Location header. Hmmm, but the status code has already been sent and the headers have already been sent, forced out by the res.write(...). So, at this point, res.redirect(...) cannot do its job.
It's unclear what this code is supposed to do. You're either serving index.html or you're doing something else that redirects to a different URL. The same route can't do both of those operations. For a route like this, you either send an HTML response OR you redirect to another URL. Pick one.
Here's one possible solution that contains only res.redirect():
router.get('/', function(req, res) {
const email = req.query.email;
if (!email) {
console.log("req.query.email not present");
res.redirect('/error')
return;
}
conn.query("SELECT * FROM users WHERE email = ?", [email], function(err, result) {
if (err) {
console.log(err);
res.redirect('/error')
return;
}
const resultLength = result.length;
if (resultLength == 0) {
conn.query('INSERT INTO users (email) VALUES (?)', [email], function(err, result) {
if (err) {
console.log(err);
res.redirect('/error')
return;
}
console.log('1 record inserted')
res.redirect('/succes');
return;
})
} else {
console.log("You are already subscribed to our newsletter!")
res.redirect('/error')
return;
}
});
});

softlayer-client: unable to update VPN for User_Customer via REST API

I am experimenting with the softlayer-client api wrapper in my Node Express application. My goal is to update the VPN password of a User_Customer by calling the updateVpnPassword method on a specific user.
I can construct a call to achieve a VPN password update using request, but I'm not sure it's the best way to achieve the desired result.
Can the softlayer-client module be used to make an similar call to this:
function updateVpnPassword(req, res, next) {
// Construct URL to update VPN
myURL = 'https://' + <userIDAdmin> + ':' + <apiKeyAdmin> + '#api.softlayer.com/rest/v3/SoftLayer_User_Customer/' + <softLayerID> + '/updateVpnPassword/' + <newPassword> + '.json';
request.get({url: myURL}, function (error, response, body) {
console.log('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('body:', body);
});
next();
}
My initial attempts have been to try variations on this:
function updateVpnPassword(req, res, next) {
// Assuming var client = new SoftLayer();
client
.auth(<userIDAdmin>, <apiKeyAdmin>)
.path('User_Customer', <softLayerID>,'updateVpnPassword')
.parameters(<newPassword>)
.put(function(err,result){
console.log(result);
if (err) {
next(err); // Pass errors to Express.
}
else {
// update successful
}
});
next();
}
But the console log gives an error response like
{ message: { error: 'Internal Error', code: 'SoftLayer_Exception_Public' } }.
I expect a TRUE or FALSE response, to indicate the whether the update is successful.
A similar python client can be found here but I require an implementation in JS.
I'm not familiar with nodejs but I installed the package softlayer-node and run your second code and it worked.
I also created the following script and I got TRUE
var username = 'set me';
var apikey = 'set me';
var userId = 1111111;
var SoftLayer = require('softlayer-node');
var client = new SoftLayer();
client
.auth(username, apikey)
.path('User_Custome', userId, 'updateVpnPassword')
.parameters('P#ssword123')
.put()
.then(function(result) {
console.log(result);
}, function(error) {
console.log(error);
});
node command:
$ node updateVpnPassword.js
true
Did you tried by sending that request using curl or any other REST client like postman?
If you get the same error then I recommend you submit a ticket and provide information like the id of users you are trying to update the vpn password and the user with which you are sending the request.

Return response with status code in Express

In my MongoDB/Node backend I have a pretty standard function for adding a new language to the database via a POST request. It looks like this:
exports.add = async function(req, res) {
let request, doc;
request = new EndpointRequestController(req, res);
try {
doc = await request.save(Language, req.body);
} catch(err) {
return request.sendError('An error occurred while trying to find existing records.', err);
}
request.sendResponse(doc, res.status(201));
}
Currently, this is working - in that when the user passes a value in the body of the POST request, the new language is added to our languages collection.
What I'm trying to figure out is, why is this returning a "204" response, rather than the "201" status I'm explicitly sending here as part of the response? How can I edit this to send the "201" response?
You are sending response outside the await function... you need to put res.send inside await function
exports.add = async function(req, res) {
let request, doc;
request = new EndpointRequestController(req, res);
try {
doc = await request.save(Language, req.body);
res.status(201).send(doc)
} catch(err) {
return request.sendError('An error occurred while trying to find existing records.', err);
}
}

passportjs custom callback 200

I am using angularjs and jade templating for my client side. And node and express on the server side. I recently added passport authentication and have the local strategy working fine when the login is attempted with valid credentials. It's the error cases that I'm having trouble with. I have implemented the custom callback to get my error messages to bubble back to the client. Now I get the error messages but the status gets set to 200 somewhere, instead of the 401 I expect.
// login route
app.post("/api/login", function(req, res, next) {
passport.authenticate("local", function(err, user, info) {
if (err) {
return next(err);
}
if (user === false) {
res.status = 401;
res.send(info.message);
} else {
res.json({success:"User logged in"});
}
})(req, res, next);
});
and this is the angular controller that submits the login request:
var loginObj = {email: $scope.login.email,
password:$scope.login.password};
$http.post("/api/login", loginObj)
.success(function(data, status) {
console.log("successful login");
console.log("data = " + JSON.stringify(data));
console.log("status = " + status);
})
.error(function(error) {
console.log("error logging in.");
console.log("error = " + JSON.stringify(error));
$scope.error = error;
});
So, suppose I have a password mismatch... the error in my validation routine sets my error message to "Invalid password, please try again." But what I see on the console is:
successful login
data = "Invalid password, please try again."
status = 200
I'm not resetting the status anywhere else. How is it getting set to 200? What am I doing wrong? Sorry, I'm a relative newbie to node and angular, so any help would really be appreciated. I know I'm not understanding some key point, I just can't figure out what. Thanks!
It's res.status(401), not res.status = 401.
res.status() is a function.

Response headers of previous request affecting current request

The following code is the user-facing part of a new node app we are building:
var loadInvoice = function(req, res, next) {
Invoice.findById(req.params.invoiceId, function (err, invoice) {
if (err) {
res.send(404, 'Page not found');
} else {
req.invoice = invoice;
next();
}
});
};
app.namespace('/invoices/:invoiceId', loadInvoice, function () {
app.get('', function(req, res){
var templateVals = {
//some template data
};
res.render('paymentselection', templateVals);
});
app.post('', function(req, res){
var data = {
// some data for the apiCall
};
someAPI.someRequest(data, function(err, data) {
console.log(res.status());
res.redirect(data.url);
});
});
});
The first method returns a confirmation page where the user presses a button to post to the same url, which triggers a redirect to an external website.
This all works exactly once. Every second request will crash the app with the message Cant set headers after they are sent. After carefull inspection of the code I could find no reason for this to happen so I added the console.log line which indeed confirms the location header has been set. But it is set to the value i got from someAPI on the previous request not the current one.
This makes absolutely no sense to me. I do not store this value anywhere nor do I do caching or persistence of this data in any way.
Does anybody know what could be causing this?
I use express, express-namespace, mogoose and swig
I found out the problem was being caused bij the 'Restler' libaray used within 'someAPI'. I have no idea how this is possible but swapping it out with something else fixed the problem.

Resources