Skip to content

Commit 0bcea20

Browse files
committed
update tests
1 parent 0d6902c commit 0bcea20

File tree

6 files changed

+66
-56
lines changed

6 files changed

+66
-56
lines changed

src/prisma/schema.prisma

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,11 @@ model Transactions {
175175
}
176176

177177
model Webhooks {
178-
id Int @id @default(autoincrement()) @map("id")
179-
name String? @map("name")
180-
url String @map("url")
181-
secret String @map("secret")
182-
eventType String @map("evenType")
183-
178+
id Int @id @default(autoincrement()) @map("id")
179+
name String? @map("name")
180+
url String @map("url")
181+
secret String @map("secret")
182+
eventType String @map("evenType")
184183
createdAt DateTime @default(now()) @map("createdAt")
185184
updatedAt DateTime @updatedAt @map("updatedAt")
186185
revokedAt DateTime? @map("revokedAt")

src/server/routes/webhooks/create.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,12 @@ const requestBodySchema = Type.Object({
1313
description: "Webhook URL. Non-HTTPS URLs are not supported.",
1414
examples: ["https://example.com/webhook"],
1515
}),
16-
name: Type.Optional(Type.String()),
17-
eventType: Type.Enum(WebhooksEventTypes),
18-
mtlsClientCert: Type.Optional(
19-
Type.String({
20-
description:
21-
"(For mTLS) The client certificate used to authenticate your to your server.",
22-
}),
23-
),
24-
mtlsClientKey: Type.Optional(
16+
name: Type.Optional(
2517
Type.String({
26-
description:
27-
"(For mTLS) The private key associated with your client certificate.",
28-
}),
29-
),
30-
mtlsCaCert: Type.Optional(
31-
Type.String({
32-
description:
33-
"(For mTLS) The Certificate Authority (CA) that signed your client certificate, used to verify the authenticity of the `mtlsClientCert`. This is only required if using a self-signed certficate.",
18+
minLength: 3,
3419
}),
3520
),
21+
eventType: Type.Enum(WebhooksEventTypes),
3622
});
3723

