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.
		
		
		
		
			
				
					168 lines
				
				4.7 KiB
			
		
		
			
		
	
	
					168 lines
				
				4.7 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								'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
							 |