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});