1 | 'use strict'
|
2 |
|
3 | var caseless = require('caseless')
|
4 | , uuid = require('node-uuid')
|
5 | , helpers = require('./helpers')
|
6 |
|
7 | var md5 = helpers.md5
|
8 | , toBase64 = helpers.toBase64
|
9 |
|
10 |
|
11 | function Auth (request) {
|
12 |
|
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 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
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 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
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
|