UNPKG

git-webhook-handler2

Version:

Web handler / middleware for processing GitCode, GitHub, Gitee, Gitlab Webhooks

267 lines (225 loc) 7.06 kB
const EventEmitter = require('events') const crypto = require('crypto') const bl = require('bl') function findHandler (url, arr) { if (!Array.isArray(arr)) { return arr } let ret = arr[0] for (let i = 0; i < arr.length; i++) { if (url === arr[i].path) { ret = arr[i] } } return ret } function checkType (options) { if (typeof options !== 'object') { throw new TypeError('must provide an options object') } if (typeof options.path !== 'string') { throw new TypeError('must provide a \'path\' option') } if (typeof options.secret !== 'string') { throw new TypeError('must provide a \'secret\' option') } } function create (initOptions) { let options // validate type of options if (Array.isArray(initOptions)) { for (let i = 0; i < initOptions.length; i++) { checkType(initOptions[i]) } } else { checkType(initOptions) } // make it an EventEmitter Object.setPrototypeOf(handler, EventEmitter.prototype) EventEmitter.call(handler) handler.sign = sign handler.verify = verify return handler function sign (data) { return `sha1=${crypto.createHmac('sha1', options.secret).update(data).digest('hex')}` } function verify (signature, data) { const sig = Buffer.from(signature) const signed = Buffer.from(sign(data)) if (sig.length !== signed.length) { return false } return crypto.timingSafeEqual(sig, signed) } function verifyGitee (signature, data, json) { if (json.sign) { const sig = Buffer.from(signature) const signed = Buffer.from(crypto.createHmac('sha256', options.secret).update(`${json.timestamp}\n${options.secret}`).digest('base64')) if (sig.length !== signed.length) { return false } return crypto.timingSafeEqual(sig, signed) } else { return signature === options.secret } } function verifyGitCode (signature, data, json) { const sign = json['x-gitcode-signature-256'] if (sign) { const sig = Buffer.from(signature) const signed = Buffer.from(`sha256=${crypto.createHmac('sha256', options.secret).update(data).digest('hex')}`) if (sig.length !== signed.length) { return false } return crypto.timingSafeEqual(sig, signed) } else { return signature === options.secret } } function verifyGitCode (signature, data, json) { const sign = json['x-gitcode-signature-256'] if (sign) { const sig = Buffer.from(signature) const signed = Buffer.from(`sha256=${crypto.createHmac('sha256', options.secret).update(data).digest('hex')}`) if (sig.length !== signed.length) { return false } return crypto.timingSafeEqual(sig, signed) } else { return signature === options.secret } } function verifyGitlab (signature) { return signature === options.secret } function verifyGiteaGogs (signature, data, json) { const expected = crypto.createHmac('sha256', options.secret).update(JSON.stringify(json, null, 2)).digest('hex') return Buffer.from(expected).equals(Buffer.from(signature)) } function verifyCodeup (signature) { return signature === options.secret } function handler (req, res, callback) { let events options = findHandler(req.url, initOptions) if (typeof options.events === 'string' && options.events !== '*') { events = [options.events] } else if (Array.isArray(options.events) && options.events.indexOf('*') === -1) { events = options.events } if (req.url.split('?').shift() !== options.path || req.method !== 'POST') { return callback() } function hasError (msg) { res.writeHead(400, { 'content-type': 'application/json' }) res.end(JSON.stringify({ error: msg })) const err = new Error(msg) handler.emit('error', err, req) callback(err) } // get platform const ua = req.headers['user-agent'] const keyMap = { sig: 'x-hub-signature', event: 'x-github-event', id: 'x-github-delivery', verify } // gitee if (ua === 'git-oschina-hook') { keyMap.sig = 'x-gitee-token' keyMap.event = 'x-gitee-event' keyMap.id = 'x-gitee-timestamp' keyMap.verify = verifyGitee } else if (req.headers['x-gitlab-token']) { // gitlab keyMap.sig = 'x-gitlab-token' keyMap.event = 'x-gitlab-event' keyMap.id = 'x-gitlab-token' keyMap.verify = verifyGitlab } else if (req.headers['x-gitea-signature']) { // gitea keyMap.sig = 'x-gitea-signature' keyMap.event = 'x-gitea-event' keyMap.id = 'x-gitea-delivery' keyMap.verify = verifyGiteaGogs } else if (req.headers['x-gogs-signature']) { // gogs keyMap.sig = 'x-gogs-signature' keyMap.event = 'x-gogs-event' keyMap.id = 'x-gogs-delivery' keyMap.verify = verifyGiteaGogs }else if (req.headers['x-codeup-token']){ keyMap.sig = 'x-codeup-token' keyMap.event = 'x-codeup-event' keyMap.id = 'x-codeup-delivery' keyMap.verify = verifyCodeup } else if (ua === 'git-gitcode-hook') { // gitcode keyMap.sig = 'x-gitcode-token' keyMap.signature = 'x-gitcode-signature-256' keyMap.event = 'x-gitcode-event' keyMap.id = 'x-gitcode-delivery' keyMap.verify = verifyGitCode } const sig = req.headers[keyMap.sig] const event = req.headers[keyMap.event] const id = req.headers[keyMap.id] if (ua === 'git-gitcode-hook' && !sig) { sig = req.headers[keyMap.signature] } if (!sig) { return hasError(`No ${keyMap.sig} found on request`) } if (!event) { return hasError(`No ${keyMap.event} found on request`) } if (!id) { return hasError(`No ${keyMap.id} found on request`) } if (events && events.indexOf(event) === -1) { return hasError(`No ${keyMap.event} found on request`) } req.pipe(bl((err, data) => { if (err) { return hasError(err.message) } let obj try { obj = JSON.parse(data.toString()) } catch (e) { return hasError(e) } if (!keyMap.verify(sig, data, obj)) { return hasError(`${keyMap.sig} does not match blob signature`) } res.writeHead(200, { 'content-type': 'application/json' }) res.end('{"ok":true}') const emitData = { event: event, id: id, payload: obj, protocol: req.protocol, host: req.headers.host, url: req.url, path: options.path } // set common event function commonEvent (event) { if (event === 'Push Hook') { return 'push' } if (event === 'Issue Hook') { return 'issues' } if (event === 'Merge Request Hook'){ return 'merge' } return event } handler.emit(commonEvent(event), emitData) handler.emit('*', emitData) })) } } module.exports = create