github-webhook-handler 0.7.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.travis.yml +14 -0
- package/README.md +4 -3
- package/github-webhook-handler.d.ts +3 -1
- package/github-webhook-handler.js +82 -42
- package/package.json +8 -7
- package/test.js +167 -130
- package/.jshintrc +0 -59
package/.travis.yml
ADDED
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# github-webhook-handler
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/rvagg/github-webhook-handler)
|
|
4
|
+
|
|
5
|
+
[](https://nodei.co/npm/github-webhook-handler/)
|
|
5
6
|
|
|
6
7
|
GitHub allows you to register **[Webhooks](https://developer.github.com/webhooks/)** for your repositories. Each time an event occurs on your repository, whether it be pushing code, filling issues or creating pull requests, the webhook address you register can be configured to be pinged with details.
|
|
7
8
|
|
|
@@ -75,4 +76,4 @@ Additionally, there is a special `'*'` even you can listen to in order to receiv
|
|
|
75
76
|
|
|
76
77
|
## License
|
|
77
78
|
|
|
78
|
-
**github-webhook-handler** is Copyright (c) 2014 Rod Vagg
|
|
79
|
+
**github-webhook-handler** is Copyright (c) 2014 Rod Vagg and licensed under the MIT License. All rights not explicitly granted in the MIT License are reserved. See the included [LICENSE.md](./LICENSE.md) file for more details.
|
|
@@ -13,4 +13,6 @@ interface handler extends EventEmitter {
|
|
|
13
13
|
(req: IncomingMessage, res: ServerResponse, callback: (err: Error) => void): void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
declare function createHandler(options: CreateHandlerOptions|CreateHandlerOptions[]): handler;
|
|
17
|
+
|
|
18
|
+
export = createHandler;
|
|
@@ -1,29 +1,49 @@
|
|
|
1
|
-
const EventEmitter = require('events')
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
const EventEmitter = require('events')
|
|
2
|
+
const crypto = require('crypto')
|
|
3
|
+
const bl = require('bl')
|
|
4
|
+
|
|
5
|
+
function findHandler (url, arr) {
|
|
6
|
+
if (!Array.isArray(arr)) {
|
|
7
|
+
return arr
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let ret = arr[0]
|
|
11
|
+
for (let i = 0; i < arr.length; i++) {
|
|
12
|
+
if (url === arr[i].path) {
|
|
13
|
+
ret = arr[i]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return ret
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function checkType (options) {
|
|
21
|
+
if (typeof options !== 'object') {
|
|
9
22
|
throw new TypeError('must provide an options object')
|
|
23
|
+
}
|
|
10
24
|
|
|
11
|
-
if (typeof options.path
|
|
25
|
+
if (typeof options.path !== 'string') {
|
|
12
26
|
throw new TypeError('must provide a \'path\' option')
|
|
27
|
+
}
|
|
13
28
|
|
|
14
|
-
if (typeof options.secret
|
|
29
|
+
if (typeof options.secret !== 'string') {
|
|
15
30
|
throw new TypeError('must provide a \'secret\' option')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
function create (initOptions) {
|
|
35
|
+
let options
|
|
36
|
+
// validate type of options
|
|
37
|
+
if (Array.isArray(initOptions)) {
|
|
38
|
+
for (let i = 0; i < initOptions.length; i++) {
|
|
39
|
+
checkType(initOptions[i])
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
checkType(initOptions)
|
|
43
|
+
}
|
|
24
44
|
|
|
25
|
-
// make it an EventEmitter
|
|
26
|
-
handler
|
|
45
|
+
// make it an EventEmitter
|
|
46
|
+
Object.setPrototypeOf(handler, EventEmitter.prototype)
|
|
27
47
|
EventEmitter.call(handler)
|
|
28
48
|
|
|
29
49
|
handler.sign = sign
|
|
@@ -31,54 +51,74 @@ function create (options) {
|
|
|
31
51
|
|
|
32
52
|
return handler
|
|
33
53
|
|
|
34
|
-
|
|
35
54
|
function sign (data) {
|
|
36
|
-
return
|
|
55
|
+
return `sha1=${crypto.createHmac('sha1', options.secret).update(data).digest('hex')}`
|
|
37
56
|
}
|
|
38
57
|
|
|
39
58
|
function verify (signature, data) {
|
|
40
|
-
|
|
59
|
+
const sig = Buffer.from(signature)
|
|
60
|
+
const signed = Buffer.from(sign(data))
|
|
61
|
+
if (sig.length !== signed.length) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
return crypto.timingSafeEqual(sig, signed)
|
|
41
65
|
}
|
|
42
66
|
|
|
43
67
|
function handler (req, res, callback) {
|
|
44
|
-
|
|
68
|
+
let events
|
|
69
|
+
|
|
70
|
+
options = findHandler(req.url, initOptions)
|
|
71
|
+
|
|
72
|
+
if (typeof options.events === 'string' && options.events !== '*') {
|
|
73
|
+
events = [options.events]
|
|
74
|
+
} else if (Array.isArray(options.events) && options.events.indexOf('*') === -1) {
|
|
75
|
+
events = options.events
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (req.url !== options.path || req.method !== 'POST') {
|
|
45
79
|
return callback()
|
|
80
|
+
}
|
|
46
81
|
|
|
47
82
|
function hasError (msg) {
|
|
48
83
|
res.writeHead(400, { 'content-type': 'application/json' })
|
|
49
84
|
res.end(JSON.stringify({ error: msg }))
|
|
50
85
|
|
|
51
|
-
|
|
86
|
+
const err = new Error(msg)
|
|
52
87
|
|
|
53
88
|
handler.emit('error', err, req)
|
|
54
89
|
callback(err)
|
|
55
90
|
}
|
|
56
91
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
const sig = req.headers['x-hub-signature']
|
|
93
|
+
const event = req.headers['x-github-event']
|
|
94
|
+
const id = req.headers['x-github-delivery']
|
|
60
95
|
|
|
61
|
-
if (!sig)
|
|
96
|
+
if (!sig) {
|
|
62
97
|
return hasError('No X-Hub-Signature found on request')
|
|
98
|
+
}
|
|
63
99
|
|
|
64
|
-
if (!event)
|
|
100
|
+
if (!event) {
|
|
65
101
|
return hasError('No X-Github-Event found on request')
|
|
102
|
+
}
|
|
66
103
|
|
|
67
|
-
if (!id)
|
|
104
|
+
if (!id) {
|
|
68
105
|
return hasError('No X-Github-Delivery found on request')
|
|
106
|
+
}
|
|
69
107
|
|
|
70
|
-
if (events && events.indexOf(event)
|
|
108
|
+
if (events && events.indexOf(event) === -1) {
|
|
71
109
|
return hasError('X-Github-Event is not acceptable')
|
|
110
|
+
}
|
|
72
111
|
|
|
73
|
-
req.pipe(bl(
|
|
112
|
+
req.pipe(bl((err, data) => {
|
|
74
113
|
if (err) {
|
|
75
114
|
return hasError(err.message)
|
|
76
115
|
}
|
|
77
116
|
|
|
78
|
-
|
|
117
|
+
let obj
|
|
79
118
|
|
|
80
|
-
if (!verify(sig, data))
|
|
119
|
+
if (!verify(sig, data)) {
|
|
81
120
|
return hasError('X-Hub-Signature does not match blob signature')
|
|
121
|
+
}
|
|
82
122
|
|
|
83
123
|
try {
|
|
84
124
|
obj = JSON.parse(data.toString())
|
|
@@ -89,13 +129,14 @@ function create (options) {
|
|
|
89
129
|
res.writeHead(200, { 'content-type': 'application/json' })
|
|
90
130
|
res.end('{"ok":true}')
|
|
91
131
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
132
|
+
const emitData = {
|
|
133
|
+
event: event,
|
|
134
|
+
id: id,
|
|
135
|
+
payload: obj,
|
|
136
|
+
protocol: req.protocol,
|
|
137
|
+
host: req.headers.host,
|
|
138
|
+
url: req.url,
|
|
139
|
+
path: options.path
|
|
99
140
|
}
|
|
100
141
|
|
|
101
142
|
handler.emit(event, emitData)
|
|
@@ -104,5 +145,4 @@ function create (options) {
|
|
|
104
145
|
}
|
|
105
146
|
}
|
|
106
147
|
|
|
107
|
-
|
|
108
148
|
module.exports = create
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-webhook-handler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Web handler / middleware for processing GitHub Webhooks",
|
|
5
5
|
"main": "github-webhook-handler.js",
|
|
6
6
|
"types": "github-webhook-handler.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
8
|
+
"lint": "standard *.js",
|
|
9
|
+
"test": "npm run lint && node test.js"
|
|
9
10
|
},
|
|
10
11
|
"keywords": [
|
|
11
12
|
"github",
|
|
@@ -18,13 +19,13 @@
|
|
|
18
19
|
},
|
|
19
20
|
"license": "MIT",
|
|
20
21
|
"dependencies": {
|
|
21
|
-
"bl": "~
|
|
22
|
-
"buffer-equal-constant-time": "~1.0.1"
|
|
22
|
+
"bl": "~4.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "*",
|
|
26
|
-
"run-series": "~1.1.
|
|
27
|
-
"
|
|
28
|
-
"
|
|
26
|
+
"run-series": "~1.1.8",
|
|
27
|
+
"standard": "~14.3.1",
|
|
28
|
+
"tape": "~4.11.0",
|
|
29
|
+
"through2": "~3.0.1"
|
|
29
30
|
}
|
|
30
31
|
}
|
package/test.js
CHANGED
|
@@ -1,46 +1,41 @@
|
|
|
1
|
-
const test
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
const test = require('tape')
|
|
2
|
+
const crypto = require('crypto')
|
|
3
|
+
const handler = require('./')
|
|
4
|
+
const through2 = require('through2')
|
|
5
|
+
const series = require('run-series')
|
|
7
6
|
|
|
8
7
|
function signBlob (key, blob) {
|
|
9
|
-
return 'sha1
|
|
10
|
-
crypto.createHmac('sha1', key).update(blob).digest('hex')
|
|
8
|
+
return `sha1=${crypto.createHmac('sha1', key).update(blob).digest('hex')}`
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
|
|
14
11
|
function mkReq (url, method) {
|
|
15
|
-
|
|
12
|
+
const req = through2()
|
|
16
13
|
req.method = method || 'POST'
|
|
17
14
|
req.url = url
|
|
18
15
|
req.headers = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
'x-hub-signature': 'bogus',
|
|
17
|
+
'x-github-event': 'bogus',
|
|
18
|
+
'x-github-delivery': 'bogus'
|
|
22
19
|
}
|
|
23
20
|
return req
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
|
|
27
23
|
function mkRes () {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
const res = {
|
|
25
|
+
writeHead: function (statusCode, headers) {
|
|
26
|
+
res.$statusCode = statusCode
|
|
27
|
+
res.$headers = headers
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
end: function (content) {
|
|
31
|
+
res.$end = content
|
|
32
|
+
}
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
return res
|
|
40
36
|
}
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
test('handler without full options throws', function (t) {
|
|
38
|
+
test('handler without full options throws', (t) => {
|
|
44
39
|
t.plan(4)
|
|
45
40
|
|
|
46
41
|
t.equal(typeof handler, 'function', 'handler exports a function')
|
|
@@ -52,60 +47,81 @@ test('handler without full options throws', function (t) {
|
|
|
52
47
|
t.throws(handler.bind(null, { path: '/' }), /must provide a 'secret' option/, 'throws if no secret option')
|
|
53
48
|
})
|
|
54
49
|
|
|
50
|
+
test('handler without full options throws in array', (t) => {
|
|
51
|
+
t.plan(2)
|
|
52
|
+
|
|
53
|
+
t.throws(handler.bind(null, [{}]), /must provide a 'path' option/, 'throws if no path option')
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
t.throws(handler.bind(null, [{ path: '/' }]), /must provide a 'secret' option/, 'throws if no secret option')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('handler ignores invalid urls', (t) => {
|
|
59
|
+
const options = { path: '/some/url', secret: 'bogus' }
|
|
60
|
+
const h = handler(options)
|
|
59
61
|
|
|
60
62
|
t.plan(6)
|
|
61
63
|
|
|
62
|
-
h(mkReq('/'), mkRes(),
|
|
64
|
+
h(mkReq('/'), mkRes(), (err) => {
|
|
63
65
|
t.error(err)
|
|
64
66
|
t.ok(true, 'request was ignored')
|
|
65
67
|
})
|
|
66
68
|
|
|
67
69
|
// near match
|
|
68
|
-
h(mkReq('/some/url/'), mkRes(),
|
|
70
|
+
h(mkReq('/some/url/'), mkRes(), (err) => {
|
|
69
71
|
t.error(err)
|
|
70
72
|
t.ok(true, 'request was ignored')
|
|
71
73
|
})
|
|
72
74
|
|
|
73
75
|
// partial match
|
|
74
|
-
h(mkReq('/some'), mkRes(),
|
|
76
|
+
h(mkReq('/some'), mkRes(), (err) => {
|
|
75
77
|
t.error(err)
|
|
76
78
|
t.ok(true, 'request was ignored')
|
|
77
79
|
})
|
|
78
80
|
})
|
|
79
81
|
|
|
80
|
-
test('handler ingores non-POST requests',
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
test('handler ingores non-POST requests', (t) => {
|
|
83
|
+
const options = { path: '/some/url', secret: 'bogus' }
|
|
84
|
+
const h = handler(options)
|
|
83
85
|
|
|
84
86
|
t.plan(4)
|
|
85
87
|
|
|
86
|
-
h(mkReq('/some/url', 'GET'), mkRes(),
|
|
88
|
+
h(mkReq('/some/url', 'GET'), mkRes(), (err) => {
|
|
87
89
|
t.error(err)
|
|
88
90
|
t.ok(true, 'request was ignored')
|
|
89
91
|
})
|
|
90
92
|
|
|
91
|
-
h(mkReq('/some/url?test=param', 'GET'), mkRes(),
|
|
93
|
+
h(mkReq('/some/url?test=param', 'GET'), mkRes(), (err) => {
|
|
92
94
|
t.error(err)
|
|
93
95
|
t.ok(true, 'request was ignored')
|
|
94
96
|
})
|
|
95
97
|
})
|
|
96
98
|
|
|
97
|
-
test('handler accepts valid urls',
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
test('handler accepts valid urls', (t) => {
|
|
100
|
+
const options = { path: '/some/url', secret: 'bogus' }
|
|
101
|
+
const h = handler(options)
|
|
102
|
+
|
|
103
|
+
t.plan(1)
|
|
104
|
+
|
|
105
|
+
h(mkReq('/some/url'), mkRes(), (err) => {
|
|
106
|
+
t.error(err)
|
|
107
|
+
t.fail(false, 'should not call')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
setTimeout(t.ok.bind(t, true, 'done'))
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('handler accepts valid urls in Array', (t) => {
|
|
114
|
+
const options = [{ path: '/some/url', secret: 'bogus' }, { path: '/someOther/url', secret: 'bogus' }]
|
|
115
|
+
const h = handler(options)
|
|
100
116
|
|
|
101
117
|
t.plan(1)
|
|
102
118
|
|
|
103
|
-
h(mkReq('/some/url'), mkRes(),
|
|
119
|
+
h(mkReq('/some/url'), mkRes(), (err) => {
|
|
104
120
|
t.error(err)
|
|
105
121
|
t.fail(false, 'should not call')
|
|
106
122
|
})
|
|
107
123
|
|
|
108
|
-
h(mkReq('/
|
|
124
|
+
h(mkReq('/someOther/url'), mkRes(), (err) => {
|
|
109
125
|
t.error(err)
|
|
110
126
|
t.fail(false, 'should not call')
|
|
111
127
|
})
|
|
@@ -113,77 +129,75 @@ test('handler accepts valid urls', function (t) {
|
|
|
113
129
|
setTimeout(t.ok.bind(t, true, 'done'))
|
|
114
130
|
})
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
132
|
+
test('handler can reject events', (t) => {
|
|
133
|
+
const acceptableEvents = {
|
|
134
|
+
undefined: undefined,
|
|
135
|
+
'a string equal to the event': 'bogus',
|
|
136
|
+
'a string equal to *': '*',
|
|
137
|
+
'an array containing the event': ['bogus'],
|
|
138
|
+
'an array containing *': ['not-bogus', '*']
|
|
139
|
+
}
|
|
140
|
+
const unacceptableEvents = {
|
|
141
|
+
'a string not equal to the event or *': 'not-bogus',
|
|
142
|
+
'an array not containing the event or *': ['not-bogus']
|
|
143
|
+
}
|
|
144
|
+
const acceptable = Object.keys(acceptableEvents)
|
|
145
|
+
const unacceptable = Object.keys(unacceptableEvents)
|
|
146
|
+
const acceptableTests = acceptable.map((events) => {
|
|
147
|
+
return acceptableReq.bind(null, events)
|
|
148
|
+
})
|
|
149
|
+
const unacceptableTests = unacceptable.map((events) => {
|
|
150
|
+
return unacceptableReq.bind(null, events)
|
|
151
|
+
})
|
|
137
152
|
|
|
138
153
|
t.plan(acceptable.length + unacceptable.length)
|
|
139
154
|
series(acceptableTests.concat(unacceptableTests))
|
|
140
155
|
|
|
141
156
|
function acceptableReq (events, callback) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
const h = handler({
|
|
158
|
+
path: '/some/url',
|
|
159
|
+
secret: 'bogus',
|
|
160
|
+
events: acceptableEvents[events]
|
|
146
161
|
})
|
|
147
162
|
|
|
148
|
-
h(mkReq('/some/url'), mkRes(),
|
|
163
|
+
h(mkReq('/some/url'), mkRes(), (err) => {
|
|
149
164
|
t.error(err)
|
|
150
165
|
t.fail(false, 'should not call')
|
|
151
166
|
})
|
|
152
167
|
|
|
153
|
-
setTimeout(
|
|
168
|
+
setTimeout(() => {
|
|
154
169
|
t.ok(true, 'accepted because options.events was ' + events)
|
|
155
170
|
callback()
|
|
156
171
|
})
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
function unacceptableReq (events, callback) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
const h = handler({
|
|
176
|
+
path: '/some/url',
|
|
177
|
+
secret: 'bogus',
|
|
178
|
+
events: unacceptableEvents[events]
|
|
164
179
|
})
|
|
165
180
|
|
|
166
|
-
h.on('error',
|
|
181
|
+
h.on('error', () => {})
|
|
167
182
|
|
|
168
|
-
h(mkReq('/some/url'), mkRes(),
|
|
183
|
+
h(mkReq('/some/url'), mkRes(), (err) => {
|
|
169
184
|
t.ok(err, 'rejected because options.events was ' + events)
|
|
170
185
|
callback()
|
|
171
186
|
})
|
|
172
187
|
}
|
|
173
188
|
})
|
|
174
189
|
|
|
175
|
-
|
|
176
190
|
// because we don't inherit in a traditional way
|
|
177
|
-
test('handler is an EventEmitter',
|
|
191
|
+
test('handler is an EventEmitter', (t) => {
|
|
178
192
|
t.plan(5)
|
|
179
193
|
|
|
180
|
-
|
|
194
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
181
195
|
|
|
182
196
|
t.equal(typeof h.on, 'function', 'has h.on()')
|
|
183
197
|
t.equal(typeof h.emit, 'function', 'has h.emit()')
|
|
184
198
|
t.equal(typeof h.removeListener, 'function', 'has h.removeListener()')
|
|
185
199
|
|
|
186
|
-
h.on('ping',
|
|
200
|
+
h.on('ping', (pong) => {
|
|
187
201
|
t.equal(pong, 'pong', 'got event')
|
|
188
202
|
})
|
|
189
203
|
|
|
@@ -192,85 +206,110 @@ test('handler is an EventEmitter', function (t) {
|
|
|
192
206
|
t.throws(h.emit.bind(h, 'error', new Error('threw an error')), /threw an error/, 'acts like an EE')
|
|
193
207
|
})
|
|
194
208
|
|
|
195
|
-
|
|
196
|
-
test('handler accepts a signed blob', function (t) {
|
|
209
|
+
test('handler accepts a signed blob', (t) => {
|
|
197
210
|
t.plan(4)
|
|
198
211
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
213
|
+
const json = JSON.stringify(obj)
|
|
214
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
215
|
+
const req = mkReq('/')
|
|
216
|
+
const res = mkRes()
|
|
204
217
|
|
|
205
218
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
206
|
-
req.headers['x-github-event']
|
|
219
|
+
req.headers['x-github-event'] = 'push'
|
|
207
220
|
|
|
208
|
-
h.on('push',
|
|
209
|
-
t.deepEqual(event, { event: 'push', id: 'bogus', payload: obj, url: '/', host: undefined, protocol: undefined })
|
|
221
|
+
h.on('push', (event) => {
|
|
222
|
+
t.deepEqual(event, { event: 'push', id: 'bogus', payload: obj, url: '/', host: undefined, protocol: undefined, path: '/' })
|
|
210
223
|
t.equal(res.$statusCode, 200, 'correct status code')
|
|
211
224
|
t.deepEqual(res.$headers, { 'content-type': 'application/json' })
|
|
212
225
|
t.equal(res.$end, '{"ok":true}', 'got correct content')
|
|
213
226
|
})
|
|
214
227
|
|
|
215
|
-
h(req, res,
|
|
228
|
+
h(req, res, (err) => {
|
|
216
229
|
t.error(err)
|
|
217
230
|
t.fail(true, 'should not get here!')
|
|
218
231
|
})
|
|
219
232
|
|
|
220
|
-
process.nextTick(
|
|
233
|
+
process.nextTick(() => {
|
|
221
234
|
req.end(json)
|
|
222
235
|
})
|
|
223
236
|
})
|
|
224
237
|
|
|
238
|
+
test('handler accepts multi blob in Array', (t) => {
|
|
239
|
+
t.plan(4)
|
|
240
|
+
|
|
241
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
242
|
+
const json = JSON.stringify(obj)
|
|
243
|
+
const h = handler([{ path: '/', secret: 'bogus' }, { path: '/some/url', secret: 'bogus' }])
|
|
244
|
+
const req = mkReq('/some/url')
|
|
245
|
+
const res = mkRes()
|
|
246
|
+
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
247
|
+
req.headers['x-github-event'] = 'push'
|
|
248
|
+
|
|
249
|
+
h.on('push', (event) => {
|
|
250
|
+
t.deepEqual(event, { event: 'push', id: 'bogus', payload: obj, url: '/some/url', host: undefined, protocol: undefined, path: '/some/url' })
|
|
251
|
+
t.equal(res.$statusCode, 200, 'correct status code')
|
|
252
|
+
t.deepEqual(res.$headers, { 'content-type': 'application/json' })
|
|
253
|
+
t.equal(res.$end, '{"ok":true}', 'got correct content')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
h(req, res, (err) => {
|
|
257
|
+
t.error(err)
|
|
258
|
+
t.fail(true, 'should not get here!')
|
|
259
|
+
})
|
|
225
260
|
|
|
226
|
-
|
|
261
|
+
process.nextTick(() => {
|
|
262
|
+
req.end(json)
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('handler accepts a signed blob with alt event', (t) => {
|
|
227
267
|
t.plan(4)
|
|
228
268
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
269
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
270
|
+
const json = JSON.stringify(obj)
|
|
271
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
272
|
+
const req = mkReq('/')
|
|
273
|
+
const res = mkRes()
|
|
234
274
|
|
|
235
275
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
236
|
-
req.headers['x-github-event']
|
|
276
|
+
req.headers['x-github-event'] = 'issue'
|
|
237
277
|
|
|
238
|
-
h.on('push',
|
|
278
|
+
h.on('push', (event) => {
|
|
239
279
|
t.fail(true, 'should not get here!')
|
|
240
280
|
})
|
|
241
281
|
|
|
242
|
-
h.on('issue',
|
|
243
|
-
t.deepEqual(event, { event: 'issue', id: 'bogus', payload: obj, url: '/', host: undefined, protocol: undefined })
|
|
282
|
+
h.on('issue', (event) => {
|
|
283
|
+
t.deepEqual(event, { event: 'issue', id: 'bogus', payload: obj, url: '/', host: undefined, protocol: undefined, path: '/' })
|
|
244
284
|
t.equal(res.$statusCode, 200, 'correct status code')
|
|
245
285
|
t.deepEqual(res.$headers, { 'content-type': 'application/json' })
|
|
246
286
|
t.equal(res.$end, '{"ok":true}', 'got correct content')
|
|
247
287
|
})
|
|
248
288
|
|
|
249
|
-
h(req, res,
|
|
289
|
+
h(req, res, (err) => {
|
|
250
290
|
t.error(err)
|
|
251
291
|
t.fail(true, 'should not get here!')
|
|
252
292
|
})
|
|
253
293
|
|
|
254
|
-
process.nextTick(
|
|
294
|
+
process.nextTick(() => {
|
|
255
295
|
req.end(json)
|
|
256
296
|
})
|
|
257
297
|
})
|
|
258
298
|
|
|
259
|
-
|
|
260
|
-
test('handler rejects a badly signed blob', function (t) {
|
|
299
|
+
test('handler rejects a badly signed blob', (t) => {
|
|
261
300
|
t.plan(6)
|
|
262
301
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
302
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
303
|
+
const json = JSON.stringify(obj)
|
|
304
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
305
|
+
const req = mkReq('/')
|
|
306
|
+
const res = mkRes()
|
|
268
307
|
|
|
269
308
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
270
309
|
// break signage by a tiny bit
|
|
271
310
|
req.headers['x-hub-signature'] = '0' + req.headers['x-hub-signature'].substring(1)
|
|
272
311
|
|
|
273
|
-
h.on('error',
|
|
312
|
+
h.on('error', (err, _req) => {
|
|
274
313
|
t.ok(err, 'got an error')
|
|
275
314
|
t.strictEqual(_req, req, 'was given original request object')
|
|
276
315
|
t.equal(res.$statusCode, 400, 'correct status code')
|
|
@@ -278,56 +317,54 @@ test('handler rejects a badly signed blob', function (t) {
|
|
|
278
317
|
t.equal(res.$end, '{"error":"X-Hub-Signature does not match blob signature"}', 'got correct content')
|
|
279
318
|
})
|
|
280
319
|
|
|
281
|
-
h.on('push',
|
|
320
|
+
h.on('push', (event) => {
|
|
282
321
|
t.fail(true, 'should not get here!')
|
|
283
322
|
})
|
|
284
323
|
|
|
285
|
-
h(req, res,
|
|
324
|
+
h(req, res, (err) => {
|
|
286
325
|
t.ok(err, 'got error on callback')
|
|
287
326
|
})
|
|
288
327
|
|
|
289
|
-
process.nextTick(
|
|
328
|
+
process.nextTick(() => {
|
|
290
329
|
req.end(json)
|
|
291
330
|
})
|
|
292
331
|
})
|
|
293
332
|
|
|
294
|
-
test('handler responds on a bl error',
|
|
333
|
+
test('handler responds on a bl error', (t) => {
|
|
295
334
|
t.plan(4)
|
|
296
335
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
336
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
337
|
+
const json = JSON.stringify(obj)
|
|
338
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
339
|
+
const req = mkReq('/')
|
|
340
|
+
const res = mkRes()
|
|
302
341
|
|
|
303
342
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
304
|
-
req.headers['x-github-event']
|
|
343
|
+
req.headers['x-github-event'] = 'issue'
|
|
305
344
|
|
|
306
|
-
h.on('push',
|
|
345
|
+
h.on('push', (event) => {
|
|
307
346
|
t.fail(true, 'should not get here!')
|
|
308
347
|
})
|
|
309
348
|
|
|
310
|
-
h.on('issue',
|
|
349
|
+
h.on('issue', (event) => {
|
|
311
350
|
t.fail(true, 'should never get here!')
|
|
312
351
|
})
|
|
313
352
|
|
|
314
|
-
h.on('error',
|
|
353
|
+
h.on('error', (err) => {
|
|
315
354
|
t.ok(err, 'got an error')
|
|
316
355
|
t.equal(res.$statusCode, 400, 'correct status code')
|
|
317
|
-
})
|
|
356
|
+
})
|
|
318
357
|
|
|
319
|
-
h(req, res,
|
|
358
|
+
h(req, res, (err) => {
|
|
320
359
|
t.ok(err)
|
|
321
360
|
})
|
|
322
361
|
|
|
323
|
-
|
|
324
|
-
res.end = function () {
|
|
362
|
+
res.end = () => {
|
|
325
363
|
t.equal(res.$statusCode, 400, 'correct status code')
|
|
326
364
|
}
|
|
327
365
|
|
|
328
366
|
req.write('{')
|
|
329
|
-
process.nextTick(
|
|
367
|
+
process.nextTick(() => {
|
|
330
368
|
req.emit('error', new Error('simulated explosion'))
|
|
331
|
-
})
|
|
332
|
-
|
|
369
|
+
})
|
|
333
370
|
})
|
package/.jshintrc
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"predef": [ ]
|
|
3
|
-
, "bitwise": false
|
|
4
|
-
, "camelcase": false
|
|
5
|
-
, "curly": false
|
|
6
|
-
, "eqeqeq": false
|
|
7
|
-
, "forin": false
|
|
8
|
-
, "immed": false
|
|
9
|
-
, "latedef": false
|
|
10
|
-
, "noarg": true
|
|
11
|
-
, "noempty": true
|
|
12
|
-
, "nonew": true
|
|
13
|
-
, "plusplus": false
|
|
14
|
-
, "quotmark": true
|
|
15
|
-
, "regexp": false
|
|
16
|
-
, "undef": true
|
|
17
|
-
, "unused": true
|
|
18
|
-
, "strict": false
|
|
19
|
-
, "trailing": true
|
|
20
|
-
, "maxlen": 120
|
|
21
|
-
, "asi": true
|
|
22
|
-
, "boss": true
|
|
23
|
-
, "debug": true
|
|
24
|
-
, "eqnull": true
|
|
25
|
-
, "esnext": true
|
|
26
|
-
, "evil": true
|
|
27
|
-
, "expr": true
|
|
28
|
-
, "funcscope": false
|
|
29
|
-
, "globalstrict": false
|
|
30
|
-
, "iterator": false
|
|
31
|
-
, "lastsemic": true
|
|
32
|
-
, "laxbreak": true
|
|
33
|
-
, "laxcomma": true
|
|
34
|
-
, "loopfunc": true
|
|
35
|
-
, "multistr": false
|
|
36
|
-
, "onecase": false
|
|
37
|
-
, "proto": false
|
|
38
|
-
, "regexdash": false
|
|
39
|
-
, "scripturl": true
|
|
40
|
-
, "smarttabs": false
|
|
41
|
-
, "shadow": false
|
|
42
|
-
, "sub": true
|
|
43
|
-
, "supernew": false
|
|
44
|
-
, "validthis": true
|
|
45
|
-
, "browser": true
|
|
46
|
-
, "couch": false
|
|
47
|
-
, "devel": false
|
|
48
|
-
, "dojo": false
|
|
49
|
-
, "mootools": false
|
|
50
|
-
, "node": true
|
|
51
|
-
, "nonstandard": true
|
|
52
|
-
, "prototypejs": false
|
|
53
|
-
, "rhino": false
|
|
54
|
-
, "worker": true
|
|
55
|
-
, "wsh": false
|
|
56
|
-
, "nomen": false
|
|
57
|
-
, "onevar": false
|
|
58
|
-
, "passfail": false
|
|
59
|
-
}
|