Home > Blockchain >  How to validate GitHub webhook with Deno?
How to validate GitHub webhook with Deno?

Time:02-14

I'm trying to make a GitHub webhook server with Deno, but I cannot find any possible way to do the validation.

This is my current attempt using webhooks-methods.js:

import { Application } from "https://deno.land/x/oak/mod.ts";
import { verify } from "https://cdn.skypack.dev/@octokit/webhooks-methods?dts";

const app = new Application();

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (_err) {
    ctx.response.status = 500;
  }
});

const secret = "...";

app.use(async (ctx) => {
  const signature = ctx.request.headers.get("X-Hub-Signature-256");
  if (signature) {
    const payload = await ctx.request.body({ type: "text" }).value;
    const result = await verify(secret, payload, signature);
    console.log(result);
  }
  ctx.response.status = 200;
});

The verify function is returning false every time.

CodePudding user response:

Your example is very close. The GitHub webhook documentation details the signature header schema. The value is a digest algorithm prefix followed by the signature, in the format of ${ALGO}=${SIGNATURE}:

X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c

So, you need to extract the signature from the value (omitting the prefix):

const signatureHeader = request.headers.get('X-Hub-Signature-256');
const signature = signatureHeader.slice('sha256='.length);

Here's a complete, working example that you can simply copy paste into a playground or project on Deno Deploy:

gh-webhook-logger.ts:

import {Application, Router} from 'https://deno.land/x/[email protected]/mod.ts';
import {verify} from 'https://raw.githubusercontent.com/octokit/webhooks-methods.js/v2.0.0/src/web.ts';

// In actual usage, use a private secret:
// const SECRET = Deno.env.get('SIGNING_SECRET');

// But for the purposes of this demo, the exposed secret is:
const SECRET = 'Let me know if you found this to be helpful!';

type GitHubWebhookVerificationStatus = {
  id: string;
  verified: boolean;
}

// Because this uses a native Request, it can be used in other contexts besides Oak (e.g. `std/http/serve`)
async function verifyGitHubWebhook (request: Request): Promise<GitHubWebhookVerificationStatus> {
  const id = request.headers.get('X-GitHub-Delivery');

  // This should be more strict in reality
  if (!id) throw new Error('Not a GH webhhok');

  const signatureHeader = request.headers.get('X-Hub-Signature-256');
  let verified = false;
  if (signatureHeader) {
    const signature = signatureHeader.slice('sha256='.length);
    const payload = await request.clone().text();
    verified = await verify(SECRET, payload, signature);
  }
  return {id, verified};
}

const webhookLogger = new Router().post('/webhook', async (ctx) => {
  const status = await verifyGitHubWebhook(ctx.request.originalRequest.request);
  console.log(status);
  ctx.response.status = 200;
});

const app = new Application()
  .use(webhookLogger.routes())
  .use(webhookLogger.allowedMethods());

// The port is not important in Deno Deploy
await app.listen({port: 8080});

  • Related