diff --git a/digest-fetch-src.js b/digest-fetch-src.js index 85b5e20..26d623e 100644 --- a/digest-fetch-src.js +++ b/digest-fetch-src.js @@ -12,6 +12,14 @@ const base64 = require('base-64') const supported_algorithms = ['MD5', 'MD5-sess'] +// challenge = auth-scheme 1*SP 1#auth-param + +const tokenRegexp = '[!#-\'*+-.0-9A-Z^_`a-z~]+' +const schemeRegexp = tokenRegexp +const paramRegexp = tokenRegexp+'='+'('+tokenRegexp+'|"([^"\\\\]|\\\\.)*")' + +const challengeRegexp = new RegExp('^(('+schemeRegexp+') +'+paramRegexp+'( *, *'+paramRegexp+')*) *(, *'+schemeRegexp+' .*)?$') + const parse = (raw, field, trim=true) => { const regex = new RegExp(`${field}=("[^"]*"|[^,]*)`, "i") const match = regex.exec(raw) @@ -117,7 +125,7 @@ class DigestClient { const opaqueString = this.digest.opaque !== null ? `opaque="${this.digest.opaque}",` : '' const qopString = this.digest.qop ? `qop="${this.digest.qop}",` : '' - const digest = `${this.digest.scheme} username="${this.user}",realm="${this.digest.realm}",\ + const digest = `Digest username="${this.user}",realm="${this.digest.realm}",\ nonce="${this.digest.nonce}",uri="${uri}",${opaqueString}${qopString}\ algorithm="${this.digest.algorithm}",response="${response}",nc=${ncString},cnonce="${this.digest.cnonce}"` options.headers = options.headers || {} @@ -140,19 +148,38 @@ algorithm="${this.digest.algorithm}",response="${response}",nc=${ncString},cnonc } this.hasAuth = true - - this.digest.scheme = h.split(/\s/)[0] + while (true) { + const challengeMatch = h.match(challengeRegexp) + if (!challengeMatch) { + break + } + const challenge = challengeMatch[1] + const scheme = challengeMatch[2] + + if (scheme.match(/^Digest$/i)) { - this.digest.realm = (parse(h, 'realm', false) || '').replace(/["]/g, '') + this.digest.realm = (parse(challenge, 'realm', false) || '').replace(/["]/g, '') - this.digest.qop = this.parseQop(h) + this.digest.qop = this.parseQop(challenge) - this.digest.opaque = parse(h, 'opaque') - - this.digest.nonce = parse(h, 'nonce') || '' + this.digest.opaque = parse(challenge, 'opaque') - this.digest.cnonce = this.makeNonce() - this.digest.nc++ + this.digest.nonce = parse(challenge, 'nonce') || '' + + this.digest.cnonce = this.makeNonce() + this.digest.nc++ + } + h = h.substr(challenge.length) + if (!h) { + break + } + const comma = h.match("^( *, *).*") + if (!comma) { + // TODO: parse error + break + } + h = h.substr(comma[1].length) + } } parseQop (rawAuth) { diff --git a/test/digest-fetch-basic.js b/test/digest-fetch-basic.js index a6c62a3..0b7bc21 100644 --- a/test/digest-fetch-basic.js +++ b/test/digest-fetch-basic.js @@ -13,18 +13,40 @@ var app = factory.getApp() describe('digest-fetch', function(){ - it('Test Basic Authentication', function() { + it('Test Basic Authentication', function(done) { var client = new DigestFetch('test', 'test', { basic: true }) const auth = client.addBasicAuth().headers.Authorization chai.request(app).get('/basic').set('Authorization', auth).then(res => { expect(res).to.have.status(200) + done() }) + .catch(done) }) - it('Test Basic Authentication with wrong credential', function() { + it('Test Basic Authentication with wrong credential', function(done) { var client = new DigestFetch('test', 'test-null', { basic: true }) const auth = client.addBasicAuth().headers.Authorization chai.request(app).get('/basic').set('Authorization', auth).then(res => { expect(res).to.have.status(401) + done() }) + .catch(done) + }) + it('Test Basic Authentication with basic-and-digest', function(done) { + var client = new DigestFetch('test', 'test', { basic: true }) + const auth = client.addBasicAuth().headers.Authorization + chai.request(app).get('/basic-and-digest').set('Authorization', auth).then(res => { + expect(res).to.have.status(200) + done() + }) + .catch(done) + }) + it('Test Basic Authentication with digest-and-basic', function(done) { + var client = new DigestFetch('test', 'test', { basic: true }) + const auth = client.addBasicAuth().headers.Authorization + chai.request(app).get('/digest-and-basic').set('Authorization', auth).then(res => { + expect(res).to.have.status(200) + done() + }) + .catch(done) }) }) diff --git a/test/digest-fetch-rfc2069.js b/test/digest-fetch-rfc2069.js index ff8d6db..6ca3b56 100644 --- a/test/digest-fetch-rfc2069.js +++ b/test/digest-fetch-rfc2069.js @@ -13,7 +13,7 @@ var app = factory.getApp() describe('digest-fetch', function(){ - it('Test RFC2069', function() { + it('Test RFC2069', function(done) { var client = new DigestFetch('test', 'test') chai.request(app).get('/auth').then(res => { expect(res).to.have.status(401) @@ -24,11 +24,14 @@ describe('digest-fetch', function(){ const auth = client.addAuth('/auth', { method: 'GET' }).headers.Authorization chai.request(app).get('/auth').set('Authorization', auth).then(res => { expect(res).to.have.status(200) + done() }) + .catch(done) }) + .catch(done) }) - it('Test RFC2069 with wrong credential', function() { + it('Test RFC2069 with wrong credential', function(done) { var client = new DigestFetch('test', 'test-null') chai.request(app).get('/auth').then(res => { res.should.have.status(401) @@ -39,7 +42,10 @@ describe('digest-fetch', function(){ const auth = client.addAuth('/auth', { method: 'GET' }).headers.Authorization chai.request(app).get('/auth').set('Authorization', auth).then(res => { expect(res).to.have.status(401) + done() }) + .catch(done) }) + .catch(done) }) }) diff --git a/test/digest-fetch-rfc2617.js b/test/digest-fetch-rfc2617.js index 747e596..f3a4ab3 100644 --- a/test/digest-fetch-rfc2617.js +++ b/test/digest-fetch-rfc2617.js @@ -13,7 +13,7 @@ var app = factory.getApp('auth') describe('digest-fetch', function(){ - it('Test RFC2617', function() { + it('Test RFC2617', function(done) { var client = new DigestFetch('test', 'test') chai.request(app).get('/auth').then(res => { expect(res).to.have.status(401) @@ -24,11 +24,14 @@ describe('digest-fetch', function(){ const auth = client.addAuth('/auth', { method: 'GET' }).headers.Authorization chai.request(app).get('/auth').set('Authorization', auth).then(res => { expect(res).to.have.status(200) + done() }) + .catch(done) }) + .catch(done) }) - it('Test RFC2617 with precomputed hash', function() { + it('Test RFC2617 with precomputed hash', function(done) { var client = new DigestFetch('test', DigestFetch.computeHash('test', 'Users', 'test'), { precomputedHash: true }) chai.request(app).get('/auth').then(res => { expect(res).to.have.status(401) @@ -39,11 +42,14 @@ describe('digest-fetch', function(){ const auth = client.addAuth('/auth', { method: 'GET' }).headers.Authorization chai.request(app).get('/auth').set('Authorization', auth).then(res => { expect(res).to.have.status(200) + done() }) + .catch(done) }) + .catch(done) }) - it('Test RFC2617 with wrong credential', function() { + it('Test RFC2617 with wrong credential', function(done) { var client = new DigestFetch('test', 'test-null') chai.request(app).get('/auth').then(res => { expect(res).to.have.status(401) @@ -54,7 +60,48 @@ describe('digest-fetch', function(){ const auth = client.addAuth('/auth', { method: 'GET' }).headers.Authorization chai.request(app).get('/auth').set('Authorization', auth).then(res => { expect(res).to.have.status(401) + done() }) + .catch(done) }) + .catch(done) + }) + + it('Test RFC2617 with basic-and-digest', function(done) { + var client = new DigestFetch('test', 'test') + chai.request(app).get('/basic-and-digest').then(res => { + expect(res).to.have.status(401) + client.lastAuth = res.res.headers['www-authenticate'] + }) + .then(() => { + client.parseAuth(client.lastAuth) + const auth = client.addAuth('/basic-and-digest', { method: 'GET' }).headers.Authorization + expect(auth).to.match(/^Digest /) + chai.request(app).get('/basic-and-digest').set('Authorization', auth).then(res => { + expect(res).to.have.status(200) + done() + }) + .catch(done) + }) + .catch(done) + }) + + it('Test RFC2617 with digest-and-basic', function(done) { + var client = new DigestFetch('test', 'test') + chai.request(app).get('/digest-and-basic').then(res => { + expect(res).to.have.status(401) + client.lastAuth = res.res.headers['www-authenticate'] + }) + .then(() => { + client.parseAuth(client.lastAuth) + const auth = client.addAuth('/digest-and-basic', { method: 'GET' }).headers.Authorization + expect(auth).to.match(/^Digest /) + chai.request(app).get('/digest-and-basic').set('Authorization', auth).then(res => { + expect(res).to.have.status(200) + done() + }) + .catch(done) + }) + .catch(done) }) }) diff --git a/test/digest-fetch.js b/test/digest-fetch.js index 5804f2b..50b62f2 100644 --- a/test/digest-fetch.js +++ b/test/digest-fetch.js @@ -24,9 +24,9 @@ describe('digest-fetch', function(){ assert.equal(DigestFetch.parse('a="v2",', 'a'), 'v2') assert.equal(DigestFetch.parse('a="v1,v2"', 'a'), 'v1,v2') const client = new DigestFetch("", "") - client.parseAuth('qop=auth-int,realm=test') + client.parseAuth('Digest qop=auth-int,realm=test',1) assert.equal(client.digest.realm, "test") - client.parseAuth('qop="auth",realm="v1 v2"') + client.parseAuth('Digest qop="auth",realm="v1 v2"') assert.equal(client.digest.realm, "v1 v2") }) diff --git a/test/test-server.js b/test/test-server.js index 611195f..a9d4684 100644 --- a/test/test-server.js +++ b/test/test-server.js @@ -53,6 +53,18 @@ module.exports = { getApp(method) { function(req, res) { res.json(req.user); }); + // http basic and digest authentication + app.get('/basic-and-digest', + passport.authenticate(['basic', 'digest'], { session: false }), + function(req, res) { + res.json(req.user); + }); + // http digest and basic authentication + app.get('/digest-and-basic', + passport.authenticate(['digest','basic'], { session: false }), + function(req, res) { + res.json(req.user); + }); // app.use("/static", express.static(path.join(__dirname, './static')));