3824
requestBodySchema.examples = [

src/tests/webhook.test.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
import type { Webhooks } from "@prisma/client";
12
import { describe, expect, it } from "vitest";
3+
import { WebhooksEventTypes } from "../schema/webhooks";
4+
import { generateRequestHeaders } from "../utils/webhook";
25
import { generateSecretHmac256 } from "../utils/webhook/customAuthHeader";
36

47
describe("generateSecretHmac256", () => {
58
it("should generate a valid MAC header with correct structure and values", () => {
6-
const timestampSeconds = new Date("2024-01-01").getTime() / 1000;
9+
const timestamp = new Date("2024-01-01");
710
const nonce = "6b98df0d-5f33-4121-96cb-77a0b9df2bbe";
811

912
const result = generateSecretHmac256({
1013
webhookUrl: "https://example.com/webhook",
1114
body: { bodyArgName: "bodyArgValue" },
12-
timestampSeconds,
15+
timestamp,
1316
nonce,
1417
clientId: "testClientId",
1518
clientSecret: "testClientSecret",
@@ -20,3 +23,35 @@ describe("generateSecretHmac256", () => {
2023
);
2124
});
2225
});
26+
27+
describe("generateRequestHeaders", () => {
28+
const webhook: Webhooks = {
29+
id: 42,
30+
name: "test webhook",
31+
url: "https://www.example.com/webhook",
32+
secret: "test-secret-string",
33+
eventType: WebhooksEventTypes.SENT_TX,
34+
createdAt: new Date(),
35+
updatedAt: new Date(),
36+
revokedAt: null,
37+
};
38+
const body = {
39+
name: "Alice",
40+
age: 25,
41+
occupation: ["Founder", "Developer"],
42+
};
43+
const timestamp = new Date("2024-01-01");
44+
45+
it("Generate a consistent webhook header", () => {
46+
const result = generateRequestHeaders({ webhook, body, timestamp });
47+
48+
expect(result).toEqual({
49+
Accept: "application/json",
50+
Authorization: "Bearer test-secret-string",
51+
"Content-Type": "application/json",
52+
"x-engine-signature":
53+
"ca272da65f1145b9cfadab6d55086ee458eccc03a2c5f7f5ea84094d95b219cc",
54+
"x-engine-timestamp": "1704067200",
55+
});
56+
});
57+
});

src/utils/env.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@ import { z } from "zod";
66
const path = process.env.NODE_ENV === "test" ? ".env.test" : ".env";
77
dotenv.config({ path });
88

9-
export const JsonSchema = z.string().refine(
10-
(value) => {
11-
try {
12-
JSON.parse(value);
13-
return true;
14-
} catch {
15-
return false;
16-
}
17-
},
18-
{ message: "Invalid JSON string" },
19-
);
20-
219
const boolEnvSchema = (defaultBool: boolean) =>
2210
z
2311
.string()

src/utils/webhook.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ const generateSignature = (
2020

2121
const generateAuthorization = (args: {
2222
webhook: Webhooks;
23-
timestampSeconds: number;
23+
timestamp: Date;
2424
body: Record<string, unknown>;
2525
}): string => {
26-
const { webhook, timestampSeconds, body } = args;
26+
const { webhook, timestamp, body } = args;
2727
if (env.ENABLE_CUSTOM_HMAC_AUTH) {
2828
assert(
2929
env.CUSTOM_HMAC_AUTH_CLIENT_ID,
@@ -37,7 +37,7 @@ const generateAuthorization = (args: {
3737
return generateSecretHmac256({
3838
webhookUrl: webhook.url,
3939
body,
40-
timestampSeconds,
40+
timestamp,
4141
nonce: randomUUID(),
4242
clientId: env.CUSTOM_HMAC_AUTH_CLIENT_ID,
4343
clientSecret: env.CUSTOM_HMAC_AUTH_CLIENT_SECRET,
@@ -46,17 +46,16 @@ const generateAuthorization = (args: {
4646
return `Bearer ${webhook.secret}`;
4747
};
4848

49-
const generateRequestHeaders = (
50-
webhook: Webhooks,
51-
body: Record<string, unknown>,
52-
): HeadersInit => {
53-
const timestampSeconds = Math.floor(Date.now() / 1000);
49+
export const generateRequestHeaders = (args: {
50+
webhook: Webhooks;
51+
body: Record<string, unknown>;
52+
timestamp: Date;
53+
}): HeadersInit => {
54+
const { webhook, body, timestamp } = args;
55+
56+
const timestampSeconds = Math.floor(timestamp.getTime() / 1000);
5457
const signature = generateSignature(body, timestampSeconds, webhook.secret);
55-
const authorization = generateAuthorization({
56-
webhook,
57-
timestampSeconds,
58-
body,
59-
});
58+
const authorization = generateAuthorization({ webhook, timestamp, body });
6059
return {
6160
Accept: "application/json",
6261
"Content-Type": "application/json",
@@ -92,7 +91,11 @@ export const sendWebhookRequest = async (
9291
})
9392
: undefined;
9493

95-
const headers = await generateRequestHeaders(webhook, body);
94+
const headers = await generateRequestHeaders({
95+
webhook,
96+
body,
97+
timestamp: new Date(),
98+
});
9699
const resp = await fetch(webhook.url, {
97100
method: "POST",
98101
headers: headers,

src/utils/webhook/customAuthHeader.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createHmac } from "node:crypto";
55
*
66
* @param webhookUrl - The URL to call.
77
* @param body - The request body.
8-
* @param timestampSeconds - The timestamp in seconds.
8+
* @param timestamp - The request timestamp.
99
* @param nonce - A unique string for this request. Should not be re-used.
1010
* @param clientId - Your application's client id.
1111
* @param clientSecret - Your application's client secret.
@@ -14,21 +14,20 @@ import { createHmac } from "node:crypto";
1414
export const generateSecretHmac256 = (args: {
1515
webhookUrl: string;
1616
body: Record<string, unknown>;
17-
timestampSeconds: number;
17+
timestamp: Date;
1818
nonce: string;
1919
clientId: string;
2020
clientSecret: string;
2121
}): string => {
22-
const { webhookUrl, body, timestampSeconds, nonce, clientId, clientSecret } =
23-
args;
22+
const { webhookUrl, body, timestamp, nonce, clientId, clientSecret } = args;
2423

2524
// Create the body hash by hashing the payload.
2625
const bodyHash = createHmac("sha256", clientSecret)
2726
.update(JSON.stringify(body), "utf8")
2827
.digest("base64");
2928

3029
// Create the signature hash by hashing the signature.
31-
const ts = timestampSeconds * 1000; // timestamp expected in milliseconds
30+
const ts = timestamp.getTime(); // timestamp expected in milliseconds
3231
const httpMethod = "POST";
3332
const url = new URL(webhookUrl);
3433
const resourcePath = url.pathname;

0 commit comments

Comments
 (0)