Skip to content

Commit

Permalink
fix: Implement HTTP signing for Val.town API requests (#424)
Browse files Browse the repository at this point in the history
* feat: Implement HTTP signing for Val.town API requests

* chore: Remove redundant secret parameter from Val.town integration functions
  • Loading branch information
nadeesha authored Dec 30, 2024
1 parent c3981c1 commit 72c103c
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 5 deletions.
20 changes: 20 additions & 0 deletions control-plane/src/modules/integrations/valtown.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { signedHeaders } from "./valtown";

describe("Val.town Integration", () => {
describe("signedHeaders", () => {
it("should sign headers", async () => {
const result = signedHeaders({
body: JSON.stringify({ test: "value" }),
method: "GET",
path: "/meta",
secret: "secret",
timestamp: "819118800000",
});

expect(result).toStrictEqual({
"X-Signature": "126f621ef1898cba0c6b0c0bd74d443c6004e7c3291a6432f90a233db903c8d8",
"X-Timestamp": "819118800000",
});
});
});
});
43 changes: 40 additions & 3 deletions control-plane/src/modules/integrations/valtown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { deleteServiceDefinition, upsertServiceDefinition } from "../service-def
import { integrationSchema } from "./schema";
import { InstallableIntegration } from "./types";
import { valtownIntegration } from "./constants";
import { env } from "../../utilities/env";
import { createHmac } from "crypto";

// Schema for the /meta endpoint response
const valtownMetaSchema = z.object({
Expand All @@ -28,12 +30,42 @@ const valtownMetaSchema = z.object({

type ValTownMeta = z.infer<typeof valtownMetaSchema>;

export const signedHeaders = ({
body,
method,
path,
secret = env.VALTOWN_HTTP_SIGNING_SECRET,
timestamp = Date.now().toString(),
}: {
body: string;
method: string;
path: string;
secret?: string;
timestamp?: string;
}): Record<string, string> => {
if (!secret) {
logger.error("Missing Val.town HTTP signing secret");
return {};
}

const hmac = createHmac("sha256", secret);
hmac.update(`${timestamp}${method}${path}${body}`);
const xSignature = hmac.digest("hex");

return {
"X-Signature": xSignature,
"X-Timestamp": timestamp,
};
};

/**
* Fetch metadata from Val.town endpoint
*/
async function fetchValTownMeta({ endpoint }: { endpoint: string }): Promise<ValTownMeta> {
export async function fetchValTownMeta({ endpoint }: { endpoint: string }): Promise<ValTownMeta> {
const metaUrl = new URL("/meta", endpoint).toString();
const response = await fetch(metaUrl);
const response = await fetch(metaUrl, {
headers: signedHeaders({ body: "", method: "GET", path: "/meta" }),
});

if (!response.ok) {
logger.error("Failed to fetch Val.town metadata", {
Expand All @@ -52,7 +84,7 @@ async function fetchValTownMeta({ endpoint }: { endpoint: string }): Promise<Val
/**
* Execute a Val.town function
*/
async function executeValTownFunction({
export async function executeValTownFunction({
endpoint,
functionName,
params,
Expand All @@ -67,6 +99,11 @@ async function executeValTownFunction({
method: "POST",
headers: {
"Content-Type": "application/json",
...signedHeaders({
body: JSON.stringify(params),
method: "POST",
path: `/exec/functions/${functionName}`,
}),
},
body: JSON.stringify(params),
});
Expand Down
7 changes: 5 additions & 2 deletions control-plane/src/utilities/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { z } from "zod";
export const truthy = z
.enum(["0", "1", "true", "false"])
.catch("false")
.transform((value) => value == "true" || value == "1");
.transform(value => value == "true" || value == "1");

const envSchema = z
.object({
NODE_ENV: z
.enum(["test", "development", "production"])
.default("development")
.transform((value) => {
.transform(value => {
if (process.env.CI) {
return "test";
}
Expand Down Expand Up @@ -75,6 +75,9 @@ const envSchema = z
POSTHOG_API_KEY: z.string().optional(),
POSTHOG_HOST: z.string().default("https://us.i.posthog.com"),
ANALYTICS_BUCKET_NAME: z.string().optional(),

// Integrations
VALTOWN_HTTP_SIGNING_SECRET: z.string().optional(),
})
.superRefine((value, ctx) => {
if (!value.MANAGEMENT_API_SECRET && !value.JWKS_URL) {
Expand Down

0 comments on commit 72c103c

Please sign in to comment.