Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81141 views
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
// 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
// - 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
112
Auth.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
129
Auth.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
153
exports.Auth = Auth
154
155