@@ -14,46 +14,52 @@ import handleGithubWebhook from './handle-github-webhook';
1414import * as handleGithubWebhookModule from './handle-github-webhook' ;
1515import * as Config from './config' ;
1616import createHttpServer from './http-server' ;
17+ import * as crypto from 'crypto' ;
1718
18- const MOCK_DISCORD_WEBHOOK_URL = 'https://discord.com/api/webhooks/12345/s3creTk3Y' ;
19+ const GITHUB_WEBHOOK_SECRET = 's3creTk3Y' ;
20+ const GITHUB_WEBHOOK_DISCORD_URL = 'https://discord.com/api/webhooks/12345/key' ;
1921
2022describe ( 'handleGithubWebhook - unit tests' , ( ) => {
2123 beforeEach ( ( ) => {
22- Sinon . stub ( Config , 'getConfig' ) . returns ( MOCK_DISCORD_WEBHOOK_URL ) ;
24+ const getConfigStub = Sinon . stub ( Config , 'getConfig' ) ;
25+ getConfigStub . withArgs ( 'GITHUB_WEBHOOK_SECRET' ) . returns ( GITHUB_WEBHOOK_SECRET ) ;
26+ getConfigStub . withArgs ( 'GITHUB_WEBHOOK_DISCORD_URL' ) . returns ( GITHUB_WEBHOOK_DISCORD_URL ) ;
2327 } ) ;
2428
2529 afterEach ( ( ) => {
2630 Sinon . restore ( ) ;
2731 } ) ;
2832
29- it ( 'should return 401 if webhook secret is not provided ' , async ( ) => {
30- const result = await handleGithubWebhook ( '/githubWebhook/wrong-secret' , { } ) ;
33+ it ( 'should return 401 if webhook signature is invalid ' , async ( ) => {
34+ const result = await handleGithubWebhook ( { } , Buffer . from ( '' ) , { } ) ;
3135
3236 expect ( result ) . to . eql ( { statusCode : 401 } ) ;
3337 } ) ;
3438
3539 it ( 'should return 200 if webhook was filtered out' , async ( ) => {
36- const result = await handleGithubWebhook ( '/githubWebhook/s3creTk3Y' , {
37- sender : {
38- login : 'dependabot' ,
39- } ,
40- } ) ;
40+ const result = await handleGithubWebhook (
41+ ...makeHandleGithubWebhookParams ( {
42+ sender : {
43+ login : 'dependabot' ,
44+ } ,
45+ } )
46+ ) ;
4147
4248 expect ( result ) . to . eql ( { statusCode : 200 } ) ;
4349 } ) ;
4450
4551 it ( 'should return status code from discord if the webhook was forwarded' , async ( ) => {
4652 const payload = { sender : { login : 'kbkk' } } ;
4753
48- nock ( 'https://discord.com' ) . post ( '/api/webhooks/12345/s3creTk3Y ' , payload ) . reply ( 599 ) ;
54+ nock ( 'https://discord.com' ) . post ( '/api/webhooks/12345/key ' , payload ) . reply ( 599 ) ;
4955
50- const result = await handleGithubWebhook ( '/githubWebhook/s3creTk3Y' , payload ) ;
56+ const result = await handleGithubWebhook ( ... makeHandleGithubWebhookParams ( payload ) ) ;
5157
5258 expect ( result ) . to . eql ( { statusCode : 599 } ) ;
5359 } ) ;
5460} ) ;
5561
56- describe ( 'handleGithubWebhook - integration tests' , ( ) => {
62+ describe . only ( 'handleGithubWebhook - integration tests' , ( ) => {
5763 let httpServer : Http . Server ;
5864 let baseUrl : string ;
5965
@@ -79,7 +85,7 @@ describe('handleGithubWebhook - integration tests', () => {
7985 it ( 'should respond with 400 on invalid request' , async ( ) => {
8086 const jsonParseSpy = Sinon . spy ( JSON , 'parse' ) ;
8187
82- const { status } = await fetch ( `${ baseUrl } /githubWebhook/test ` , {
88+ const { status } = await fetch ( `${ baseUrl } /githubWebhook` , {
8389 method : 'POST' ,
8490 body : 'invalid json' ,
8591 } ) ;
@@ -88,18 +94,23 @@ describe('handleGithubWebhook - integration tests', () => {
8894 expect ( status ) . to . eql ( 400 ) ;
8995 } ) ;
9096
91- it ( 'should call handleGithubWebhook with url and body' , async ( ) => {
97+ it ( 'should call handleGithubWebhook with headers, rawBody and body' , async ( ) => {
9298 const handleGithubWebhookStub = Sinon . stub ( handleGithubWebhookModule , 'default' ) ;
9399 handleGithubWebhookStub . resolves ( { statusCode : 200 } ) ;
94100
95- await fetch ( `${ baseUrl } /githubWebhook/test ` , {
101+ await fetch ( `${ baseUrl } /githubWebhook` , {
96102 method : 'POST' ,
97103 body : '{"a":1}' ,
104+ headers : {
105+ 'x-hub-signature' : 'test-signature' ,
106+ } ,
98107 } ) ;
99108
100- expect ( handleGithubWebhookStub ) . to . have . been . calledOnceWithExactly ( '/githubWebhook/test' , {
101- a : 1 ,
102- } ) ;
109+ expect ( handleGithubWebhookStub ) . to . have . been . calledOnceWithExactly (
110+ Sinon . match ( { 'x-hub-signature' : 'test-signature' } ) ,
111+ Buffer . from ( '{"a":1}' ) ,
112+ { a : 1 }
113+ ) ;
103114 } ) ;
104115
105116 it ( 'should respond with status code from handleGithubWebhook' , async ( ) => {
@@ -114,3 +125,23 @@ describe('handleGithubWebhook - integration tests', () => {
114125 expect ( status ) . eql ( 599 ) ;
115126 } ) ;
116127} ) ;
128+
129+ function forgeSignature ( rawBody : Buffer ) : string {
130+ const hmacString = crypto . createHmac ( 'sha1' , GITHUB_WEBHOOK_SECRET ) . update ( rawBody ) . digest ( 'hex' ) ;
131+ const signature = `sha1=${ hmacString } ` ;
132+
133+ return signature ;
134+ }
135+
136+ function makeHandleGithubWebhookParams ( body : object ) : Parameters < typeof handleGithubWebhook > {
137+ const rawBody = Buffer . from ( JSON . stringify ( body ) ) ;
138+ const signature = forgeSignature ( rawBody ) ;
139+
140+ return [
141+ {
142+ 'x-hub-signature' : signature ,
143+ } ,
144+ rawBody ,
145+ body ,
146+ ] ;
147+ }
0 commit comments