You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							167 lines
						
					
					
						
							4.7 KiB
						
					
					
				
			
		
		
	
	
							167 lines
						
					
					
						
							4.7 KiB
						
					
					
				'use strict' | 
						|
 | 
						|
var caseless = require('caseless') | 
						|
var uuid = require('uuid/v4') | 
						|
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
 | 
						|
 |