All files digest-fetch-src.js

28.77% Statements 21/73
25% Branches 11/44
60% Functions 3/5
29.23% Lines 19/65
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 1031x 1x   1x       1x 1x 1x 1x 1x 1x 1x 1x                                           1x 1x                                                   1x 1x   1x 1x 1x                                                                 1x  
const canRequire = typeof(require) == 'function'
Eif (typeof(fetch) !== 'function' && canRequire) var fetch = require('node-fetch')
// if (typeof(cryptojs) !== 'function' && canRequire) var cryptojs = require('crypto-js')
const cryptojs = require('crypto-js')
 
class DigestClient {
  constructor(user, password, options={}) {
    this.user = user
    this.password = password
    this.nonceRaw = 'abcdef0123456789'
    this.digest = { nc: 0, algorithm: options.algorithm || 'MD5' }
    this.hasAuth = false
    const _cnonceSize = parseInt(options.cnonceSize)
    this.cnonceSize = isNaN(_cnonceSize) ? 32 : _cnonceSize // cnonce length 32 as default
    this.logger = options.logger
  }
 
  async fetch (url, options={}) {
    const resp = await fetch(url, this.addAuth(url, options))
    if (resp.status == 401) {
      this.hasAuth = false
      await this.parseAuth(resp)
      if (this.hasAuth) {
        const respFinal = await fetch(url, this.addAuth(url, options))
        if (respFinal.status == 401) {
          this.hasAuth = false
        } else {
          this.digest.nc++
        }
        return respFinal
      }
    } else this.digest.nc++
    return resp
  }
 
  addAuth (url, options) {
    Iif (typeof(options.factory) == 'function') options = options.factory()
    Eif (!this.hasAuth) return options
    if (this.logger) this.logger.info(`requesting with auth carried`)
    const _url = url.replace('//', '')
    const uri = _url.indexOf('/') == -1 ? '/' : _url.slice(_url.indexOf('/'))
    const method = options.method ? options.method.toUpperCase() : 'GET'
    const ha1 = cryptojs.MD5(`${this.user}:${this.digest.realm}:${this.password}`).toString()
    const ha2 = cryptojs.MD5(`${method}:${uri}`).toString()
    const ncString = ('00000000'+this.digest.nc).slice(-8)
    const response = cryptojs.MD5(`${ha1}:${this.digest.nonce}:${ncString}:${this.digest.cnonce}:${this.digest.qop}:${ha2}`).toString()
    const opaqueString = this.digest.opaque? `opaque="${this.digest.opaque}",` : ''
    const digest = `${this.digest.scheme} username="${this.user}",realm="${this.digest.realm}",\
nonce="${this.digest.nonce}",uri="${uri}",${opaqueString}\
qop=${this.digest.qop},algorithm="{this.digest.algorithm}",response="${response}",nc=${ncString},cnonce="${this.digest.cnonce}"`
    options.headers = options.headers || {}
    options.headers.Authorization = digest
    if (this.logger) {
      this.logger.info(options)
    }
    // const {factory, ..._options} = options
    const _options = {}
    Object.assign(_options, options)
    delete _options.factory
    return _options;
  }
 
  async parseAuth (data) {
    const h = data.headers['www-authenticate']
    this.lastAuth = h
 
    Eif (!h || h.length < 5) {
      this.hasAuth = false
      return
    }
 
    this.hasAuth = true
    
    this.digest.scheme = h.split(/\s/)[0]
 
    const _realm = /realm=\"([^\"]+)\"/i.exec(h) 
    if (_realm) this.digest.realm = _realm[1]
 
    const _qop = /qop=\"([^\"]+)\"/i.exec(h) 
    if (_qop) this.digest.qop = _qop[1]
 
    const _opaque = /opaque=\"([^\"]+)\"/i.exec(h) 
    if (_opaque) this.digest.opaque = _opaque[1]
    
    const _nonce = /nonce=\"([^\"]+)\"/i.exec(h) 
    if (_nonce) this.digest.nonce = _nonce[1]
 
    this.digest.cnonce = this.makeNonce()
    this.digest.nc++
  }
 
  makeNonce () {
    let uid = ''
    for (let i = 0; i < this.cnonceSize; ++i) {
      uid += this.nonceRaw[Math.floor(Math.random() * this.nonceRaw.length)];
    }
    return uid
  }
 
}
 
module.exports = DigestClient