Skip to content

Commit a6741d4

Browse files
hueniversemikeal
authored andcommitted
Replace hawk dependency with a local implemenation (#2943)
* Replace hawk dependency with local implementation * Fix access * Fix access * Improve coverage * Improve coverage * Improve coverage * Fix access * Fix style
1 parent a7f0a36 commit a6741d4

4 files changed

Lines changed: 246 additions & 26 deletions

File tree

lib/hawk.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict'
2+
3+
var crypto = require('crypto')
4+
5+
function randomString (size) {
6+
var bits = (size + 1) * 6
7+
var buffer = crypto.randomBytes(Math.ceil(bits / 8))
8+
var string = buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
9+
return string.slice(0, size)
10+
}
11+
12+
function calculatePayloadHash (payload, algorithm, contentType) {
13+
var hash = crypto.createHash(algorithm)
14+
hash.update('hawk.1.payload\n')
15+
hash.update((contentType ? contentType.split(';')[0].trim().toLowerCase() : '') + '\n')
16+
hash.update(payload || '')
17+
hash.update('\n')
18+
return hash.digest('base64')
19+
}
20+
21+
exports.calculateMac = function (credentials, opts) {
22+
var normalized = 'hawk.1.header\n' +
23+
opts.ts + '\n' +
24+
opts.nonce + '\n' +
25+
(opts.method || '').toUpperCase() + '\n' +
26+
opts.resource + '\n' +
27+
opts.host.toLowerCase() + '\n' +
28+
opts.port + '\n' +
29+
(opts.hash || '') + '\n'
30+
31+
if (opts.ext) {
32+
normalized = normalized + opts.ext.replace('\\', '\\\\').replace('\n', '\\n')
33+
}
34+
35+
normalized = normalized + '\n'
36+
37+
if (opts.app) {
38+
normalized = normalized + opts.app + '\n' + (opts.dlg || '') + '\n'
39+
}
40+
41+
var hmac = crypto.createHmac(credentials.algorithm, credentials.key).update(normalized)
42+
var digest = hmac.digest('base64')
43+
return digest
44+
}
45+
46+
exports.header = function (uri, method, opts) {
47+
var timestamp = opts.timestamp || Math.floor((Date.now() + (opts.localtimeOffsetMsec || 0)) / 1000)
48+
var credentials = opts.credentials
49+
if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
50+
return ''
51+
}
52+
53+
if (['sha1', 'sha256'].indexOf(credentials.algorithm) === -1) {
54+
return ''
55+
}
56+
57+
var artifacts = {
58+
ts: timestamp,
59+
nonce: opts.nonce || randomString(6),
60+
method: method,
61+
resource: uri.pathname + (uri.search || ''),
62+
host: uri.hostname,
63+
port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
64+
hash: opts.hash,
65+
ext: opts.ext,
66+
app: opts.app,
67+
dlg: opts.dlg
68+
}
69+
70+
if (!artifacts.hash && (opts.payload || opts.payload === '')) {
71+
artifacts.hash = calculatePayloadHash(opts.payload, credentials.algorithm, opts.contentType)
72+
}
73+
74+
var mac = exports.calculateMac(credentials, artifacts)
75+
76+
var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''
77+
var header = 'Hawk id="' + credentials.id +
78+
'", ts="' + artifacts.ts +
79+
'", nonce="' + artifacts.nonce +
80+
(artifacts.hash ? '", hash="' + artifacts.hash : '') +
81+
(hasExt ? '", ext="' + artifacts.ext.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : '') +
82+
'", mac="' + mac + '"'
83+
84+
if (artifacts.app) {
85+
header = header + ', app="' + artifacts.app + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"'
86+
}
87+
88+
return header
89+
}

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"forever-agent": "~0.6.1",
3636
"form-data": "~2.3.1",
3737
"har-validator": "~5.0.3",
38-
"hawk": "~6.0.2",
3938
"http-signature": "~1.2.0",
4039
"is-typedarray": "~1.0.0",
4140
"isstream": "~0.1.2",

