Skip to content

Commit

Permalink
r
Browse files Browse the repository at this point in the history
  • Loading branch information
julianbenegas committed Jan 3, 2025
1 parent 7c31138 commit 32275ed
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"tame-otters-shake",
"warm-books-compete",
"witty-chefs-camp",
"witty-rockets-melt"
"witty-rockets-melt",
"young-camels-play"
]
}
5 changes: 5 additions & 0 deletions .changeset/young-camels-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"basehub": patch
---

auth webhooks
6 changes: 6 additions & 0 deletions packages/basehub/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# basehub

## 8.0.0-canary.41

### Patch Changes

- auth webhooks

## 8.0.0-canary.40

### Patch Changes
Expand Down
7 changes: 5 additions & 2 deletions packages/basehub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "basehub",
"description": "A very fast Headless CMS.",
"author": "JB <[email protected]>",
"version": "8.0.0-canary.40",
"version": "8.0.0-canary.41",
"license": "MIT",
"repository": "basehub-ai/basehub",
"bugs": "https://github.com/basehub-ai/basehub/issues",
Expand All @@ -26,6 +26,7 @@
"src/next/toolbar",
"src/react/pump",
"src/events",
"src/workflows",
"react-svg.js",
"react-svg.d.ts",
"react-rich-text.js",
Expand All @@ -38,6 +39,8 @@
"api-transaction.d.ts",
"events.js",
"events.d.ts",
"workflows.js",
"workflows.d.ts",
"search.js",
"search.d.ts",
"next-image.js",
Expand All @@ -52,7 +55,7 @@
"build:client": "tsup --config tsup-client.config.ts"
},
"dependencies": {
"@basehub/genql": "9.0.0-canary.9",
"@basehub/genql": "9.0.0-canary.10",
"@basehub/mutation-api-helpers": "2.0.7",
"@radix-ui/react-slot": "^1.1.0",
"@shikijs/transformers": "1.17.7",
Expand Down
32 changes: 32 additions & 0 deletions packages/basehub/src/bin/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ import type { Language } from './react-code-block';
const reactPumpOutDir = path.join(basehubOutputPath, "react-pump");
const nextToolbarOutDir = path.join(basehubOutputPath, "next-toolbar");
const analyticsOutDir = path.join(basehubOutputPath, "events");
const workflowsOutDir = path.join(basehubOutputPath, "workflows");

await esbuild.build({
entryPoints: [generatedMainExportPath],
Expand Down Expand Up @@ -414,13 +415,18 @@ import type { Language } from './react-code-block';
path.join(basehubModulePath, "dts", "src", "events"),
analyticsOutDir
);
copyDirSync(
path.join(basehubModulePath, "dts", "src", "workflows"),
workflowsOutDir
);

if (args["--debug"]) {
console.log(`[basehub] copied dts for react pump to: ${reactPumpOutDir}`);
console.log(
`[basehub] copied dts for next toolbar to: ${nextToolbarOutDir}`
);
console.log(`[basehub] copied dts for events to: ${analyticsOutDir}`);
console.log(`[basehub] copied dts for workflows to: ${workflowsOutDir}`);
}

appendGeneratedCodeBanner(basehubOutputPath, args["--banner"]);
Expand Down Expand Up @@ -486,6 +492,11 @@ import type { Language } from './react-code-block';
);
const analyticsIndexJsPath = path.join(basehubModulePath, "events.js");
const analyticsIndexDtsPath = path.join(basehubModulePath, "events.d.ts");
const workflowsIndexJsPath = path.join(basehubModulePath, "workflows.js");
const workflowsIndexDtsPath = path.join(
basehubModulePath,
"workflows.d.ts"
);
fs.writeFileSync(
indexJsPath,
ensureCrossPlatformTsImport(
Expand Down Expand Up @@ -558,6 +569,24 @@ import type { Language } from './react-code-block';
)}";`
)
);
fs.writeFileSync(
workflowsIndexJsPath,
ensureCrossPlatformTsImport(
`export * from "${path.relative(
basehubModulePath,
path.join(workflowsOutDir, "index.js")
)}";`
)
);
fs.writeFileSync(
workflowsIndexDtsPath,
ensureCrossPlatformTsImport(
`export * from "${path.relative(
basehubModulePath,
path.join(workflowsOutDir, "index.d.ts")
)}";`
)
);

