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