twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

auth.js (4778B)


      1 'use strict'
      2 
      3 var caseless = require('caseless')
      4 var uuid = require('uuid/v4')
      5 var helpers = require('./helpers')
      6 
      7 var md5 = helpers.md5
      8 var toBase64 = helpers.toBase64
      9 
     10 function Auth (request) {
     11   // define all public properties here
     12   this.request = request
     13   this.hasAuth = false
     14   this.sentAuth = false
     15   this.bearerToken = null
     16   this.user = null
     17   this.pass = null
     18 }
     19 
     20 Auth.prototype.basic = function (user, pass, sendImmediately) {
     21   var self = this
     22   if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
     23     self.request.emit('error', new Error('auth() received invalid user or password'))
     24   }
     25   self.user = user
     26   self.pass = pass
     27   self.hasAuth = true
     28   var header = user + ':' + (pass || '')
     29   if (sendImmediately || typeof sendImmediately === 'undefined') {
     30     var authHeader = 'Basic ' + toBase64(header)
     31     self.sentAuth = true
     32     return authHeader
     33   }
     34 }
     35 
     36 Auth.prototype.bearer = function (bearer, sendImmediately) {
     37   var self = this
     38   self.bearerToken = bearer
     39   self.hasAuth = true
     40   if (sendImmediately || typeof sendImmediately === 'undefined') {
     41     if (typeof bearer === 'function') {
     42       bearer = bearer()
     43     }
     44     var authHeader = 'Bearer ' + (bearer || '')
     45     self.sentAuth = true
     46     return authHeader
     47   }
     48 }
     49 
     50 Auth.prototype.digest = function (method, path, authHeader) {
     51   // TODO: More complete implementation of RFC 2617.
     52   //   - handle challenge.domain
     53   //   - support qop="auth-int" only
     54   //   - handle Authentication-Info (not necessarily?)
     55   //   - check challenge.stale (not necessarily?)
     56   //   - increase nc (not necessarily?)
     57   // For reference:
     58   // http://tools.ietf.org/html/rfc2617#section-3
     59   // https://github.com/bagder/curl/blob/master/lib/http_digest.c
     60 
     61   var self = this
     62 
     63   var challenge = {}
     64   var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
     65   while (true) {
     66     var match = re.exec(authHeader)
     67     if (!match) {
     68       break
     69     }
     70     challenge[match[1]] = match[2] || match[3]
     71   }
     72 
     73   /**
     74    * RFC 2617: handle both MD5 and MD5-sess algorithms.
     75    *
     76    * If the algorithm directive's value is "MD5" or unspecified, then HA1 is
     77    *   HA1=MD5(username:realm:password)
     78    * If the algorithm directive's value is "MD5-sess", then HA1 is
     79    *   HA1=MD5(MD5(username:realm:password):nonce:cnonce)
     80    */
     81   var ha1Compute = function (algorithm, user, realm, pass, nonce, cnonce) {
     82     var ha1 = md5(user + ':' + realm + ':' + pass)
     83     if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
     84       return md5(ha1 + ':' + nonce + ':' + cnonce)
     85     } else {
     86       return ha1
     87     }
     88   }
     89 
     90   var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
     91   var nc = qop && '00000001'
     92   var cnonce = qop && uuid().replace(/-/g, '')
     93   var ha1 = ha1Compute(challenge.algorithm, self.user, challenge.realm, self.pass, challenge.nonce, cnonce)
     94   var ha2 = md5(method + ':' + path)
     95   var digestResponse = qop
     96     ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
     97     : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
     98   var authValues = {
     99     username: self.user,
    100     realm: challenge.realm,
    101     nonce: challenge.nonce,
    102     uri: path,
    103     qop: qop,
    104     response: digestResponse,
    105     nc: nc,
    106     cnonce: cnonce,
    107     algorithm: challenge.algorithm,
    108     opaque: challenge.opaque
    109   }
    110 
    111   authHeader = []
    112   for (var k in authValues) {
    113     if (authValues[k]) {
    114       if (k === 'qop' || k === 'nc' || k === 'algorithm') {
    115         authHeader.push(k + '=' + authValues[k])
    116       } else {
    117         authHeader.push(k + '="' + authValues[k] + '"')
    118       }
    119     }
    120   }
    121   authHeader = 'Digest ' + authHeader.join(', ')
    122   self.sentAuth = true
    123   return authHeader
    124 }
    125 
    126 Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
    127   var self = this
    128   var request = self.request
    129 
    130   var authHeader
    131   if (bearer === undefined && user === undefined) {
    132     self.request.emit('error', new Error('no auth mechanism defined'))
    133   } else if (bearer !== undefined) {
    134     authHeader = self.bearer(bearer, sendImmediately)
    135   } else {
    136     authHeader = self.basic(user, pass, sendImmediately)
    137   }
    138   if (authHeader) {
    139     request.setHeader('authorization', authHeader)
    140   }
    141 }
    142 
    143 Auth.prototype.onResponse = function (response) {
    144   var self = this
    145   var request = self.request
    146 
    147   if (!self.hasAuth || self.sentAuth) { return null }
    148 
    149   var c = caseless(response.headers)
    150 
    151   var authHeader = c.get('www-authenticate')
    152   var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
    153   request.debug('reauth', authVerb)
    154 
    155   switch (authVerb) {
    156     case 'basic':
    157       return self.basic(self.user, self.pass, true)
    158 
    159     case 'bearer':
    160       return self.bearer(self.bearerToken, true)
    161 
    162     case 'digest':
    163       return self.digest(request.method, request.path, authHeader)
    164   }
    165 }
    166 
    167 exports.Auth = Auth