3 var caseless = require('caseless')
4 , uuid = require('uuid')
5 , helpers = require('./helpers')
8 , toBase64 = helpers.toBase64
11 function Auth (request) {
12 // define all public properties here
13 this.request = request
16 this.bearerToken = null
21 Auth.prototype.basic = function (user, pass, sendImmediately) {
23 if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
24 self.request.emit('error', new Error('auth() received invalid user or password'))
29 var header = user + ':' + (pass || '')
30 if (sendImmediately || typeof sendImmediately === 'undefined') {
31 var authHeader = 'Basic ' + toBase64(header)
37 Auth.prototype.bearer = function (bearer, sendImmediately) {
39 self.bearerToken = bearer
41 if (sendImmediately || typeof sendImmediately === 'undefined') {
42 if (typeof bearer === 'function') {
45 var authHeader = 'Bearer ' + (bearer || '')
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?)
59 // http://tools.ietf.org/html/rfc2617#section-3
60 // https://github.com/bagder/curl/blob/master/lib/http_digest.c
65 var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
67 var match = re.exec(authHeader)
71 challenge[match[1]] = match[2] || match[3]
75 * RFC 2617: handle both MD5 and MD5-sess algorithms.
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)
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)
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)
101 realm: challenge.realm,
102 nonce: challenge.nonce,
105 response: digestResponse,
108 algorithm: challenge.algorithm,
109 opaque: challenge.opaque
113 for (var k in authValues) {
115 if (k === 'qop' || k === 'nc' || k === 'algorithm') {
116 authHeader.push(k + '=' + authValues[k])
118 authHeader.push(k + '="' + authValues[k] + '"')
122 authHeader = 'Digest ' + authHeader.join(', ')
127 Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
129 , request = self.request
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)
137 authHeader = self.basic(user, pass, sendImmediately)
140 request.setHeader('authorization', authHeader)
144 Auth.prototype.onResponse = function (response) {
146 , request = self.request
148 if (!self.hasAuth || self.sentAuth) { return null }
150 var c = caseless(response.headers)
152 var authHeader = c.get('www-authenticate')
153 var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
154 request.debug('reauth', authVerb)
158 return self.basic(self.user, self.pass, true)
161 return self.bearer(self.bearerToken, true)
164 return self.digest(request.method, request.path, authHeader)