I'm trying to create a proxy with node-http-proxy in Node.js that checks whether a request is authorized in a mongodb.
Basically, I created a middleware module for the node-http-proxy that I use like this:
httpProxy.createServer(
require('./example-middleware')(),
9005, 'localhost'
).listen(8005)
What the middleware module does is using mongojs to connect to mongodb and run a query to see if the user is authorized to access the resource:
module.exports = function(){
// Do something when first loaded!
console.log("Middleware loaded!");
return function (req, res, next) {
var record = extractCredentials(req);
var query = -- Db query --
//Debug:
log("Query result", query);
db.authList.find(query).sort({
"url.len": -1
}, function(err, docs){
console.log(docs);
// Return the creator for the longest matching path:
if(docs.length > 0) {
console.log("User confirmed!");
next();
} else {
console.log("User not confirmed!");
res.writeHead(403, {
'Content-Type': 'text/plain'
});
res.write('You are not allowed to access this resource...');
res.end();
}
});
}
}
Now the problem is that as soon as I add the asynchronous call to mongodb using mongojs the proxy hangs and never send the response back.
To clarify: on a "User not confirmed" everything works fine and the 403 is returned. On a "user confirmed" however I see the log but the browser then hangs forever and the request isn't proxied.
Now, if I remove the "user confirmed" and next() part outside of a callback it does work:
module.exports = function(){
// Do something when first loaded!
console.log("Middleware loaded!");
return function (req, res, next) {
var record = extractCredentials(req);
var query = --- query ---
console.log("User confirmed!");
next();
}
but I can't do that since the mongojs query is meant (rightfully I guess) to be executed asynchronously, the callback being triggered only when the db replied...
I also tried the version without using a middleware:
http.createServer(function (req, res) {
// run the async query here!
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 9000
});
}).listen(8001);
But that did not help either...
Any clue? Note that I'm new to node.js so I suspect a misunderstanding on my side...
Found the answer, actually the catch is that the request needs to be buffered:
httpProxy.createServer(function (req, res, proxy) {
// ignore favicon
if (req.url === '/favicon.ico') {
res.writeHead(200, {
'Content-Type': 'image/x-icon'
} );
res.end();
console.log('favicon requested');
return;
}
var credentials = extractCredentials(req);
console.log(credentials);
var buffer = httpProxy.buffer(req);
checkRequest(credentials, function(user){
if(user == ...) {
console.log("Access granted!");
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 9005,
buffer: buffer
});
} else {
console.log("Access denied!");
res.writeHead(403, {
"Content-Type": "text/plain"
});
res.write("You are not allowed to access this resource...");
res.end();
}
});
}).listen(8005);
Two problems:
You're not calling next(); in the else case of your sort callback.
The second parameter to your sort callback is a Cursor, not an array of documents. As such, docs.length > 0 is never true and the code always follows the else path.
Related
I'm using Express 4, and I'm using a middleware http-proxy-middleware ( https://github.com/chimurai/http-proxy-middleware), and having the following issues
In normal way, I can do the following to manupulate the response before return to the client
app.get('/v1/users/:username', function(request, response, next) {
var username = request.params.username;
findUserByUsername(username, function(error, user) {
if (error) return next(error);
return response.render('user', user);
});
});
But how do I execute custom logic if I'm using proxy, let's say I want to manipulate some of the data before response to the client? Is there a good pattern to do that with this middleware ?
app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
Here is the backlink for the issue I put in github as well - https://github.com/chimurai/http-proxy-middleware/issues/97
Any help would be appreciated.
I think this is the correct way to do it according to the official documentation of http-proxy.
modify -response
app.use('/api', proxy({
target: 'http://www.example.org',
changeOrigin: true,
selfHandleResponse: true, // so that the onProxyRes takes care of sending the response
onProxyRes: function(proxyRes, req, res) {
var body = new Buffer('');
proxyRes.on('data', function(data) {
body = Buffer.concat([body, data]);
});
proxyRes.on('end', function() {
body = body.toString();
console.log("res from proxied server:", body);
res.end("my response to cli");
});
}
}));
here is my answer,
onProxyRes :function(proxyRes, req, res){
var _write = res.write;
var output;
var body = "";
proxyRes.on('data', function(data) {
data = data.toString('utf-8');
body += data;
});
res.write = function (data) {
try{
/*** something detect if data is all download.my data is json,so I can do by this***/
eval("output="+body)
output = mock.mock(output)
_write.call(res,JSON.stringify(output));
} catch (err) {}
}
}
add onProxyRes option on the http-proxy-middleware
use the data event on the proxyRes to get the output
then modify the output in res.write
I have an authentication middleware I will like to test, the middleware makes an external call to an authentication service and based on the returned statusCode either calls the next middleware/controller or it returns a 401 status. Something like what I have below.
var auth = function (req, res, next) {
needle.get('http://route-auth-service.com', options, function (err, reply) {
if (reply.statusCode === 200) {
next();
} else {
res.statusCode(401)
}
})
}
I use SinonJS, nock, and node-mocks-http for testing and my simple test is as below.
// require all the packages and auth middleware
it('should login user, function (done) {
res = httpMocks.createResponse();
req = httpMocks.createRequest({
url: '/api',
cookies: {
'session': true
}
});
nock('http://route-auth-service.com')
.get('/')
.reply(200);
var next = sinon.spy()
auth(res, req, next);
next.called.should.equal(true); // Fails returns false instead
done();
});
The test always fails and returns false, I feel that the reason is because the needle call is asynchronous, and before the call returns the assertion part is reached. I have been working on this all day, I need help please.
you need to split the test setup away from the assertion
// this may be "beforeEach"
// depends on what testing framework you're using
before(function(done){
res = httpMocks.createResponse();
req = httpMocks.createRequest({
url: '/api',
cookies: {
'session': true
}
});
nock('http://route-auth-service.com').get('/').reply(200);
var next = sinon.spy();
auth(res, req, function() {
next();
done();
});
});
it('should login user', function () {
next.called.should.equal(true); // Fails returns false instead
});
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);
}
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.
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.