Skip to content

Commit

Permalink
Merge pull request #51 from gentlementlegen/fix/payload-signature
Browse files Browse the repository at this point in the history
fix: change decode for server payload to match action payload
  • Loading branch information
gentlementlegen authored Jan 17, 2025
2 parents 8cd118a + 254c54c commit ca60218
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 70 deletions.
15 changes: 1 addition & 14 deletions src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import * as core from "@actions/core";
import * as github from "@actions/github";
import { EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks";
import { Type as T } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { LogReturn, Logs } from "@ubiquity-os/ubiquity-os-logger";
import { config } from "dotenv";
import { postComment } from "./comment";
import { Context } from "./context";
import { customOctokit } from "./octokit";
import { verifySignature } from "./signature";
import { commandCallSchema } from "./types/command";
import { inputSchema } from "./types/input-schema";
import { HandlerReturn } from "./types/sdk";
import { jsonType } from "./types/util";
import { getPluginOptions, Options } from "./util";

config();

const inputSchema = T.Object({
stateId: T.String(),
eventName: T.String(),
eventPayload: jsonType(T.Record(T.String(), T.Any())),
command: jsonType(commandCallSchema),
authToken: T.String(),
settings: jsonType(T.Record(T.String(), T.Any())),
ref: T.String(),
signature: T.String(),
});

export async function createActionsPlugin<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSupportedEvents extends WebhookEventName = WebhookEventName>(
handler: (context: Context<TConfig, TEnv, TCommand, TSupportedEvents>) => HandlerReturn,
options?: Options
Expand Down
19 changes: 4 additions & 15 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks";
import { Type as T } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { LogReturn, Logs } from "@ubiquity-os/ubiquity-os-logger";
import { Hono } from "hono";
Expand All @@ -10,21 +9,11 @@ import { Context } from "./context";
import { PluginRuntimeInfo } from "./helpers/runtime-info";
import { customOctokit } from "./octokit";
import { verifySignature } from "./signature";
import { inputSchema } from "./types/input-schema";
import { Manifest } from "./types/manifest";
import { HandlerReturn } from "./types/sdk";
import { getPluginOptions, Options } from "./util";

const inputSchema = T.Object({
stateId: T.String(),
eventName: T.String(),
eventPayload: T.Record(T.String(), T.Any()),
command: T.Union([T.Null(), T.Object({ name: T.String(), parameters: T.Unknown() })]),
authToken: T.String(),
settings: T.Record(T.String(), T.Any()),
ref: T.String(),
signature: T.String(),
});

export function createPlugin<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSupportedEvents extends WebhookEventName = WebhookEventName>(
handler: (context: Context<TConfig, TEnv, TCommand, TSupportedEvents>) => HandlerReturn,
manifest: Manifest,
Expand All @@ -49,11 +38,11 @@ export function createPlugin<TConfig = unknown, TEnv = unknown, TCommand = unkno
console.log(inputSchemaErrors, { depth: null });
throw new HTTPException(400, { message: "Invalid body" });
}
const inputs = Value.Decode(inputSchema, body);
const signature = inputs.signature;
if (!pluginOptions.bypassSignatureVerification && !(await verifySignature(pluginOptions.kernelPublicKey, inputs, signature))) {
const signature = body.signature;
if (!pluginOptions.bypassSignatureVerification && !(await verifySignature(pluginOptions.kernelPublicKey, body, signature))) {
throw new HTTPException(400, { message: "Invalid signature" });
}
const inputs = Value.Decode(inputSchema, body);

let config: TConfig;
if (pluginOptions.settingsSchema) {
Expand Down
14 changes: 14 additions & 0 deletions src/types/input-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Type as T } from "@sinclair/typebox";
import { commandCallSchema } from "./command";
import { jsonType } from "./util";

export const inputSchema = T.Object({
stateId: T.String(),
eventName: T.String(),
eventPayload: jsonType(T.Record(T.String(), T.Any())),
command: jsonType(commandCallSchema),
authToken: T.String(),
settings: jsonType(T.Record(T.String(), T.Any())),
ref: T.String(),
signature: T.String(),
});
48 changes: 7 additions & 41 deletions tests/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,7 @@ const sdkOctokitImportPath = "../src/octokit";
const githubActionImportPath = "@actions/github";
const githubCoreImportPath = "@actions/core";

async function getWorkerInputs(
stateId: string,
eventName: string,
eventPayload: object,
settings: object,
authToken: string,
ref: string,
command: CommandCall | null
) {
const inputs = {
stateId,
eventName,
eventPayload,
settings,
authToken,
ref,
command,
};
const signature = await signPayload(JSON.stringify(inputs), privateKey);

return {
...inputs,
signature,
};
}

async function getWorkflowInputs(
async function getInputs(
stateId: string,
eventName: string,
eventPayload: object,
Expand Down Expand Up @@ -133,15 +107,7 @@ describe("SDK worker tests", () => {
expect(res.status).toEqual(400);
});
it("Should deny POST request with invalid signature", async () => {
const inputs = await getWorkerInputs(
"stateId",
issueCommentedEvent.eventName,
issueCommentedEvent.eventPayload,
{ shouldFail: false },
"test",
"main",
null
);
const inputs = await getInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, { shouldFail: false }, "test", "main", null);

const res = await app.request("/", {
headers: {
Expand Down Expand Up @@ -193,7 +159,7 @@ describe("SDK worker tests", () => {
{ kernelPublicKey: publicKey }
);

const inputs = await getWorkerInputs(
const inputs = await getInputs(
"stateId",
issueCommentedEvent.eventName,
issueCommentedEvent.eventPayload,
Expand Down Expand Up @@ -228,7 +194,7 @@ describe("SDK worker tests", () => {
});
});
it("Should accept correct request", async () => {
const inputs = await getWorkerInputs(
const inputs = await getInputs(
"stateId",
issueCommentedEvent.eventName,
issueCommentedEvent.eventPayload,
Expand Down Expand Up @@ -265,7 +231,7 @@ describe("SDK actions tests", () => {
};

it("Should accept correct request", async () => {
const githubInputs = await getWorkflowInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "main", {
const githubInputs = await getInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "main", {
name: "test",
parameters: { param1: "test" },
});
Expand Down Expand Up @@ -327,7 +293,7 @@ describe("SDK actions tests", () => {
});
});
it("Should deny invalid signature", async () => {
const githubInputs = await getWorkflowInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "main", null);
const githubInputs = await getInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "main", null);

jest.unstable_mockModule("@actions/github", () => ({
default: {},
Expand Down Expand Up @@ -364,7 +330,7 @@ describe("SDK actions tests", () => {
expect(setOutput).not.toHaveBeenCalled();
});
it("Should accept inputs in different order", async () => {
const githubInputs = await getWorkflowInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "main", null);
const githubInputs = await getInputs("stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "main", null);

jest.unstable_mockModule(githubActionImportPath, () => ({
default: {},
Expand Down

0 comments on commit ca60218

Please sign in to comment.