Skip to content

Commit

Permalink
Merge pull request #168 from ubiquity-whilefoo/signature-fix
Browse files Browse the repository at this point in the history
Fix signature and add plugin github token
  • Loading branch information
gentlementlegen authored Oct 28, 2024
2 parents ebf9635 + 9eb1014 commit a47ea4b
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 111 deletions.
Binary file modified bun.lockb
Binary file not shown.
4 changes: 0 additions & 4 deletions src/github/utils/kv-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ export interface KvStore<T> {
* @template T - The type of values to be stored.
*/
export class EmptyStore<T> implements KvStore<T> {
constructor(kv: KVNamespace) {
console.log(`Creating empty kv`, kv);
}

get(id: string): Promise<T | null> {
console.log(`get KV ${id}`);
return Promise.resolve(null);
Expand Down
15 changes: 9 additions & 6 deletions src/sdk/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@ export async function createActionsPlugin<TConfig = unknown, TEnv = unknown, TSu
kernelPublicKey: options?.kernelPublicKey || KERNEL_PUBLIC_KEY,
};

const githubInputs = { ...github.context.payload.inputs };
const signature = githubInputs.signature;
delete githubInputs.signature;
if (!(await verifySignature(pluginOptions.kernelPublicKey, githubInputs, signature))) {
core.setFailed(`Error: Invalid signature`);
const pluginGithubToken = process.env.PLUGIN_GITHUB_TOKEN;
if (!pluginGithubToken) {
core.setFailed("Error: PLUGIN_GITHUB_TOKEN env is not set");
return;
}

const inputs = Value.Decode(inputSchema, github.context.payload.inputs);
const signature = inputs.signature;
if (!(await verifySignature(pluginOptions.kernelPublicKey, inputs, signature))) {
core.setFailed(`Error: Invalid signature`);
return;
}

let config: TConfig;
if (pluginOptions.settingsSchema) {
Expand Down Expand Up @@ -79,7 +82,7 @@ export async function createActionsPlugin<TConfig = unknown, TEnv = unknown, TSu
try {
const result = await handler(context);
core.setOutput("result", result);
await returnDataToKernel(inputs.authToken, inputs.stateId, result);
await returnDataToKernel(pluginGithubToken, inputs.stateId, result);
} catch (error) {
console.error(error);

Expand Down
32 changes: 21 additions & 11 deletions src/sdk/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Context } from "./context";
import { customOctokit } from "./octokit";
import { verifySignature } from "./signature";
import { sanitizeMetadata } from "./util";
import { Type as T } from "@sinclair/typebox";

interface Options {
kernelPublicKey?: string;
Expand All @@ -19,7 +20,17 @@ interface Options {
envSchema?: TAnySchema;
}

export async function createPlugin<TConfig = unknown, TEnv = unknown, TSupportedEvents extends WebhookEventName = WebhookEventName>(
const inputSchema = T.Object({
stateId: T.String(),
eventName: T.String(),
eventPayload: T.Record(T.String(), T.Any()),
authToken: T.String(),
settings: T.Record(T.String(), T.Any()),
ref: T.String(),
signature: T.String(),
});

export function createPlugin<TConfig = unknown, TEnv = unknown, TSupportedEvents extends WebhookEventName = WebhookEventName>(
handler: (context: Context<TConfig, TEnv, TSupportedEvents>) => Promise<Record<string, unknown> | undefined>,
manifest: Manifest,
options?: Options
Expand All @@ -43,18 +54,17 @@ export async function createPlugin<TConfig = unknown, TEnv = unknown, TSupported
throw new HTTPException(400, { message: "Content-Type must be application/json" });
}

const payload = await ctx.req.json();
const signature = payload.signature;
delete payload.signature;
if (!(await verifySignature(pluginOptions.kernelPublicKey, payload, signature))) {
const inputs = Value.Decode(inputSchema, await ctx.req.json());
const signature = inputs.signature;
if (!(await verifySignature(pluginOptions.kernelPublicKey, inputs, signature))) {
throw new HTTPException(400, { message: "Invalid signature" });
}

let config: TConfig;
if (pluginOptions.settingsSchema) {
config = Value.Decode(pluginOptions.settingsSchema, Value.Default(pluginOptions.settingsSchema, payload.settings));
config = Value.Decode(pluginOptions.settingsSchema, Value.Default(pluginOptions.settingsSchema, inputs.settings));
} else {
config = payload.settings as TConfig;
config = inputs.settings as TConfig;
}

let env: TEnv;
Expand All @@ -65,17 +75,17 @@ export async function createPlugin<TConfig = unknown, TEnv = unknown, TSupported
}

const context: Context<TConfig, TEnv, TSupportedEvents> = {
eventName: payload.eventName,
payload: payload.eventPayload,
octokit: new customOctokit({ auth: payload.authToken }),
eventName: inputs.eventName as TSupportedEvents,
payload: inputs.eventPayload,
octokit: new customOctokit({ auth: inputs.authToken }),
config: config,
env: env,
logger: new Logs(pluginOptions.logLevel),
};

try {
const result = await handler(context);
return ctx.json({ stateId: payload.stateId, output: result });
return ctx.json({ stateId: inputs.stateId, output: result });
} catch (error) {
console.error(error);

Expand Down
22 changes: 20 additions & 2 deletions src/sdk/signature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
export async function verifySignature(publicKeyPem: string, payload: unknown, signature: string) {
interface Inputs {
stateId: unknown;
eventName: unknown;
eventPayload: unknown;
authToken: unknown;
settings: unknown;
ref: unknown;
}

export async function verifySignature(publicKeyPem: string, inputs: Inputs, signature: string) {
try {
const inputsOrdered = {
stateId: inputs.stateId,
eventName: inputs.eventName,
eventPayload: inputs.eventPayload,
settings: inputs.settings,
authToken: inputs.authToken,
ref: inputs.ref,
};
console.log(JSON.stringify(inputs));
const pemContents = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim();
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));

Expand All @@ -15,7 +33,7 @@ export async function verifySignature(publicKeyPem: string, payload: unknown, si
);

const signatureArray = Uint8Array.from(atob(signature), (c) => c.charCodeAt(0));
const dataArray = new TextEncoder().encode(JSON.stringify(payload));
const dataArray = new TextEncoder().encode(JSON.stringify(inputsOrdered));

return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, signatureArray, dataArray);
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
webhookSecret: env.APP_WEBHOOK_SECRET,
appId: env.APP_ID,
privateKey: env.APP_PRIVATE_KEY,
pluginChainState: new EmptyStore(env.PLUGIN_CHAIN_STATE),
pluginChainState: new EmptyStore(),
});
bindHandlers(eventHandler);
await eventHandler.webhooks.verifyAndReceive({ id, name: eventName, payload: await request.text(), signature: signatureSha256 });
Expand Down
Loading

0 comments on commit a47ea4b

Please sign in to comment.