Spaces:
Configuration error
Configuration error
var caseless = require('caseless') | |
var { v4: uuid } = require('uuid') | |
var helpers = require('./helpers') | |
var md5 = helpers.md5 | |
var toBase64 = helpers.toBase64 | |
function Auth (request) { | |
// define all public properties here | |
this.request = request | |
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')) { | |
self.request.emit('error', new Error('auth() received invalid user or password')) | |
} | |
self.user = user | |
self.pass = pass | |
self.hasAuth = true | |
var header = user + ':' + (pass || '') | |
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. | |
// - 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 | |
while (true) { | |
var match = re.exec(authHeader) | |
if (!match) { | |
break | |
} | |
challenge[match[1]] = match[2] || match[3] | |
} | |
/** | |
* RFC 2617: handle both MD5 and MD5-sess algorithms. | |
* | |
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is | |
* HA1=MD5(username:realm:password) | |
* If the algorithm directive's value is "MD5-sess", then HA1 is | |
* HA1=MD5(MD5(username:realm:password):nonce:cnonce) | |
*/ | |
var ha1Compute = function (algorithm, user, realm, pass, nonce, cnonce) { | |
var ha1 = md5(user + ':' + realm + ':' + pass) | |
if (algorithm && algorithm.toLowerCase() === 'md5-sess') { | |
return md5(ha1 + ':' + nonce + ':' + cnonce) | |
} else { | |
return ha1 | |
} | |
} | |
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth' | |
var nc = qop && '00000001' | |
var cnonce = qop && uuid().replace(/-/g, '') | |
var ha1 = ha1Compute(challenge.algorithm, self.user, challenge.realm, self.pass, challenge.nonce, cnonce) | |
var ha2 = md5(method + ':' + path) | |
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.onRequest = function (user, pass, sendImmediately, bearer) { | |
var self = this | |
var request = self.request | |
var authHeader | |
if (bearer === undefined && user === undefined) { | |
self.request.emit('error', new Error('no auth mechanism defined')) | |
} else if (bearer !== undefined) { | |
authHeader = self.bearer(bearer, sendImmediately) | |
} else { | |
authHeader = self.basic(user, pass, sendImmediately) | |
} | |
if (authHeader) { | |
request.setHeader('authorization', authHeader) | |
} | |
} | |
Auth.prototype.onResponse = function (response) { | |
var self = this | |
var request = self.request | |
if (!self.hasAuth || self.sentAuth) { return null } | |
var c = caseless(response.headers) | |
var authHeader = c.get('www-authenticate') | |
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase() | |
request.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(request.method, request.path, authHeader) | |
} | |
} | |
exports.Auth = Auth | |