如何使用 Deno 验证 GitHub webhook?
How to validate GitHub webhook with Deno?
我正在尝试使用 Deno 创建一个 GitHub webhook 服务器,但我找不到任何可能的方法来进行验证。
这是我目前尝试使用 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;
});
verify
函数每次都返回 false
。
你的例子非常接近。 GitHub webhook documentation 详细说明了签名 header 架构。值为摘要算法前缀,后跟签名,格式为${ALGO}=${SIGNATURE}
:
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c
因此,您需要从值中提取签名(省略前缀):
const signatureHeader = request.headers.get('X-Hub-Signature-256');
const signature = signatureHeader.slice('sha256='.length);
这是一个完整的工作示例,您可以简单地将其复制并粘贴到 Deno Deploy 上的 playground 或项目中:
gh-webhook-logger.ts
:
import {assert} from 'https://deno.land/std@0.132.0/testing/asserts.ts';
import {Application, Router, NativeRequest} from 'https://deno.land/x/oak@v10.5.1/mod.ts';
import type {ServerRequest} from 'https://deno.land/x/oak@v10.5.1/types.d.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};
}
// Type predicate used to access native Request instance
// Ref: https://github.com/oakserver/oak/issues/501#issuecomment-1084046581
function isNativeRequest (r: ServerRequest): r is NativeRequest {
// deno-lint-ignore no-explicit-any
return (r as any).request instanceof Request;
}
const webhookLogger = new Router().post('/webhook', async (ctx) => {
assert(isNativeRequest(ctx.request.originalRequest));
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});
我正在尝试使用 Deno 创建一个 GitHub webhook 服务器,但我找不到任何可能的方法来进行验证。
这是我目前尝试使用 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;
});
verify
函数每次都返回 false
。
你的例子非常接近。 GitHub webhook documentation 详细说明了签名 header 架构。值为摘要算法前缀,后跟签名,格式为${ALGO}=${SIGNATURE}
:
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c
因此,您需要从值中提取签名(省略前缀):
const signatureHeader = request.headers.get('X-Hub-Signature-256');
const signature = signatureHeader.slice('sha256='.length);
这是一个完整的工作示例,您可以简单地将其复制并粘贴到 Deno Deploy 上的 playground 或项目中:
gh-webhook-logger.ts
:
import {assert} from 'https://deno.land/std@0.132.0/testing/asserts.ts';
import {Application, Router, NativeRequest} from 'https://deno.land/x/oak@v10.5.1/mod.ts';
import type {ServerRequest} from 'https://deno.land/x/oak@v10.5.1/types.d.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};
}
// Type predicate used to access native Request instance
// Ref: https://github.com/oakserver/oak/issues/501#issuecomment-1084046581
function isNativeRequest (r: ServerRequest): r is NativeRequest {
// deno-lint-ignore no-explicit-any
return (r as any).request instanceof Request;
}
const webhookLogger = new Router().post('/webhook', async (ctx) => {
assert(isNativeRequest(ctx.request.originalRequest));
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});