feat: ugh full github bot rewrite with octokit

This commit is contained in:
zkldi
2022-12-19 05:08:56 +00:00
parent a8655be8ff
commit 4dc21507a7
4 changed files with 339 additions and 113 deletions
+2 -2
View File
@@ -10,10 +10,10 @@ function ParseEnvVars() {
{
APP_ID: "string",
WEBHOOK_SECRET: "string",
BASE64_PRIVATE_KEY: "string",
PORT: (self) =>
p.isPositiveInteger(Number(self)) === true ||
"Should be a string representing a whole integer port.",
CLIENT_SECRET: "string",
},
{},
{ allowExcessKeys: true }
@@ -27,7 +27,7 @@ function ParseEnvVars() {
appId: process.env.APP_ID!,
webhookSecret: process.env.WEBHOOK_SECRET!,
port: process.env.PORT!,
clientSecret: process.env.CLIENT_SECRET!,
privateKey: Buffer.from(process.env.BASE64_PRIVATE_KEY!, "base64").toString("utf-8"),
};
}
+30 -107
View File
@@ -1,36 +1,17 @@
/* eslint-disable no-console */
import { ProcessEnv } from "./config";
import bodyParser from "body-parser";
import { App, createNodeMiddleware } from "@octokit/app";
import express from "express";
import fetch from "node-fetch";
import crypto from "crypto";
import { URLSearchParams } from "url";
import type { EmitterWebhookEvent } from "@octokit/webhooks";
import type { Express } from "express";
export const app: Express = express();
// Let NGINX work its magic.
app.set("trust proxy", "loopback");
// Disable query string nesting such as ?a[b]=4 -> {a: {b: 4}}. This
// almost always results in a painful security vuln.
app.set("query parser", "simple");
/**
* Return the status of this bot and the version it's running.
*
* @name GET /
*/
app.get("/", (req, res) =>
res.status(200).json({
success: true,
description: "Github Bot is online!",
body: {
time: Date.now(),
},
})
);
const app = new App({
appId: ProcessEnv.appId,
privateKey: ProcessEnv.privateKey,
webhooks: {
secret: ProcessEnv.webhookSecret,
},
});
/**
* Create a response that contains a link to the seeds diff viewer.
@@ -55,94 +36,36 @@ function ConvertGitHubURL(url: string) {
return url.replace("https://github.com/", "GitHub:");
}
/**
* Listens for GitHub webhook calls.
*
* @note THIS ENDPOINT DOES NOT PARSE REQ BODY INTO AN OBJECT!
* it instead leaves it as a raw string, which is necessary because GitHub have
* an insane secret checking process.
*
* @name POST /webhook
*/
app.post("/webhook", bodyParser.text({ type: "*/*" }), async (req, res) => {
const hash = crypto
.createHmac("SHA256", ProcessEnv.webhookSecret)
.update(req.body as string)
.digest("hex");
app.webhooks.on(
["pull_request.opened", "pull_request.edited"],
async ({ octokit, payload: body }) => {
const filesChanged = (await fetch(
`https://api.github.com/repos/TNG-dev/Tachi/pulls/${body.number}/files`
).then((r) => r.json())) as Array<{ filename: string }>;
if (`sha256=${hash}` !== req.header("X-Hub-Signature-256")) {
console.log(
`Signatures didn't match. ours="sha256=${hash}" theirs=${req.header(
"X-Hub-Signature-256"
)}`
);
return res.status(400).json({
success: false,
description: `Invalid Signature.`,
});
}
const event = req.header("X-GitHub-Event");
if (event !== "pull_request") {
return res.status(400).json({
success: false,
description: `Unsupported Event.`,
});
}
const body = JSON.parse(req.body as string) as EmitterWebhookEvent<"pull_request">["payload"];
if (body.action !== "opened" && body.action !== "edited") {
return res.status(400).json({
success: false,
description: `We only care about opened/edited pull requests!`,
});
}
const filesChanged = (await fetch(
`https://api.github.com/repos/TNG-dev/Tachi/pulls/${body.number}/files`
).then((r) => r.json())) as Array<{ filename: string }>;
// if any file modified in this pr is a collection
if (filesChanged.some((k) => k.filename.startsWith("database-seeds/collections"))) {
// post a link to the diff viewer in the PR comments.
await fetch(body.pull_request._links.comments.href, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${ProcessEnv.clientSecret}`,
},
body: JSON.stringify({
// if any file modified in this pr is a collection
if (filesChanged.some((k) => k.filename.startsWith("database-seeds/collections"))) {
// post a link to the diff viewer in the PR comments.
await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
owner: body.repository.owner.login,
repo: body.repository.name,
issue_number: body.pull_request.number,
body: mkSeedDiffViewMsg(
body.pull_request.head.repo.url,
body.pull_request.head.sha,
body.pull_request.base.repo.url,
body.pull_request.base.sha
),
}),
});
});
}
}
return res.status(200).json({
success: true,
description: "Handled request.",
body: {},
});
});
/**
* 404 Handler. If something gets to this point, they haven't matched with anything.
*
* @name ALL *
*/
app.all("*", (req, res) =>
res.status(404).json({
success: false,
description: "Nothing found here.",
})
);
console.log(`Starting express server on port ${ProcessEnv.port}.`);
const serverMiddleware = createNodeMiddleware(app);
app.listen(ProcessEnv.port);
const expressApp = express();
expressApp.use(serverMiddleware);
console.log(`Listening on port ${ProcessEnv.port}.`);
expressApp.listen(ProcessEnv.port);