From fc8493e72e000f2bbd81267a890fb57da66b886c Mon Sep 17 00:00:00 2001 From: Mohammad Bagher Abiyat Date: Thu, 21 Mar 2024 07:23:25 +0330 Subject: [PATCH] gh app webhook --- packages/backend/nitro.config.ts | 14 +++ .../backend/server/routes/webhook.post.ts | 113 +++++++++++++++++- packages/cli/index.ts | 1 - 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/packages/backend/nitro.config.ts b/packages/backend/nitro.config.ts index 2468171b..58b05b5d 100644 --- a/packages/backend/nitro.config.ts +++ b/packages/backend/nitro.config.ts @@ -7,9 +7,23 @@ declare module "nitro-cloudflare-dev" { } } +declare module "nitropack" { + interface NitroRuntimeConfig { + appId: string + webhookSecret: string + privateKey: string + } +} + // https://nitro.unjs.io/config export default defineNitroConfig({ preset: 'cloudflare-pages', modules: [ncb], srcDir: "server", + + runtimeConfig: { + appId: "", + webhookSecret: "", + privateKey: "" + } }); diff --git a/packages/backend/server/routes/webhook.post.ts b/packages/backend/server/routes/webhook.post.ts index ff407e08..99223276 100644 --- a/packages/backend/server/routes/webhook.post.ts +++ b/packages/backend/server/routes/webhook.post.ts @@ -1,3 +1,112 @@ -export default eventHandler((event) => { - console.log(event) +import { App } from "octokit"; + +export default eventHandler(async (event) => { + const { appId, privateKey, webhookSecret } = useRuntimeConfig(event); + const app = new App({ + appId, + privateKey, + webhooks: { + secret: webhookSecret, + }, + }); + app.webhooks.on("workflow_job", ({ octokit, payload }) => { + console.log(payload) + }); + // event.context.cloudflare.request. + + type EmitterWebhookEvent = Parameters[0] + const id: EmitterWebhookEvent['id'] = event.headers.get("x-github-delivery"); + const name = event.headers.get("x-github-event") as EmitterWebhookEvent['name']; + const signature = event.headers.get("x-hub-signature-256") ?? ""; + const payloadString = await event.context.cloudflare.request.text(); + const payload: EmitterWebhookEvent['payload'] = JSON.parse(payloadString); + + // Verify webhook signature + try { + await verifyWebhookSignature(payloadString, signature, webhookSecret); + } catch (error) { + app.log.warn(error.message); + return new Response(`{ "error": "${error.message}" }`, { + status: 400, + headers: { "content-type": "application/json" }, + }); + } + + // Now handle the request + try { + await app.webhooks.receive({ + id, + name, + payload, + } as EmitterWebhookEvent); + + return new Response(`{ "ok": true }`, { + headers: { "content-type": "application/json" }, + }); + } catch (error) { + app.log.error(error); + + return new Response(`{ "error": "${error.message}" }`, { + status: 500, + headers: { "content-type": "application/json" }, + }); + } }); + +// https://github.com/gr2m/cloudflare-worker-github-app-example/blob/main/lib/verify.js +export async function verifyWebhookSignature( + payload: string, + signature: string, + secret: string +) { + if (!signature) { + throw new Error("Signature is missing"); + } else if (!signature.startsWith("sha256=")) { + throw new Error("Invalid signature format"); + } + + const algorithm = { name: "HMAC", hash: "SHA-256" }; + const enc = new TextEncoder(); + const key = await crypto.subtle.importKey( + "raw", + enc.encode(secret), + algorithm, + false, + ["sign", "verify"] + ); + + const signed = await crypto.subtle.sign( + algorithm.name, + key, + enc.encode(payload) + ); + const expectedSignature = "sha256=" + array2hex(signed); + if (!safeCompare(expectedSignature, signature)) { + throw new Error("Signature does not match event payload and secret"); + } + + // All good! +} + +function array2hex(arr: ArrayBuffer) { + return [...new Uint8Array(arr)] + .map((x) => x.toString(16).padStart(2, "0")) + .join(""); +} + +/** Constant-time string comparison */ +function safeCompare(expected: string, actual: string) { + const lenExpected = expected.length; + let result = 0; + + if (lenExpected !== actual.length) { + actual = expected; + result = 1; + } + + for (let i = 0; i < lenExpected; i++) { + result |= expected.charCodeAt(i) ^ actual.charCodeAt(i); + } + + return result === 0; +} diff --git a/packages/cli/index.ts b/packages/cli/index.ts index c492c9eb..18b57e40 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -14,7 +14,6 @@ const eventPayload = await import(process.env.GITHUB_EVENT_PATH, { with: { type: "json" }, }); -console.log(process.env) // console.log(octokit) // console.log(eventPayload)