Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1360 from simov/refactor-auth
Refactor basic, bearer, digest auth logic into separate class
- Loading branch information
Showing
2 changed files
with
148 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
'use strict' | ||
|
||
var caseless = require('caseless') | ||
, uuid = require('node-uuid') | ||
, helpers = require('./helpers') | ||
|
||
var md5 = helpers.md5 | ||
, toBase64 = helpers.toBase64 | ||
|
||
|
||
function Auth () { | ||
// define all public properties here | ||
this.hasAuth = false | ||
this.sentAuth = false | ||
this.bearerToken = null | ||
this.user = null | ||
this.pass = null | ||
} | ||
|
||
Auth.prototype.basic = function (user, pass, sendImmediately) { | ||
var self = this | ||
if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) { | ||
throw new Error('auth() received invalid user or password') | ||
} | ||
self.user = user | ||
self.pass = pass | ||
self.hasAuth = true | ||
var header = typeof pass !== 'undefined' ? user + ':' + pass : user | ||
if (sendImmediately || typeof sendImmediately === 'undefined') { | ||
var authHeader = 'Basic ' + toBase64(header) | ||
self.sentAuth = true | ||
return authHeader | ||
} | ||
} | ||
|
||
Auth.prototype.bearer = function (bearer, sendImmediately) { | ||
var self = this | ||
self.bearerToken = bearer | ||
self.hasAuth = true | ||
if (sendImmediately || typeof sendImmediately === 'undefined') { | ||
if (typeof bearer === 'function') { | ||
bearer = bearer() | ||
} | ||
var authHeader = 'Bearer ' + bearer | ||
self.sentAuth = true | ||
return authHeader | ||
} | ||
} | ||
|
||
Auth.prototype.digest = function (method, path, authHeader) { | ||
// TODO: More complete implementation of RFC 2617. | ||
// - check challenge.algorithm | ||
// - support algorithm="MD5-sess" | ||
// - handle challenge.domain | ||
// - support qop="auth-int" only | ||
// - handle Authentication-Info (not necessarily?) | ||
// - check challenge.stale (not necessarily?) | ||
// - increase nc (not necessarily?) | ||
// For reference: | ||
// http://tools.ietf.org/html/rfc2617#section-3 | ||
// https://github.com/bagder/curl/blob/master/lib/http_digest.c | ||
|
||
var self = this | ||
|
||
var challenge = {} | ||
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi | ||
for (;;) { | ||
var match = re.exec(authHeader) | ||
if (!match) { | ||
break | ||
} | ||
challenge[match[1]] = match[2] || match[3] | ||
} | ||
|
||
var ha1 = md5(self.user + ':' + challenge.realm + ':' + self.pass) | ||
var ha2 = md5(method + ':' + path) | ||
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth' | ||
var nc = qop && '00000001' | ||
var cnonce = qop && uuid().replace(/-/g, '') | ||
var digestResponse = qop | ||
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2) | ||
: md5(ha1 + ':' + challenge.nonce + ':' + ha2) | ||
var authValues = { | ||
username: self.user, | ||
realm: challenge.realm, | ||
nonce: challenge.nonce, | ||
uri: path, | ||
qop: qop, | ||
response: digestResponse, | ||
nc: nc, | ||
cnonce: cnonce, | ||
algorithm: challenge.algorithm, | ||
opaque: challenge.opaque | ||
} | ||
|
||
authHeader = [] | ||
for (var k in authValues) { | ||
if (authValues[k]) { | ||
if (k === 'qop' || k === 'nc' || k === 'algorithm') { | ||
authHeader.push(k + '=' + authValues[k]) | ||
} else { | ||
authHeader.push(k + '="' + authValues[k] + '"') | ||
} | ||
} | ||
} | ||
authHeader = 'Digest ' + authHeader.join(', ') | ||
self.sentAuth = true | ||
return authHeader | ||
} | ||
|
||
Auth.prototype.response = function (method, path, headers) { | ||
var self = this | ||
if (!self.hasAuth || self.sentAuth) { return null } | ||
|
||
var c = caseless(headers) | ||
|
||
var authHeader = c.get('www-authenticate') | ||
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase() | ||
// debug('reauth', authVerb) | ||
|
||
switch (authVerb) { | ||
case 'basic': | ||
return self.basic(self.user, self.pass, true) | ||
|
||
case 'bearer': | ||
return self.bearer(self.bearerToken, true) | ||
|
||
case 'digest': | ||
return self.digest(method, path, authHeader) | ||
} | ||
} | ||
|
||
exports.Auth = Auth |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters