diff --git a/lib/passport-http/strategies/digest.js b/lib/passport-http/strategies/digest.js index f0433e8..afa1cea 100644 --- a/lib/passport-http/strategies/digest.js +++ b/lib/passport-http/strategies/digest.js @@ -80,6 +80,7 @@ function DigestStrategy(options, secret, validate) { if (options.qop) { this._qop = (Array.isArray(options.qop)) ? options.qop : [ options.qop ]; } + this._nonce = options.nonce || function() { return nonce(32); }; } /** @@ -96,7 +97,7 @@ util.inherits(DigestStrategy, passport.Strategy); */ DigestStrategy.prototype.authenticate = function(req) { var authorization = req.headers['authorization']; - if (!authorization) { return this.fail(this._challenge()); } + if (!authorization) { return this.fail(this._challenge(req)); } var parts = authorization.split(' ') if (parts.length < 2) { return this.fail(400); } @@ -104,13 +105,13 @@ DigestStrategy.prototype.authenticate = function(req) { var scheme = parts[0] , params = parts.slice(1).join(' '); - if (!/Digest/i.test(scheme)) { return this.fail(this._challenge()); } + if (!/Digest/i.test(scheme)) { return this.fail(this._challenge(req)); } var creds = parse(params); if (Object.keys(creds).length === 0) { return this.fail(400); } if (!creds.username) { - return this.fail(this._challenge()); + return this.fail(this._challenge(req)); } if (req.url !== creds.uri) { return this.fail(400); @@ -130,7 +131,7 @@ DigestStrategy.prototype.authenticate = function(req) { // value. this._secret(creds.username, function(err, user, password) { if (err) { return self.error(err); } - if (!user) { return self.fail(self._challenge()); } + if (!user) { return self.fail(self._challenge(req)); } var ha1; if (!creds.algorithm || creds.algorithm === 'MD5') { @@ -177,7 +178,7 @@ DigestStrategy.prototype.authenticate = function(req) { } if (creds.response != digest) { - return self.fail(self._challenge()); + return self.fail(self._challenge(req)); } else { if (self._validate) { self._validate({ @@ -188,7 +189,7 @@ DigestStrategy.prototype.authenticate = function(req) { }, function(err, valid) { if (err) { return self.error(err); } - if (!valid) { return self.fail(self._challenge()); } + if (!valid) { return self.fail(self._challenge(req)); } self.success(user); }); } else { @@ -203,7 +204,7 @@ DigestStrategy.prototype.authenticate = function(req) { * * @api private */ -DigestStrategy.prototype._challenge = function() { +DigestStrategy.prototype._challenge = function(req) { // TODO: For maximum flexibility, a mechanism for delegating the generation // of the nonce and opaque data to the application would be useful. @@ -211,9 +212,11 @@ DigestStrategy.prototype._challenge = function() { if (this._domain) { challenge += ', domain="' + this._domain.join(' ') + '"'; } - challenge += ', nonce="' + nonce(32) + '"'; + var nonce = (typeof this._nonce == 'function') ? this._nonce(req) : this._nonce; + challenge += ', nonce="' + nonce + '"'; if (this._opaque) { - challenge += ', opaque="' + this._opaque + '"'; + var opaque = (typeof this._opaque == 'function') ? this._opaque(req) : this._opaque; + challenge += ', opaque="' + opaque + '"'; } if (this._algorithm) { challenge += ', algorithm=' + this._algorithm; diff --git a/test/strategies/digest-test.js b/test/strategies/digest-test.js index 28115ec..978955a 100644 --- a/test/strategies/digest-test.js +++ b/test/strategies/digest-test.js @@ -1163,6 +1163,85 @@ vows.describe('DigestStrategy').addBatch({ }, }, + 'strategy handling a request without authorization credentials with nonce option set': { + topic: function() { + var strategy = new DigestStrategy({ nonce: '8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC' }, + function(username, done) { + done(null, { username: username }, 'secret'); + }, + function(options, done) { + done(null, true); + } + ); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.url = '/'; + req.headers = {}; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.match(challenge, /^Digest realm="Users", nonce="8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC"$/); + }, + }, + }, + + 'strategy handling a request without authorization credentials with nonce function option set': { + topic: function() { + var strategy = new DigestStrategy({ nonce: function(req) { return req.ip; } }, + function(username, done) { + done(null, { username: username }, 'secret'); + }, + function(options, done) { + done(null, true); + } + ); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.ip = '192.168.1.66'; + req.url = '/'; + req.headers = {}; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.match(challenge, /^Digest realm="Users", nonce="192.168.1.66"$/); + }, + }, + }, + 'strategy handling a request without authorization credentials with opaque option set': { topic: function() { var strategy = new DigestStrategy({ opaque: 'abcdefg1234' }, @@ -1202,6 +1281,46 @@ vows.describe('DigestStrategy').addBatch({ }, }, + 'strategy handling a request without authorization credentials with opaque function option set': { + topic: function() { + var strategy = new DigestStrategy({ opaque: function(req) { return req.ip; } }, + function(username, done) { + done(null, { username: username }, 'secret'); + }, + function(options, done) { + done(null, true); + } + ); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.ip = '192.168.1.66'; + req.url = '/'; + req.headers = {}; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="192.168.1.66"$/); + }, + }, + }, + 'strategy handling a request without authorization credentials with algorithm option set': { topic: function() { var strategy = new DigestStrategy({ algorithm: 'MD5-sess' }, @@ -1318,7 +1437,7 @@ vows.describe('DigestStrategy').addBatch({ }, }, }, - + 'strategy constructed without a secret callback or validate callback': { 'should throw an error': function (strategy) { assert.throws(function() { new DigestStrategy() });