I'm new to Sails.js and I was trying to make a filter to authorize using a Bearer token which come from a higher server, a gatekeeper which is responsable to do the OAuth2 authentication from GitHub API. The services streams works well. I'm already aware of Passport.js but I'm trying to implement this on my own. I came with a policy which looks like:
module.exports = function (req, res, next) {
var httpsExec = require('https');
if (req.headers.authorization) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var tokenType = parts[0]
, credentials = parts[1];
if (/^Bearer$/i.test(tokenType) || /^bearer$/i.test(tokenType)) {
httpsExec.request({
host: 'api.github.com',
post: 443,
path: '/user',
method: 'GET',
headers: {'Authorization': 'token ' + credentials, 'User-Agent': 'curly'}
}, function (response) {
var responseData = '';
response.setEncoding('utf8');
response.on('data', function (chunk) {
responseData += chunk;
});
response.once('error', function (err) {
next(err);
});
response.on('end', function () {
try {
req.options.user = JSON.parse(responseData);
next();
} catch (e) {
res.send(401, {error: e});
}
});
}).end();
} else {
console.err("The token is not a Bearer");
res.send(401)
}
}
} else {
res.send(401, {error: "Full authentication is necessary to access this resource"})
}
};
The policy is called once I hit the controller route but it throws a _http_outgoing.js:335
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
And the process is terminate.
The problem I think is the next() and the returns I tried everywhere I think, to put the next() call, but still gives me this error, if I remove then I lock the request on the policy.
EDIT
I did a simple sample of policy where I just set some property on req.options.values and happened the same problem, so maybe could be an issue with req.options.requestData = JSON.parse(responseData); ? How else could I set a property to send to controller ?
response.once('error', function (err) {
next(err);
});
response.on('end', function () {
try {
req.options.user = JSON.parse(responseData);
next();
} catch (e) {
res.send(401, {error: e});
}
});
both are getting executed.to check console.log("something") in error to see if there is error.
This happens when you're trying to modify the request and response together or modify one of them twice.
In your code, I think the callback is being called twice and you are also modifying the response at the same time. Check the lines where you're calling callback "next()". You'll find your issue.
Related
I am trying to use node-http-proxy inside an AdonisJS controller, but I get the error
The "url" argument must be of type string. Received type function
The line causing the error is the proxy.web(request, response, { target: urlToProxy });
async proxy({ request, response }){
var resource = await Resource.query().where('uri', request.url()).with('service.user').with('userSecurity').first()
resource = resource.toJSON()
if(!resource.service.active){
return response.status(404).send(`Parent service '${resource.service.title}' is disabled`)
}
if(!resource.active){
return response.status(404).send(`Resource is disabled`)
}
if(resource.userSecurity.length <= 0) {
return response.status(403).send(`You are not permitted to access that resource. Contact ${resource.service.user.first_name} ${resource.service.user.last_name} (${resource.service.user.email})`)
}
var urlToProxy = url.resolve(resource.service.basepath, request.originalUrl())
var proxy = httpProxy.createProxyServer()
proxy.web(request, response, { target: urlToProxy });
}
In the end I got a bit closer but not a full fix. The getting close bit was to realise the http-proxy was delivering data via buffer so I had to do something like this
proxy.web(req, res, { target: data.service.basepath})
proxy.on('error', function(err, req, res){
console.log("There was an error", err)
})
proxy.on('proxyRes', async (proxyRes, request, response) =>{
var body = new Buffer('')
proxyRes.on('data', (data)=>{
body = Buffer.concat([body, data])
})
proxyRes.on('end', ()=>{
body = body.toString()
try{
res.send(body)
}catch(err){
}
})
});
However, I still could not get it to work as the controller was returning before http-proxy had completed the request.
In the end and probably for the best, I wrote a stand alone proxy app and used the main app just to validate JWT tokens before they go through the Proxy.
You were so close, I wanted to do something similar and wrapped the proxy in a promise so we can wait for the proxy to return before responding with our response:
const proxy = httpProxy.createProxyServer();
const prom = new Promise((resolve, reject) => {
proxy.web(request.request, response.response, {
target: urlToTarget
}, (e) => {
reject(e);
});
proxy.on('proxyRes', function (proxyRes, req, res) {
let body = [];
proxyRes.on('data', function (chunk) {
body.push(chunk);
});
proxyRes.on('end', function () {
body = Buffer.concat(body).toString();
resolve(body);
});
});
});
const result = await prom;
response.body(result);
return response;
I thought I'd give you a complete answer for anyone else that comes across this.
I have to scrape a web page for a key to use later as a cookie. That part works. But because the request is async the error is not handled. I want to make sure the error is passed along the middleware chain. But I can't get my brain around this one. Thanks for helping.
app.use('/', makeLoginCookie, function (req, res, next){
console.log('My app.use() starts here.');
//console.log('makeLoginCookie: %s', err);
next();
});
And here's the function
function makeLoginCookie(req, res, next) {
httpOptions = {
url: site.url,
headers: {
Cookie: null
}
}
// Get HIDDEN key, build login cookie
request(httpOptions, function(error, response, body) {
if (!error && response.statusCode == 200) {
//console.log(body)
var match = body.match( /\<INPUT TYPE=HIDDEN id=\"key\", value=\"([0-9a-f]+)\"\>/i );
var key = match[1]
var encrypted = sha1(key + site.username + site.password);
loginCookie = "username=" + key + ";password=" + encrypted;
next();
} else {
console.log("Connect failed %s" , error);
//err = new Error("Can't connect");
next();
}
});
};
Refer to Express Error handling, you can use next(err); to pass error in Express. Here is one good link.
I would use promises (Q library) in order to resolve this, and for another things too, specially for web scraping. Your "makeLoginCookie" function could return a deferred.promise and, when the request fails, reject it with the error.
Edit 1: I recommend this great video that explains how to work properly with async code https://www.youtube.com/watch?v=obaSQBBWZLk. It could help you with this and another stuff.
Edit 2: Using promises would be like this, see if it helps you:
var Q = require("q");
app.use('/', function (req, res, next) {
// This part, we will call your function "makeLoginCookie"
makeLoginCookie().then(function(){
// This is called when your function RESOLVES the promise
// Here you could log or do some stuff...
// Then, go next...
next();
}, function(error){
// This is called when your function REJECTS the promise
console.log("Connect failed %s" , error);
// And then, handle the error, like stopping the request and sending an error:
res.status(400).json({errorMessage: error});
})
}, function (req, res, next){
console.log('My app.use() starts here.');
next();
});
// Removed parameters from function
function makeLoginCookie() {
// This is the object that will return a promise
var deferred = Q.defer();
httpOptions = {
url: site.url,
headers: {
Cookie: null
}
}
// Get HIDDEN key, build login cookie
request(httpOptions, function(error, response, body) {
if (!error && response.statusCode == 200) {
//console.log(body)
var match = body.match( /\<INPUT TYPE=HIDDEN id=\"key\", value=\"([0-9a-f]+)\"\>/i );
var key = match[1]
var encrypted = sha1(key + site.username + site.password);
loginCookie = "username=" + key + ";password=" + encrypted;
// Instead of using next(), RESOLVE your promise
// next();
deferred.resolve(); // You can pass some data into it..
} else {
// Instead of using next(), REJECT your promise
// next();
deferred.reject(error); // You can pass some data into it, like an error object or string...
}
});
// Promise that something will be fulfilled or reject later
return deferred.promise;
};
There must have been some error elsewhere in my code because this works as expected now.
} else {
console.log("Connect failed %s" , error);
err = new Error("Can't connect");
next(err);
}
Probably an obvious answer to this but I'm not sure what way to take.
request is a node module: https://github.com/request/request
I fill an array of getHistory requests (with different parameters). p = [p1,p2...].
this.app.all('/api/heatmap', function(req,res) {
// fill p here _.each(blabla, p.push(gethistory(someparams...)
var result = [];
function getHistory(params) {
var options = { ...};
var callback = function(error, response, body) {
if(error) { //wtv
} else {
// what to do with the body here ? return body ? result.push(body) ?
}
}
request(options, callback);
}
Q.all(p).then(function() {
});
}
So the problem here is that I when all of the request to be done , put everything in an array/object then send the whole thing to the client. How to have getHistory returning the fetched value (after the request is done ).
Hope it's clear.
The core problem here is that node.js-style callbacks and promises are not compatible. Promises emphasize on return values, node emphasizes on callbacks.
Therefore you need a sort of adapter that wraps node's callback convention properly, a process called Promisifcation. This can be done manually, but it's tedious at best and error-prone when you are not careful. Luckily, since node's conventions are well-established, it can be automated. Q has a few helpers for that, but Bluebird is quite a bit more convenient in this regard.
So the easy way to do it is to switch to Bluebird as the promise library and to use promisifyAll.
var Promise = require('bluebird');
var request = Promise.promisifyAll(require("request"));
this.app.all('/api/heatmap', function(req, res) {
var requests = blabla.map(function (item) {
return request.getAsync({ /* params */ });
});
Promise.all(requests).then(function (responses) {
res.send( JSON.stringify(responses) ); // whatever
}).catch(function (error) {
res.send( "An error ocurred: " + error ); // handle error
});
}
FWIW, here's another answer that shows how the same would look like when done properly with Q:
// promisified request
function requestAsync(options) {
var result = Q.defer();
request(options, function(error, response, body) {
if (error) {
result.reject(error);
} else {
result.resolve(body);
}
});
return result.promise;
}
// returns promises for heatmapVolumes
function getHistory(params) {
return requestAsync({
method: 'GET',
url: 'https://api.kaiko.com/v1/charts/' +
encodeURIComponent(params.exchange) + '/' +
encodeURIComponent(params.pair) +'/history',
qs: params.qs,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}).then(function (body) {
return heatmapVolume(body, params.exchange, params.pair);
}).catch(function (error) {
// log detailed error and send less revealing message downstream
console.error('error fetching trades', error);
throw new Error('Something went wrong while fetching trades');
});
}
// usage
this.app.all('/api/heatmap', function(req, res) {
getHistory({
exchange: "foo", pair: "bar", qs: "qux"
}).then(function (heatmap) {
res.send(200, heatmap);
}).catch(function (error) {
res.send(500, error);
});
});
Used Q.deferred and it worked as documented \o/
function getHistory(params) {
var deferred = Q.defer();
var options = {
method: 'GET',
url: 'https://api.kaiko.com/v1/charts/' + params.exchange + '/' + params.pair +'/history',
qs:qs,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}
var callback = function(error, response, body) {
if(error) {
console.log('error fetching trades', error);
res.send(500, 'Something went wrong while fetching trades');
} else {
var body = heatmapVolume(body, params.exchange, params.pair);
// console.log("result!", body.exchange, body.pair);
result.push(body);
// return body;
deferred.resolve();
}
}
request(options, callback);
return deferred.promise;
}
While trying to debug the following get request, I notice that it returns undefined and then runs the code for the response.
configs is a json object with all the parameters defined. I am also, for some reason, getting a response form the php server saying that grant-type is invalid or can't be found, although when debugging it is passing the correct parameter from the configs file.
How can I correct my code?
var http = require("http");
var querystring = require("querystring");
var _ = require("underscore");
apiCaller = {};
apiCaller.token = null;
var server=http.createServer(function(req,res){});
server.listen(8080);
apiCaller._get = function (context, config, fn) {
// request to obtain our oauth token
var options = {
method: "GET",
hostname: config.host,
client_id: config.clientId,
client_secret: config.clientSecret,
grant_type: config.grant_type,
path: "/my/path/to/token",
headers : {
'Content-Type': "application/json",
'Accept': "application/json"
}
};
var callback = function(response) {
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + JSON.stringify(response.headers));
var str = '';
//another chunk of data has been recieved, so append it to `str`
response.on('data', function (chunk) {
str += chunk;
});
// error response
response.on("error", function (error) {
if ( !context ) {
console.error("Something went wrong with the api response.");
return;
}
context.done(new Error("Something went wrong with the api response."));
});
//the whole response has been recieved, so we just print it out here
response.on('end', function () {
apiCaller.token = JSON.parse(str).access_token;
// we want to stop the request if token is not correct
if ( !apiCaller.token || apiCaller.token === undefined || apiCaller.token === null ) {
if ( !context ) {
console.error("Something went wrong with the token. Wrong token! Token: %s", apiCaller.token);
return;
}
console.error("Token: %s", apiCaller.token);
context.done(new Error("Something went wrong with the token. Wrong token!"));
}
});
};
var request = http.request(options, callback);
request.on('error', function(e) {
console.log('problem with request:');
});
request.end();
};
It is an asynchronous function. Asynchronous functions (which are kind of the bread-and-butter of Node.js) typically return nothing. Instead, what you might think of as the return value is passed to the callback function. That's what's happening here.
As Trott says, It's asynchronous, it's possible that request.end() is executing before callback function has finished....
I try to use my compound.js-application as a (transparent) proxy-server. When a user tries to request a external website, the application will check, if the user with that ip-address was authenticated before.
If so, the external site will be shown, if not, the user will be encouraged to login.
The problem is, that the response is not processed, when there is an access to the database-object "User".
When I comment out the database section and just use the code inside the anonymous function, the programm works as expected.
action('exturl', function () {
User.all({ where: { ipaddress: req.ip }}, function(err, users) {
if (users.length > 0) {
this.user = user[0];
var proxy = http.createClient(80, req.headers['host'])
var proxy_request = proxy.request(req.method, req.url, req.headers);
proxy_request.addListener('response', function (proxy_response) {
proxy_response.addListener('data', function(chunk) {
res.write(chunk, 'binary');
});
proxy_response.addListener('end', function() {
res.end();
});
res.writeHead(proxy_response.statusCode, proxy_response.headers);
});
req.addListener('data', function(chunk) {
proxy_request.write(chunk, 'binary');
});
req.addListener('end', function() {
proxy_request.end();
});
} else {
redirect(path_to.login);
}
});
});
Is there a failure inside my code? I don't know what I am doing wrong.