request.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ var url = require('url')
66
var util = require('util')
77
var stream = require('stream')
88
var zlib = require('zlib')
9-
var hawk = require('hawk')
109
var aws2 = require('aws-sign2')
1110
var aws4 = require('aws4')
1211
var httpSignature = require('http-signature')
@@ -24,6 +23,7 @@ var Querystring = require('./lib/querystring').Querystring
2423
var Har = require('./lib/har').Har
2524
var Auth = require('./lib/auth').Auth
2625
var OAuth = require('./lib/oauth').OAuth
26+
var hawk = require('./lib/hawk')
2727
var Multipart = require('./lib/multipart').Multipart
2828
var Redirect = require('./lib/redirect').Redirect
2929
var Tunnel = require('./lib/tunnel').Tunnel
@@ -1420,7 +1420,7 @@ Request.prototype.httpSignature = function (opts) {
14201420
}
14211421
Request.prototype.hawk = function (opts) {
14221422
var self = this
1423-
self.setHeader('Authorization', hawk.client.header(self.uri, self.method, opts).field)
1423+
self.setHeader('Authorization', hawk.header(self.uri, self.method, opts))
14241424
}
14251425
Request.prototype.oauth = function (_oauth) {
14261426
var self = this

tests/test-hawk.js

Lines changed: 155 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,15 @@
22

33
var http = require('http')
44
var request = require('../index')
5-
var hawk = require('hawk')
5+
var hawk = require('../lib/hawk')
66
var tape = require('tape')
77
var assert = require('assert')
88

99
var server = http.createServer(function (req, res) {
10-
var getCred = function (id, callback) {
11-
assert.equal(id, 'dh37fgj492je')
12-
var credentials = {
13-
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
14-
algorithm: 'sha256',
15-
user: 'Steve'
16-
}
17-
return callback(null, credentials)
18-
}
19-
20-
hawk.server.authenticate(req, getCred, {}, function (err, credentials, attributes) {
21-
res.writeHead(err ? 401 : 200, {
22-
'Content-Type': 'text/plain'
23-
})
24-
res.end(err ? 'Shoosh!' : 'Hello ' + credentials.user)
10+
res.writeHead(200, {
11+
'Content-Type': 'text/plain'
2512
})
13+
res.end(authenticate(req))
2614
})
2715

2816
tape('setup', function (t) {
@@ -32,18 +20,124 @@ tape('setup', function (t) {
3220
})
3321
})
3422

35-
tape('hawk', function (t) {
36-
var creds = {
37-
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
38-
algorithm: 'sha256',
39-
id: 'dh37fgj492je'
40-
}
23+
var creds = {
24+
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
25+
algorithm: 'sha256',
26+
id: 'dh37fgj492je'
27+
}
28+
29+
tape('hawk-get', function (t) {
4130
request(server.url, {
4231
hawk: { credentials: creds }
4332
}, function (err, res, body) {
4433
t.equal(err, null)
4534
t.equal(res.statusCode, 200)
46-
t.equal(body, 'Hello Steve')
35+
t.equal(body, 'OK')
36+
t.end()
37+
})
38+
})
39+
40+
tape('hawk-post', function (t) {
41+
request.post({ url: server.url, body: 'hello', hawk: { credentials: creds, payload: 'hello' } }, function (err, res, body) {
42+
t.equal(err, null)
43+
t.equal(res.statusCode, 200)
44+
t.equal(body, 'OK')
45+
t.end()
46+
})
47+
})
48+
49+
tape('hawk-ext', function (t) {
50+
request(server.url, {
51+
hawk: { credentials: creds, ext: 'test' }
52+
}, function (err, res, body) {
53+
t.equal(err, null)
54+
t.equal(res.statusCode, 200)
55+
t.equal(body, 'OK')
56+
t.end()
57+
})
58+
})
59+
60+
tape('hawk-app', function (t) {
61+
request(server.url, {
62+
hawk: { credentials: creds, app: 'test' }
63+
}, function (err, res, body) {
64+
t.equal(err, null)
65+
t.equal(res.statusCode, 200)
66+
t.equal(body, 'OK')
67+
t.end()
68+
})
69+
})
70+
71+
tape('hawk-app+dlg', function (t) {
72+
request(server.url, {
73+
hawk: { credentials: creds, app: 'test', dlg: 'asd' }
74+
}, function (err, res, body) {
75+
t.equal(err, null)
76+
t.equal(res.statusCode, 200)
77+
t.equal(body, 'OK')
78+
t.end()
79+
})
80+
})
81+
82+
tape('hawk-missing-creds', function (t) {
83+
request(server.url, {
84+
hawk: {}
85+
}, function (err, res, body) {
86+
t.equal(err, null)
87+
t.equal(res.statusCode, 200)
88+
t.equal(body, 'FAIL')
89+
t.end()
90+
})
91+
})
92+
93+
tape('hawk-missing-creds-id', function (t) {
94+
request(server.url, {
95+
hawk: {
96+
credentials: {}
97+
}
98+
}, function (err, res, body) {
99+
t.equal(err, null)
100+
t.equal(res.statusCode, 200)
101+
t.equal(body, 'FAIL')
102+
t.end()
103+
})
104+
})
105+
106+
tape('hawk-missing-creds-key', function (t) {
107+
request(server.url, {
108+
hawk: {
109+
credentials: { id: 'asd' }
110+
}
111+
}, function (err, res, body) {
112+
t.equal(err, null)
113+
t.equal(res.statusCode, 200)
114+
t.equal(body, 'FAIL')
115+
t.end()
116+
})
117+
})
118+
119+
tape('hawk-missing-creds-algo', function (t) {
120+
request(server.url, {
121+
hawk: {
122+
credentials: { key: '123', id: '123' }
123+
}
124+
}, function (err, res, body) {
125+
t.equal(err, null)
126+
t.equal(res.statusCode, 200)
127+
t.equal(body, 'FAIL')
128+
t.end()
129+
})
130+
})
131+
132+
tape('hawk-invalid-creds-algo', function (t) {
133+
request(server.url, {
134+
hawk: {
135+
credentials: { key: '123', id: '123', algorithm: 'xx' }
136+
}
137+
}, function (err, res, body) {
138+
t.equal(err, null)
139+
t.equal(res.statusCode, 200)
140+
t.equal(body, 'FAIL')
47141
t.end()
48142
})
49143
})
@@ -53,3 +147,41 @@ tape('cleanup', function (t) {
53147
t.end()
54148
})
55149
})
150+
151+
function authenticate (req) {
152+
if (!req.headers.authorization) {
153+
return 'FAIL'
154+
}
155+
156+
var headerParts = req.headers.authorization.match(/^(\w+)(?:\s+(.*))?$/)
157+
assert.equal(headerParts[1], 'Hawk')
158+
var attributes = {}
159+
headerParts[2].replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) { attributes[$1] = $2 })
160+
var hostParts = req.headers.host.split(':')
161+
162+
const artifacts = {
163+
method: req.method,
164+
host: hostParts[0],
165+
port: (hostParts[1] ? hostParts[1] : (req.connection && req.connection.encrypted ? 443 : 80)),
166+
resource: req.url,
167+
ts: attributes.ts,
168+
nonce: attributes.nonce,
169+
hash: attributes.hash,
170+
ext: attributes.ext,
171+
app: attributes.app,
172+
dlg: attributes.dlg,
173+
mac: attributes.mac,
174+
id: attributes.id
175+
}
176+
177+
assert.equal(attributes.id, 'dh37fgj492je')
178+
var credentials = {
179+
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
180+
algorithm: 'sha256',
181+
user: 'Steve'
182+
}
183+
184+
const mac = hawk.calculateMac(credentials, artifacts)
185+
assert.equal(mac, attributes.mac)
186+
return 'OK'
187+
}

0 commit comments

Comments
 (0)