UNPKG

4.21 kBJavaScriptView Raw
1'use strict'
2
3var caseless = require('caseless')
4 , uuid = require('node-uuid')
5 , helpers = require('./helpers')
6
7var md5 = helpers.md5
8 , toBase64 = helpers.toBase64
9
10
11function 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
21Auth.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
37Auth.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
51Auth.prototype.digest = function (method, path, authHeader) {
52 // TODO: More complete implementation of RFC 2617.
53 // - check challenge.algorithm
54 // - support algorithm="MD5-sess"
55 // - handle challenge.domain
56 // - support qop="auth-int" only
57 // - handle Authentication-Info (not necessarily?)
58 // - check challenge.stale (not necessarily?)
59 // - increase nc (not necessarily?)
60 // For reference:
61 // http://tools.ietf.org/html/rfc2617#section-3
62 // https://github.com/bagder/curl/blob/master/lib/http_digest.c
63
64 var self = this
65
66 var challenge = {}
67 var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
68 for (;;) {
69 var match = re.exec(authHeader)
70 if (!match) {
71 break
72 }
73 challenge[match[1]] = match[2] || match[3]
74 }
75
76 var ha1 = md5(self.user + ':' + challenge.realm + ':' + self.pass)
77 var ha2 = md5(method + ':' + path)
78 var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
79 var nc = qop && '00000001'
80 var cnonce = qop && uuid().replace(/-/g, '')
81 var digestResponse = qop
82 ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
83 : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
84 var authValues = {
85 username: self.user,
86 realm: challenge.realm,
87 nonce: challenge.nonce,
88 uri: path,
89 qop: qop,
90 response: digestResponse,
91 nc: nc,
92 cnonce: cnonce,
93 algorithm: challenge.algorithm,
94 opaque: challenge.opaque
95 }
96
97 authHeader = []
98 for (var k in authValues) {
99 if (authValues[k]) {
100 if (k === 'qop' || k === 'nc' || k === 'algorithm') {
101 authHeader.push(k + '=' + authValues[k])
102 } else {
103 authHeader.push(k + '="' + authValues[k] + '"')
104 }
105 }
106 }
107 authHeader = 'Digest ' + authHeader.join(', ')
108 self.sentAuth = true
109 return authHeader
110}
111
112Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
113 var self = this
114 , request = self.request
115
116 var authHeader
117 if (bearer === undefined && user === undefined) {
118 self.request.emit('error', new Error('no auth mechanism defined'))
119 } else if (bearer !== undefined) {
120 authHeader = self.bearer(bearer, sendImmediately)
121 } else {
122 authHeader = self.basic(user, pass, sendImmediately)
123 }
124 if (authHeader) {
125 request.setHeader('authorization', authHeader)
126 }
127}
128
129Auth.prototype.onResponse = function (response) {
130 var self = this
131 , request = self.request
132
133 if (!self.hasAuth || self.sentAuth) { return null }
134
135 var c = caseless(response.headers)
136
137 var authHeader = c.get('www-authenticate')
138 var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
139 request.debug('reauth', authVerb)
140
141 switch (authVerb) {
142 case 'basic':
143 return self.basic(self.user, self.pass, true)
144
145 case 'bearer':
146 return self.bearer(self.bearerToken, true)
147
148 case 'digest':
149 return self.digest(request.method, request.path, authHeader)
150 }
151}
152
153exports.Auth = Auth