Skip to content

Commit

Permalink
Convert to custom middleware
Browse files Browse the repository at this point in the history
Signed-off-by: Brett Logan <lindluni@github.com>
  • Loading branch information
lindluni committed Mar 11, 2024
1 parent 45452ba commit 9c1eb87
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 27 deletions.
111 changes: 86 additions & 25 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import express from 'express'
import {App, createNodeMiddleware, Octokit} from 'octokit'
import * as crypto from 'crypto'
import {App, Octokit} from 'octokit'
import {rateLimit} from 'express-rate-limit'

const port = process.env.OSST_ACTIONS_BOT_PORT || process.env.PORT || 8080
const appID = process.env.OSST_ACTIONS_BOT_APP_ID
Expand Down Expand Up @@ -30,10 +32,6 @@ const octokit = new App({
}
})

const middleware = createNodeMiddleware(octokit)
const app = express()
app.use(middleware)

const retrieveRequiredChecks = async (properties) => {
const requiredChecks = []
for (const [_key, value] of Object.entries(properties)) {
Expand Down Expand Up @@ -106,33 +104,96 @@ const processRerunRequiredWorkflows = async (octokit, metadata, owner, repo, num
}
}

octokit.webhooks.on('issue_comment.created', async ({octokit, payload}) => {
try {
const body = payload.comment.body.trim().toLowerCase()
const owner = payload.repository.owner.login
const repo = payload.repository.name
const issueNumber = payload.issue.number
const actor = payload.comment.user.login
const commentID = payload.comment.id
const metadata = `${actor}:${owner}:${repo}:${issueNumber}:${commentID}`
const verifyGitHubWebhook = (req, res, next) => {
const payload = JSON.stringify(req.body)
if (!payload) {
return next('Request body empty')
}

if (!payload.issue.pull_request) {
return console.log(`[${metadata}] Issue is not a pull request`)
const sig = req.get('X-Hub-Signature-256') || ''
const hmac = crypto.createHmac('sha256', appSecret)
const digest = Buffer.from('sha256=' + hmac.update(payload).digest('hex'), 'utf8')
const checksum = Buffer.from(sig, 'utf8')
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
return next(`Request body digest (${digest}) did not match X-Hub-Signature-256 (${checksum})`)
}
return next()
}

const verifyIssueCommentCreatedEvent = (req, res, next) => {
if (req.get('X-GitHub-Event') === 'issue_comment') {
if (req.body.action === 'created') {
return next()
}
if (!body.startsWith('/actions-bot') || !body.includes('rerun-required-workflows')) {
return console.log(`[${metadata}] Not a command: '${body}'`)
}
}
return next(`X-GitHub-Event is not issue_comment.created`)
}

const verifyIsPR = async (req, res, next) => {
const isPR = req.body.issue.pull_request
if (isPR) {
return next()
}
return next(`Issue is not a pull request`)
}

console.log(`[${metadata}] Processing command '${body}'`)
const properties = payload.repository.custom_properties
console.log(`[${metadata}] Processing properties: ${JSON.stringify(properties)}`)
const verifyCommand = (req, res, next) => {
const command = req.body.comment.body.trim().toLowerCase()
if (command.startsWith('/actions-bot') && command.includes('rerun-required-workflows')) {
return next()
}
return next(`Not a command: '${command}'`)

}

const hydrateKey = (req, res, next) => {
const actor = req.body.comment.user.login
const owner = req.body.repository.owner.login
const repo = req.body.repository.name
const pr = req.body.issue.number
const commentID = req.body.comment.id
const commentNodeID = req.body.comment.node_id
req.key = `${actor}:${owner}:${repo}:${pr}:${commentID}:${commentNodeID}`
return next()
}

const hydrateOctokit = async (req, res, next) => {
req.octokit = await octokit.getInstallationOctokit(req.body.installation.id)
return next()
}

const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
limit: 1, // limit each IP to 1 requests per windowMs
keyGenerator: (req) => req.body.issue.node_id,
handler: (req, res, next, options) => {
console.log(`[${req.key}] Rate limit exceeded`)
return res.status(options.statusCode).send(options.message)
}
})

const app = express()
app.use(express.json())
app.use(hydrateKey)
app.use(limiter)
app.use(verifyGitHubWebhook)
app.use(verifyIsPR)
app.use(verifyIssueCommentCreatedEvent)
app.use(verifyCommand)
app.use(hydrateOctokit)

app.post('/api/github/webhooks', async (req) => {
try {
const command = req.body.comment.body.trim().toLowerCase()
console.log(`[${req.key}] Processing command '${command}'`)
const properties = req.body.repository.custom_properties
console.log(`[${req.key}] Processing properties: ${JSON.stringify(properties)}`)
const checks = await retrieveRequiredChecks(properties)
if (checks.length === 0) {
return console.log(`[${metadata}] No required checks found`)
return console.log(`[${req.key}] No required checks found`)
}
console.log(`[${metadata}] Processing rerun-required-workflows`)
await processRerunRequiredWorkflows(octokit, metadata, owner, repo, issueNumber, checks)
console.log(`[${req.key}] Processing rerun-required-workflows`)
await processRerunRequiredWorkflows(req.octokit, req.key, req.body.repository.owner.login, req.body.repository.name, req.body.issue.number, checks)
} catch (e) {
console.error(`Error: ${e.message}`)
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9c1eb87

Please sign in to comment.