Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update with request module #6

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.project
53 changes: 53 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

module.exports = function(grunt) {

// Project configuration.
grunt.initConfig({
nodeunit: {
files: ['test/**/*_test.js']
},

jshint: {
options: {
jshintrc: '.jshintrc'
},
gruntfile: {
src: 'Gruntfile.js'
},
lib: {
src: ['lib/**/*.js']
},
test: {
src: ['test/**/*.js']
},
},
watch: {
gruntfile: {
files: '<%= jshint.gruntfile.src %>',
tasks: ['jshint:gruntfile']
},
lib: {
files: '<%= jshint.lib.src %>',
tasks: ['jshint:lib', 'nodeunit']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'nodeunit']
},
work: {
files: ['<%= jshint.lib.src %>', '<%= jshint.test.src %>', 'node_modules/**/*.js'],
tasks: ['nodeunit']
}
},
});

// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');

// Default task.
grunt.registerTask('default', ['jshint', 'nodeunit' ]);

};
146 changes: 0 additions & 146 deletions http-digest-client.js

This file was deleted.

153 changes: 153 additions & 0 deletions lib/http-digest-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//
// # Digest Client
//
// Use together with HTTP Client to perform requests to servers protected
// by digest authentication.
//
var url=require('url');

var HTTPDigest = function() {
var crypto = require('crypto');
var request = require('request');

var HTTPDigest = function(username, password) {
this.nc = 0;
this.username = username;
this.password = password;
};

//
// ## Make request
//
// Wraps the http.request function to apply digest authorization.
//
HTTPDigest.prototype.request = function(options, callback) {
var self = this;
options.auth.sendImmediately=false; // ensure no basic auth
request(options, function(err, res, body) {
self._handleResponse(options, err, res, body, callback);
}).end();
};

//
// ## Handle authentication
//
// Parse authentication headers and set response.
//
HTTPDigest.prototype._handleResponse = function handleResponse(options, err, res, body, callback) {
// First check if there is an error condition
if(err != null && typeof res=='undefined')
callback(err, res, body);
if(res.statusCode != "401")
callback(err, res, body);
// If not check if callback is required
else if(typeof res.headers['www-authenticate'] === 'undefined') {
callback(err, res, body);
}
else {
var path=url.parse(options.url).path; // get path from url
var challenge = this._parseChallenge(res.headers['www-authenticate']);
var ha1 = crypto.createHash('md5');
ha1.update([ this.username, challenge.realm, this.password ].join(':'));
var ha2 = crypto.createHash('md5');
ha2.update([ options.method, path ].join(':'));

// Generate cnonce
var cnonce = false;
var nc = false;
if (typeof challenge.qop === 'string') {
var cnonceHash = crypto.createHash('md5');
cnonceHash.update(Math.random().toString(36));
cnonce = cnonceHash.digest('hex').substr(0, 8);
nc = this.updateNC();
}

// Generate response hash
var response = crypto.createHash('md5');
var responseParams = [ ha1.digest('hex'), challenge.nonce ];

if (cnonce) {
responseParams.push(nc);
responseParams.push(cnonce);
}

responseParams.push(challenge.qop);
responseParams.push(ha2.digest('hex'));
response.update(responseParams.join(':'));

// Setup response parameters
var authParams = {
username : this.username,
realm : challenge.realm,
nonce : challenge.nonce,
uri : path,
qop : challenge.qop,
response : response.digest('hex'),
opaque : challenge.opaque
};
if (cnonce) {
authParams.nc = nc;
authParams.cnonce = cnonce;
}

var headers = options.headers || {};
headers.Authorization = this._compileParams(authParams);
options.headers = headers;

request(options, function(err, res, body) {
callback(err, res, body);
});
}
};

//
// ## Parse challenge digest
//
HTTPDigest.prototype._parseChallenge = function parseChallenge(digest) {
var prefix = "Digest ";
var challenge = digest.substr(digest.indexOf(prefix) + prefix.length);
var parts = challenge.split(',');
var length = parts.length;
var params = {};
for (var i = 0; i < length; i++) {
var part = parts[i].match(/^\s*?([a-zA-Z0-0]+)="(.*)"\s*?$/);
if (part.length > 2) {
params[part[1]] = part[2];
}
}

return params;
};

//
// ## Compose authorization header
//
HTTPDigest.prototype._compileParams = function compileParams(params) {
var parts = [];
for ( var i in params) {
parts.push(i + '="' + params[i] + '"');
}
return 'Digest ' + parts.join(',');
};

//
// ## Update and zero pad nc
//
HTTPDigest.prototype.updateNC = function updateNC() {
var max = 99999999;
this.nc++;
if (this.nc > max) {
this.nc = 1;
}
var padding = new Array(8).join('0') + "";
var nc = this.nc + "";
return padding.substr(0, 8 - nc.length) + nc;
};

// Return response handler
return HTTPDigest;
}();

module.exports = function createDigestClient(username, password, https) {
return new HTTPDigest(username, password, https);
};
Loading