c0dfa8048a
* feat: BYO Auth Define client-side auth resolvers to be able to supply custom authentication credentials for integrations before a run is performed - Added new defineAuthResolver - Update all integrations to support the new auth resolvers - Strip internal symbols from .d.ts in integrations and trigger-sdk - Added BYO Auth docs - Update Dynamic Schedule to support associated account IDs - Create external accounts just-in-time - Added Account ID field to test job when there are external auth integrations - Show Account ID on run dashboard - Added new Run error state called “Unresolved auth” * Added changeset * Remove @internal from TriggerIntegration public methods * Add void to the result union * DynamicTriggers now work with the new BYO auth system, and added a bunch of docs and docs changes * Add additional key material for registering dynamic trigger task * Add new define* instance methods to the overview
296 lines
7.7 KiB
TypeScript
296 lines
7.7 KiB
TypeScript
import { Webhooks } from "@octokit/webhooks";
|
|
import { omit, safeJsonParse } from "@trigger.dev/integration-kit";
|
|
import type { Logger } from "@trigger.dev/sdk";
|
|
import { ExternalSource, HandlerEvent } from "@trigger.dev/sdk";
|
|
import { z } from "zod";
|
|
import { Github } from "./index";
|
|
|
|
type WebhookData = {
|
|
id: number;
|
|
active: boolean;
|
|
events: string[];
|
|
config: {
|
|
url: string;
|
|
};
|
|
};
|
|
|
|
function webhookData(data: any): data is WebhookData {
|
|
return (
|
|
typeof data === "object" &&
|
|
data !== null &&
|
|
typeof data.id === "number" &&
|
|
typeof data.config === "object"
|
|
);
|
|
}
|
|
|
|
export function createRepoEventSource(
|
|
integration: Github
|
|
): ExternalSource<Github, { owner: string; repo: string }, "HTTP", {}> {
|
|
return new ExternalSource("HTTP", {
|
|
id: "github.repo",
|
|
version: "0.1.1",
|
|
schema: z.object({ owner: z.string(), repo: z.string() }),
|
|
integration,
|
|
key: (params) => `${params.owner}/${params.repo}`,
|
|
properties: (params) => [
|
|
{
|
|
label: "Owner",
|
|
text: params.owner,
|
|
url: `https://github.com/${params.owner}`,
|
|
},
|
|
{
|
|
label: "Repo",
|
|
text: params.repo,
|
|
url: `https://github.com/${params.owner}/${params.repo}`,
|
|
},
|
|
],
|
|
filter: (params) => ({
|
|
repository: {
|
|
full_name: [`${params.owner}/${params.repo}`],
|
|
},
|
|
}),
|
|
handler: webhookHandler,
|
|
register: async (event, io, ctx) => {
|
|
const { params, source: httpSource, options } = event;
|
|
|
|
const registeredOptions = {
|
|
event: options.event.desired,
|
|
};
|
|
|
|
if (httpSource.active && webhookData(httpSource.data)) {
|
|
const hasMissingOptions = Object.values(options).some(
|
|
(option) => option.missing.length > 0
|
|
);
|
|
if (!hasMissingOptions) return;
|
|
|
|
// We need to update the webhook to add the new events and then return
|
|
const newWebhookData = await io.integration.updateWebhook("update-webhook", {
|
|
owner: params.owner,
|
|
repo: params.repo,
|
|
hookId: httpSource.data.id,
|
|
url: httpSource.url,
|
|
secret: httpSource.secret,
|
|
addEvents: options.event.missing,
|
|
});
|
|
|
|
return {
|
|
data: newWebhookData,
|
|
options: registeredOptions,
|
|
};
|
|
}
|
|
|
|
const webhooks = await io.integration.listWebhooks("list-webhooks", {
|
|
owner: params.owner,
|
|
repo: params.repo,
|
|
});
|
|
|
|
const existingWebhook = webhooks.find((w) => w.config.url === httpSource.url);
|
|
|
|
// There is an existing webhook, but it's not synced with Trigger.dev, so we need to update it with the secret
|
|
if (existingWebhook && existingWebhook.active) {
|
|
const updatedWebhook = await io.integration.updateWebhook("update-webhook", {
|
|
owner: params.owner,
|
|
repo: params.repo,
|
|
hookId: existingWebhook.id,
|
|
url: httpSource.url,
|
|
secret: httpSource.secret,
|
|
addEvents: options.event.missing,
|
|
});
|
|
|
|
return {
|
|
data: updatedWebhook,
|
|
options: registeredOptions,
|
|
};
|
|
}
|
|
|
|
const webhook = await io.integration.createWebhook("create-webhook", {
|
|
owner: params.owner,
|
|
repo: params.repo,
|
|
events: options.event.desired,
|
|
url: httpSource.url,
|
|
secret: httpSource.secret,
|
|
});
|
|
|
|
return { data: webhook, options: registeredOptions };
|
|
},
|
|
});
|
|
}
|
|
|
|
export function createOrgEventSource(
|
|
integration: Github
|
|
): ExternalSource<Github, { org: string }, "HTTP", {}> {
|
|
return new ExternalSource("HTTP", {
|
|
id: "github.org",
|
|
version: "0.1.1",
|
|
integration,
|
|
schema: z.object({ org: z.string() }),
|
|
key: (params) => params.org,
|
|
properties: (params) => [
|
|
{
|
|
label: "Org",
|
|
text: params.org,
|
|
url: `https://github.com/${params.org}`,
|
|
},
|
|
],
|
|
filter: (params) => ({
|
|
organization: {
|
|
login: [params.org],
|
|
},
|
|
}),
|
|
handler: webhookHandler,
|
|
register: async (event, io, ctx) => {
|
|
const { params, source: httpSource, options } = event;
|
|
|
|
const registeredOptions = {
|
|
event: options.event.desired,
|
|
};
|
|
|
|
const hasMissingOptions = Object.values(options).some((option) => option.missing.length > 0);
|
|
|
|
if (
|
|
httpSource.active &&
|
|
webhookData(httpSource.data) &&
|
|
httpSource.secret &&
|
|
hasMissingOptions
|
|
) {
|
|
const existingData = httpSource.data;
|
|
|
|
// We need to update the webhook to add the new events and then return
|
|
const newWebhookData = await io.integration.updateOrgWebhook("update-webhook", {
|
|
org: params.org,
|
|
hookId: existingData.id,
|
|
url: httpSource.url,
|
|
secret: httpSource.secret,
|
|
addEvents: options.event.missing,
|
|
});
|
|
|
|
return {
|
|
secret: httpSource.secret,
|
|
data: newWebhookData,
|
|
options: registeredOptions,
|
|
};
|
|
}
|
|
|
|
const webhooks = await io.integration.listOrgWebhooks("list-webhooks", {
|
|
org: params.org,
|
|
});
|
|
|
|
const existingWebhook = webhooks.find((w) => w.config.url === httpSource.url);
|
|
|
|
const secret = Math.random().toString(36).slice(2);
|
|
|
|
if (existingWebhook && existingWebhook.active) {
|
|
const updatedWebhook = await io.integration.updateOrgWebhook("update-webhook", {
|
|
org: params.org,
|
|
hookId: existingWebhook.id,
|
|
url: httpSource.url,
|
|
secret,
|
|
});
|
|
|
|
return {
|
|
secret,
|
|
data: updatedWebhook,
|
|
options: registeredOptions,
|
|
};
|
|
}
|
|
|
|
const webhook = await io.integration.createOrgWebhook("create-webhook", {
|
|
org: params.org,
|
|
events: options.event.desired,
|
|
url: httpSource.url,
|
|
secret,
|
|
});
|
|
|
|
return { secret, data: webhook, options: registeredOptions };
|
|
},
|
|
});
|
|
}
|
|
|
|
// Parses the body of a request
|
|
// If it's a Buffer, it will be parsed as JSON
|
|
function parseBody(body: any) {
|
|
if (Buffer.isBuffer(body)) {
|
|
return safeJsonParse(body.toString());
|
|
}
|
|
|
|
if (typeof body === "string") {
|
|
return safeJsonParse(body);
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
async function webhookHandler(event: HandlerEvent<"HTTP">, logger: Logger) {
|
|
logger.debug("[inside github integration] Handling github repo event");
|
|
|
|
const { rawEvent: request, source } = event;
|
|
|
|
if (!request.body) {
|
|
logger.debug("[inside github integration] No body found");
|
|
|
|
return;
|
|
}
|
|
|
|
const rawBody = await request.text();
|
|
|
|
const deliveryId = request.headers.get("x-github-delivery");
|
|
const hookId = request.headers.get("x-github-hook-id");
|
|
const signature = request.headers.get("x-hub-signature-256");
|
|
|
|
if (source.secret && signature) {
|
|
const githubWebhooks = new Webhooks({
|
|
secret: source.secret,
|
|
});
|
|
|
|
if (!githubWebhooks.verify(rawBody, signature)) {
|
|
logger.debug("[inside github integration] Unable to verify the signature of the rawBody", {
|
|
signature,
|
|
secret: source.secret,
|
|
});
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
const name = request.headers.get("x-github-event") ?? "unknown";
|
|
const allHeaders = Object.fromEntries(request.headers.entries());
|
|
|
|
const context = omit(allHeaders, [
|
|
"x-github-event",
|
|
"x-github-delivery",
|
|
"x-hub-signature-256",
|
|
"x-hub-signature",
|
|
"content-type",
|
|
"content-length",
|
|
"accept",
|
|
"accept-encoding",
|
|
"x-forwarded-proto",
|
|
]);
|
|
|
|
const payload = parseBody(rawBody);
|
|
|
|
if (!payload) {
|
|
logger.debug("[inside github integration] Unable to parse the rawBody");
|
|
|
|
return;
|
|
}
|
|
|
|
logger.debug("[inside github integration] Returning an event for the webhook!", {
|
|
name,
|
|
payload,
|
|
context,
|
|
});
|
|
|
|
return {
|
|
events: [
|
|
{
|
|
id: [hookId, deliveryId].join(":"),
|
|
source: "github.com",
|
|
payload,
|
|
name,
|
|
context,
|
|
},
|
|
],
|
|
};
|
|
}
|