if (args["--debug"]) {
console.log(
Expand All @@ -572,6 +601,9 @@ import type { Language } from './react-code-block';
console.log(
`[basehub] aliased events index.js and index.d.ts to: ${analyticsOutDir}`
);
console.log(
`[basehub] aliased workflows index.js and index.d.ts to: ${workflowsOutDir}`
);
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/basehub/src/workflows/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./primitive";
135 changes: 135 additions & 0 deletions packages/basehub/src/workflows/primitive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import {
// @ts-ignore
Scalars,
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
} from "../schema";
import crypto from "crypto";

/* -------------------------------------------------------------------------------------------------
* Client
* -----------------------------------------------------------------------------------------------*/

type KeysStartingWith<Obj, Prefix extends string> = {
[K in keyof Obj]: K extends `${Prefix}${string}` ? K : never;
}[keyof Obj];

type ExtractWorkflowKey<T extends string> = T extends `${infer Base}:${string}`
? Base
: T;

// Get all event key types (bshb_event_*)
export type WorkflowKeys = KeysStartingWith<Scalars, "bshb_workflow">;

// Map from event key to its schema type
type WorkflowSchemaMap = {
// @ts-ignore
[K in WorkflowKeys]: Scalars[`schema_${K}`];
};

export const authenticateWebhook = async <
Key extends `${WorkflowKeys}:${string}`,
>({
secret,
body,
signature,
}: {
/**
* The body of the incoming webhook request
* Can be:
* - Parsed JSON from request.json()
* - Raw string from request.text()
* - ReadableStream from request.body
*/
body: unknown;
/**
* The signature of the incoming webhook request—you get this via request.headers["x-basehub-webhook-signature"]
* This should be a hex-encoded HMAC SHA-256 hash of the request body
*/
signature: string;
/**
* The secret used for verifying the incoming webhook request—you get this via the BaseHub API
* This secret should never be exposed in requests or responses
*/
secret: Key;
}): Promise<
| { success: true; payload: WorkflowSchemaMap[ExtractWorkflowKey<Key>] }
| { success: false; error: string }
> => {
try {
// Handle different body types
let rawBody: string;
let parsedBody: unknown;

if (body instanceof ReadableStream) {
// Convert stream to text
const reader = body.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const bodyText = new TextDecoder().decode(
new Uint8Array(chunks.flatMap((chunk) => Array.from(chunk)))
);
rawBody = bodyText;
parsedBody = JSON.parse(bodyText);
} else if (typeof body === "string") {
rawBody = body;
parsedBody = JSON.parse(body);
} else {
// Already parsed JSON
rawBody = JSON.stringify(body);
parsedBody = body;
}

if (typeof parsedBody !== "object" || parsedBody === null) {
return { success: false, error: "Invalid body" };
}

const encoder = new TextEncoder();
const bodyData = encoder.encode(rawBody);
const secretData = encoder.encode(secret);

const key = await crypto.subtle.importKey(
"raw",
secretData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);

const signed = await crypto.subtle.sign("HMAC", key, bodyData);
const calculatedSignature = Array.from(new Uint8Array(signed))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");

if (signature.length !== calculatedSignature.length) {
return { success: false, error: "Invalid signature" };
}

let mismatch = 0;
for (let i = 0; i < signature.length; i++) {
mismatch |= signature.charCodeAt(i) ^ calculatedSignature.charCodeAt(i);
}

if (mismatch !== 0) {
return { success: false, error: "Invalid signature" };
}

return {
success: true,
payload: parsedBody as WorkflowSchemaMap[ExtractWorkflowKey<Key>],
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: "Signature verification failed",
};
}
};
2 changes: 2 additions & 0 deletions packages/basehub/workflows.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* eslint-disable import/no-unresolved */
export * from "./dist/generated-client/workflows";
6 changes: 6 additions & 0 deletions packages/basehub/workflows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
throw new Error(
`\`workflows\` not found. Make sure to run \`npx basehub\` in order to generate it.
If the error persist, please raise an issue at https://github.com/basehub-ai/basehub
`
);
7 changes: 7 additions & 0 deletions playground/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# playground

## 0.0.164-canary.41

### Patch Changes

- Updated dependencies
- [email protected]

## 0.0.164-canary.40

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "playground",
"private": true,
"version": "0.0.164-canary.40",
"version": "0.0.164-canary.41",
"scripts": {
"dev": "basehub dev & next dev --port 3003",
"build": "basehub && next build",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 32275ed

Please sign in to comment.