proxying-agent
Advanced tools
Comparing version 0.1.3 to 0.1.4
@@ -8,3 +8,3 @@ 'use strict'; | ||
var util = require('util'); | ||
var ntlm = require('ntlm'); | ||
var ntlm = require('./ntlm'); | ||
@@ -19,10 +19,31 @@ function ProxyingAgent(options) { | ||
this.options.port = this.options.proxy.port || (this.options.ssl ? 443 : 80); | ||
this.options.authType = this.options.authType || 'basic'; | ||
if (this.options.authType == 'ntlm') { | ||
if (!this.options.proxy.auth) { | ||
throw new Error('NTLM authentication credentials must be provided'); | ||
} | ||
if (!this.options.ntlm || !this.options.ntlm.domain) { | ||
throw new Error('NTLM domain must be provided'); | ||
} | ||
} | ||
// base64 decode proxy auth if necessary | ||
var auth = this.options.proxy.auth; | ||
if (auth && auth.indexOf(':') == -1) { | ||
auth = new Buffer(auth, 'base64').toString('ascii'); | ||
// if after decoding there still isn't a colon, then revert back to the original value | ||
if (auth.indexOf(':') == -1) { | ||
auth = this.options.proxy.auth; | ||
} | ||
this.options.proxy.auth = auth; | ||
} | ||
// select the Agent type to use based on the proxy protocol | ||
if (this.options.ssl) { | ||
this.agent = https.Agent; | ||
this.options.agent = https.globalAgent; | ||
this.options.agent = new https.Agent(); | ||
} else { | ||
this.agent = http.Agent; | ||
this.options.agent = http.globalAgent; | ||
this.options.agent = new http.Agent(); | ||
} | ||
@@ -33,12 +54,9 @@ this.agent.call(this, this.options); | ||
/** | ||
* Overrides the 'addRequest' Agent method for establishing a socket with the proxy | ||
* that will e used to issue the actual request | ||
* @param req | ||
* @param host | ||
* @param port | ||
* @param localAddress | ||
*/ | ||
ProxyingAgent.prototype.addRequest = function(req, host, port, localAddress) { | ||
if (this.options.ntlm) { | ||
if (this.options.authType == 'ntlm') { | ||
this.startNtlm(req, host, port, localAddress); | ||
@@ -54,23 +72,11 @@ } else { | ||
* or just issues a regular request for the proxy to transfer | ||
* @param req | ||
* @param host | ||
* @param port | ||
* @param localAddress | ||
*/ | ||
ProxyingAgent.prototype.startProxying = function(req, host, port, localAddress) { | ||
// setup the authorization header for the proxy | ||
if (this.options.proxy.auth) { | ||
var auth = this.options.proxy.auth; | ||
// if there is no colon then assume that the auth is a base64 encoded username:password | ||
if (auth.indexOf(':') == -1) { | ||
auth = new Buffer(auth, 'base64').toString('ascii'); | ||
// if after decoding there still isn't a colon, then revert back to the original value | ||
if (auth.indexOf(':') == -1) { | ||
auth = this.options.proxy.auth; | ||
} | ||
} | ||
// setup the basic authentication header for the proxy. | ||
// we do this only if we haven't already authenticated through NTLM | ||
if (this.options.authType == 'basic' && this.options.proxy.auth) { | ||
this.authHeader = { | ||
header: 'Proxy-Authorization', | ||
value: 'Basic ' + new Buffer(auth).toString('base64') | ||
value: 'Basic ' + new Buffer(this.options.proxy.auth).toString('base64') | ||
} | ||
@@ -84,2 +90,3 @@ } | ||
tunnelOptions.path = host+':'+port; | ||
tunnelOptions.headers = tunnelOptions.headers || {}; | ||
@@ -92,13 +99,29 @@ // if we already have a socket open then execute the CONNECT method on it | ||
// add the authentication header | ||
if (this.authHeader) { | ||
tunnelOptions.headers[this.authHeader.header] = this.authHeader.value; | ||
} | ||
// create a new CONNECT request to the proxy to create the tunnel | ||
// to the server | ||
var newReq = this.createNewRequest(tunnelOptions); | ||
if (this.authHeader) { | ||
newReq.setHeader(this.authHeader.header, this.authHeader.value); | ||
} | ||
newReq.once('close', function() { | ||
this.emitError(req, 'Tunnel creation failed. Socket closed prematurely'); | ||
}.bind(this)); | ||
newReq.once('error', function(error) { | ||
this.emitError(req, 'Tunnel creation failed. Socket error: ' + error); | ||
}.bind(this)); | ||
// listen for the CONNECT event to complete and execute the original request | ||
// on the TLSed socket | ||
newReq.on('connect', function(response, socket, head) { | ||
newReq.once('connect', function(response, socket, head) { | ||
newReq.removeAllListeners(); | ||
if (response.statusCode != 200) { | ||
this.emitError(req, 'Tunnel creation failed. Received status code ' + response.statusCode); | ||
return; | ||
} | ||
var tlsOptions = { | ||
socket: socket, | ||
socket: response.socket, | ||
servername: host | ||
@@ -112,2 +135,3 @@ } | ||
}.bind(this)); | ||
// execute the CONNECT method to create the tunnel | ||
@@ -129,7 +153,2 @@ newReq.end(); | ||
* to issue the actual request or open a tunnel on | ||
* | ||
* @param req | ||
* @param host | ||
* @param port | ||
* @param localAddress | ||
*/ | ||
@@ -139,5 +158,12 @@ ProxyingAgent.prototype.startNtlm = function(req, host, port, localAddress) { | ||
ntlmOptions.method = ntlmOptions.method || 'GET'; // just for the NTLM handshake | ||
ntlmOptions.path = (this.options.ssl ? 'https://' : 'http://')+host+':'+port+req.path | ||
ntlmOptions.ntlm.workstation = ntlmOptions.ntlm.workstation || require('os').hostname(); | ||
var creds = this.options.proxy.auth.split(':'); | ||
ntlmOptions.ntlm.username = creds[0]; | ||
ntlmOptions.ntlm.password = creds[1]; | ||
// set the NTLM type 1 message header | ||
ntlmOptions.headers['Authorization'] = ntlm.challengeHeader(ntlmOptions.ntlm.hostname, ntlmOptions.ntlm.domain); | ||
ntlmOptions.headers = ntlmOptions.headers || {}; | ||
ntlmOptions.headers['Proxy-Authorization'] = ntlm.createType1Message(ntlmOptions.ntlm); | ||
@@ -147,24 +173,48 @@ // create the NTLM type 1 request | ||
// capture the socket | ||
newReq.on('socket', function(socket) { | ||
this.setSocket(req, socket); | ||
}); | ||
// capture the response and set the NTLM type 3 authorization header | ||
// that will be used when issuing the actual request | ||
newReq.on('response', function(response) { | ||
if (!response.statusCode == 401 || !response.getHeader('WWW-Authenticate')) { | ||
newReq.once('response', function(response) { | ||
if (!response.statusCode == 407 || !response.headers['proxy-authenticate']) { | ||
this.emitError(req, 'did not receive NTLM type 2 message'); | ||
return; | ||
} | ||
var type2msg = ntlm.parseType2Message(response.headers['proxy-authenticate'], function(error) { | ||
this.emitError(req, error); | ||
return null; | ||
}.bind(this)); | ||
if (!type2msg) { | ||
return; | ||
} | ||
this.authHeader = { | ||
header: 'Authorization', | ||
value: ntlm.responseHeader(response, req.path, | ||
ntlmOptions.ntlm.domain, ntlmOptions.ntlm.username, ntlmOptions.ntlm.password) | ||
header: 'Proxy-Authorization', | ||
value: ntlm.createType3Message(type2msg, ntlmOptions.ntlm) | ||
} | ||
// start proxying the actual request. | ||
// te socket should have already been captured and associated with the request | ||
response.on('socket', function(socket) { | ||
// capture the socket | ||
this.setSocket(req, socket); | ||
}.bind(this)); | ||
// read all the data from the socket as it may contain a body that should be discarded | ||
response.on('data', function() { | ||
// just consume the body | ||
}.bind(this)); | ||
// start proxying | ||
this.startProxying(req, host, port, localAddress); | ||
}); | ||
}.bind(this)); | ||
// start proxying the actual request only when there is not more body to read. | ||
// the socket should have already been captured and associated with the request | ||
newReq.once('close', function() { | ||
this.emitError(req, 'NTLM failed. Socket closed prematurely'); | ||
}.bind(this)); | ||
newReq.once('error', function(error) { | ||
this.emitError(req, 'NTLM failed. Socket error: ' + error); | ||
}.bind(this)); | ||
// issue the NTLM type 1 request | ||
@@ -176,4 +226,2 @@ newReq.end(); | ||
* Create a new request instance according the needed security | ||
* @param options | ||
* @returns {*} | ||
*/ | ||
@@ -185,2 +233,3 @@ ProxyingAgent.prototype.createNewRequest = function(options) { | ||
return new http.request(options); | ||
} | ||
@@ -192,7 +241,2 @@ | ||
* will be used for issuing the request (via the 'createSocket' method) | ||
* | ||
* @param req | ||
* @param host | ||
* @param port | ||
* @param localAddress | ||
*/ | ||
@@ -208,17 +252,16 @@ ProxyingAgent.prototype.execRequest = function(req, host, port, localAddress) { | ||
} | ||
/** | ||
* Remember a socket and associate it with a specific request. | ||
* When the 'createSocket' method will be called to execute the actual request | ||
* then the already existing socket will be used | ||
* @param req | ||
* @param socket | ||
*/ | ||
* Remember a socket and associate it with a specific request. | ||
* When the 'createSocket' method will be called to execute the actual request | ||
* then the already existing socket will be used | ||
*/ | ||
ProxyingAgent.prototype.setSocket = function(req, socket) { | ||
var self = this; | ||
this.openSockets[req] = socket; | ||
var onClose = function() { | ||
if (self.openSockets[req]) { | ||
delete self.openSockets[req]; | ||
if (this.openSockets[req]) { | ||
delete this.openSockets[req]; | ||
} | ||
}; | ||
}.bind(this); | ||
this.openSockets[req].on('close', onClose); | ||
@@ -232,12 +275,6 @@ } | ||
/** | ||
* This is called during the 'addRequest' call of the original Agent to return a | ||
* new socket for executing the request. If a socket already exists then it is used | ||
* instead of creating a new one. | ||
* @param name | ||
* @param host | ||
* @param port | ||
* @param localAddress | ||
* @param req | ||
* @returns {*} | ||
*/ | ||
* This is called during the 'addRequest' call of the original Agent to return a | ||
* new socket for executing the request. If a socket already exists then it is used | ||
* instead of creating a new one. | ||
*/ | ||
ProxyingAgent.prototype.createSocket = function(name, host, port, localAddress, req) { | ||
@@ -259,4 +296,2 @@ if (this.openSockets[req]) { | ||
* A simple agent to execute a request on a given socket | ||
* @param socket | ||
* @constructor | ||
*/ | ||
@@ -263,0 +298,0 @@ function SocketAgent(socket) { |
{ | ||
"name": "proxying-agent", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"description": "Node HTTP/HTTPS Forward Proxy Agent", | ||
@@ -10,3 +10,4 @@ "keywords": [ | ||
"proxy", | ||
"tunnel" | ||
"tunnel", | ||
"ntlm" | ||
], | ||
@@ -26,6 +27,3 @@ "homepage": "https://github.com/capriza/node-proxying-agent/", | ||
}, | ||
"dependencies": { | ||
"ntlm": "~0.1.1" | ||
}, | ||
"readmeFilename": "README.md" | ||
} |
@@ -6,6 +6,6 @@ # Node HTTP/HTTPS Forward Proxy Agent | ||
It supports the following: | ||
* Connect to a proxy with a regular socket or SSL/TLS socket | ||
* Connect to a proxy with either HTTP or HTTPS | ||
* Proxying to a remote server using SSL tunneling (via the http CONNECT method) | ||
* Authenticate with a proxy with Basic authentication | ||
* Authenticate with a proxy with NTLM authentication (experimental). Depends on ``node-ntlm`` | ||
* Authenticate with a proxy with NTLM authentication (beta) | ||
@@ -21,5 +21,50 @@ The agent inherits directly from the ``http.Agent`` Node object so it benefits from all | ||
The following options are supported: | ||
* ``proxy`` - Specifies the proxy url. The supported format is ``http[s]://[auth@]host:port`` where ``auth`` | ||
is the authentication information in the form of ``username:password``. The authentication information can also be | ||
in the form of a Base64 encoded ``user:password``, e.g. ``http://dXNlcm5hbWU6cGFzc3dvcmQ=@proxy.example.com:8080`` | ||
* ``tunnel`` - If ``true`` then the proxy will become a tunnel to the server. | ||
This should usually be ``true`` only if the target server protocol is ``https`` | ||
* ``authType`` - Proxy authentication type. Possible values are ``basic`` and ``ntlm`` (default is ``basic``). | ||
* ``ntlm`` - (beta) applicable only if ``authType`` is ``ntlm``. Supported fields: | ||
* ``domain`` (required) - the NTLM domain | ||
* ``hostname`` (optional) - the local machine hostname | ||
### HTTP Server | ||
```javascript | ||
var proxying = require('proxying-agent'); | ||
var proxyingOptions = { | ||
proxy: 'http://proxy.example.com:8080' | ||
}; | ||
var proxyingAgent = new proxying.ProxyingAgent(proxyingOptions); | ||
var req = http.request({ | ||
host: 'example.com', | ||
port: 80, | ||
agent: proxyingAgent | ||
}); | ||
``` | ||
### HTTPS Server | ||
```javascript | ||
var proxying = require('proxying-agent'); | ||
var proxyingOptions = { | ||
proxy: 'http://proxy.example.com:8080', | ||
tunnel: true | ||
}; | ||
var proxyingAgent = new proxying.ProxyingAgent(proxyingOptions); | ||
var req = https.request({ | ||
host: 'example.com', | ||
port: 443, | ||
agent: proxyingAgent | ||
}); | ||
``` | ||
### Basic Authentication | ||
```javascript | ||
var proxying = require('proxying-agent'); | ||
var proxyingOptions = { | ||
proxy: 'http://username:password@proxy.example.com:8080', | ||
@@ -36,14 +81,33 @@ tunnel: true | ||
The following options are supported: | ||
### NTLM Authentication | ||
* ``proxy`` - Specifies the proxy url. The supported format is ``http[s]://[auth@]host:port`` where ``auth`` | ||
is the authentication information in the form of ``username:password``. The authentication information can also be | ||
in the form of a Base64 encoded ``user:password``, e.g. ``http://dXNlcm5hbWU6cGFzc3dvcmQ=@proxy.example.com:8080`` | ||
* ``tunnel`` - If ``true`` then the proxy will become a tunnel to the server. This should only be ``true`` if the target server protocol is https | ||
* ``ntlm`` - (experimental) connect to the proxy using NTLM authentication. ``ntlm`` is expected to contain the | ||
following fields: | ||
* ``hostname`` - the local machine hostname | ||
* ``domain`` - the NTLM domain | ||
* ``username`` - the NTLM username | ||
* ``password`` - the NTLM password | ||
When authenticating using NTLM it is important to delay sending the request data until the socket is assigned to the request. | ||
Failing to do so will result in the socket being prematurely closed, preventing the NTLM handshake from completing. | ||
```javascript | ||
var proxying = require('proxying-agent'); | ||
var proxyingOptions = { | ||
proxy: 'http://username:password@proxy.example.com:8080', | ||
tunnel: true, | ||
authType: 'ntlm', | ||
ntlm: { | ||
domain: 'MYDOMAIN' | ||
} | ||
}; | ||
var proxyingAgent = new proxying.ProxyingAgent(proxyingOptions); | ||
var req = https.request({ | ||
host: 'example.com', | ||
port: 443, | ||
agent: proxyingAgent | ||
}); | ||
req.on('socket', function(socket) { | ||
req.write('DATA'); | ||
req.end(); | ||
}); | ||
``` | ||
## References | ||
* NTLM code was forked from https://github.com/SamDecrock/node-http-ntlm.git | ||
* NTLM Authentication Scheme for HTTP - http://www.innovation.ch/personal/ronald/ntlm.html |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28551
0
6
562
111
4
- Removedntlm@~0.1.1
- Removedntlm@0.1.3(transitive)