/ index.js
index.js
1 /* 2 * Bot that receives a POST request (from a GitHub issue comment webhook), 3 * finds the address of the bounty contract and funds it automatically 4 * with either ETH or any ERC20/223 token. 5 * REVIEW hardcoded string length. 6 * Depends on openbounty version as of 2018-03-20. 7 */ 8 9 const config = require('./config') 10 const bot = require('./bot') 11 const crypto = require('crypto') 12 const lru = require('lru-cache') 13 const previouslyFundedContracts = lru(10) 14 15 const express = require('express') 16 const cors = require('cors') 17 const helmet = require('helmet') 18 const app = express() 19 const bodyParser = require('body-parser') 20 const jsonParser = bodyParser.json() 21 22 app.use(cors()) 23 app.use(helmet()) 24 25 // Receive a POST request at the url specified by an env. var. 26 app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) { 27 bot.info(`Handling ${req.body.issue.url}`) 28 29 if (!req.body || !req.body.action) { 30 return res.sendStatus(400) 31 } else if (!bot.needsFunding(req)) { 32 return res.sendStatus(204) 33 } 34 35 const validation = validateRequest(req) 36 37 if (validation.correct) { 38 setTimeout(async () => { 39 try { 40 if (await processRequest(req)) { 41 bot.info(`Issue well funded: ${req.body.issue.url}`) 42 } 43 } catch (err) { 44 bot.error(`Error processing request: ${req.body.issue.url}`) 45 bot.error(err) 46 bot.error(`Dump: ${JSON.stringify(req.body)}`) 47 } 48 }, config.delayInMiliSeconds) 49 } else { 50 bot.error(`Error validating issue: ${req.body.issue.url}`) 51 bot.error(`Error: ${validation.error}`) 52 } 53 return res.sendStatus(200) 54 }) 55 56 function validateRequest (req) { 57 const validation = { correct: false, error: '' } 58 const webhookSecret = process.env.WEBHOOK_SECRET 59 60 if (!webhookSecret) { 61 validation.error = 'Github Webhook Secret key not found. ' + 62 'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value' 63 } else { 64 const blob = JSON.stringify(req.body) 65 const hmac = crypto.createHmac('sha1', webhookSecret) 66 const ourSignature = `sha1=${hmac.update(blob).digest('hex')}` 67 68 const theirSignature = req.get('X-Hub-Signature') 69 70 const bufferA = Buffer.from(ourSignature, 'utf8') 71 const bufferB = Buffer.from(theirSignature, 'utf8') 72 73 const safe = crypto.timingSafeEqual(bufferA, bufferB) 74 75 if (safe) { 76 validation.correct = true 77 } else { 78 validation.error = 'Invalid signature. Check that WEBHOOK_SECRET ' + 79 'env variable matches github\'s webhook secret value' 80 } 81 } 82 83 return validation 84 } 85 86 async function processRequest (req) { 87 const to = bot.getAddress(req) 88 89 const previousHash = previouslyFundedContracts.get(to) 90 if (previousHash) { 91 bot.info(`Issue has been funded before (tx hash=${previousHash}), ignoring`) 92 return null 93 } 94 95 const amount = await bot.getAmount(req) 96 const gasPrice = await bot.getGasPrice() 97 const transaction = await bot.sendTransaction(to, amount, gasPrice) 98 99 previouslyFundedContracts.set(to, transaction.hash) 100 101 bot.logTransaction(transaction) 102 103 return transaction 104 } 105 106 const port = process.env.PORT || 8181 107 app.listen(port, function () { 108 bot.info(`Autobounty listening on port ${port}`) 109 })