diff --git a/.env.example b/.env.example index 73732da..16a009a 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # Webhook -PRIVATE_KEY= -WEBHOOK_URL=http://127.0.0.1:3000 PORT=9102 +WEBHOOK_URL=http://127.0.0.1:3000 # Get Substreams API Key # https://app.pinax.network @@ -16,7 +15,6 @@ START_BLOCK=-10 PRODUCTION_MODE=true # Webhook (Optional) -DISABLE_PING=false -DISABLE_SIGNATURE=false +PRIVATE_KEY= VERBOSE=true MAXIMUM_ATTEMPTS=100 diff --git a/README.md b/README.md index c46c15e..ca509c3 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,8 @@ if (!isVerified) { ```env # Webhook -PRIVATE_KEY= -WEBHOOK_URL=http://127.0.0.1:3000 PORT=9102 +WEBHOOK_URL=http://127.0.0.1:3000 # Get Substreams API Key # https://app.pinax.network @@ -124,10 +123,9 @@ START_BLOCK=-10 PRODUCTION_MODE=true # Webhook (Optional) -DISABLE_PING=false -DISABLE_SIGNATURE=false -VERBOSE=true +PRIVATE_KEY= MAXIMUM_ATTEMPTS=100 +VERBOSE=true ``` ## Help @@ -161,8 +159,6 @@ Options: --verbose Enable verbose logging (default: "false", env: VERBOSE) --webhook-url Webhook URL to send POST (env: WEBHOOK_URL) --private-key Ed25519 private key to sign POST data payload (env: PRIVATE_KEY) - --disable-ping Disable ping on init (choices: "true", "false", default: false, env: DISABLE_PING) - --disable-signature Disable Ed25519 signature (choices: "true", "false", default: false, env: DISABLE_SIGNATURE) --maximum-attempts Maximum attempts to retry POST (default: 100, env: MAXIMUM_ATTEMPTS) -h, --help display help for command ``` diff --git a/bin/cli.ts b/bin/cli.ts index 1714adc..78604fb 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -9,23 +9,18 @@ import { ping } from "../src/ping.js"; export interface WebhookRunOptions extends commander.RunOptions { webhookUrl: string; - privateKey: string; - expiryTime: number; + privateKey?: string; maximumAttempts: number; - disablePing: string; - disableSignature: string; } const webhookUrlOption = new Option("--webhook-url ", "Webhook URL to send POST").makeOptionMandatory().env("WEBHOOK_URL"); -const privateKeyOption = new Option("--private-key ", "Ed25519 private key to sign POST data payload").makeOptionMandatory().env("PRIVATE_KEY"); +const privateKeyOption = new Option("--private-key ", "Ed25519 private key to sign POST data payload").env("PRIVATE_KEY"); // Run Webhook Sink const program = commander.program(pkg); const command = commander.run(program, pkg); command.addOption(webhookUrlOption); command.addOption(privateKeyOption); -command.addOption(new Option("--disable-ping ", "Disable ping on init").choices(["true", "false"]).env("DISABLE_PING").default(false)); -command.addOption(new Option("--disable-signature ", "Disable Ed25519 signature").choices(["true", "false"]).env("DISABLE_SIGNATURE").default(false)); command.addOption(new Option("--maximum-attempts ", "Maximum attempts to retry POST").env("MAXIMUM_ATTEMPTS").default(100)); command.action(action); diff --git a/index.ts b/index.ts index 9cc27ac..f9694ae 100644 --- a/index.ts +++ b/index.ts @@ -18,11 +18,9 @@ export async function action(options: WebhookRunOptions) { // Ping URL to check if it's valid const privateKey = parsePrivateKey(options.privateKey); - if (options.disablePing === "false") { - if (!(await ping(options.webhookUrl, privateKey))) { - logger.error("exiting from invalid PING response"); - process.exit(1); - } + if (!(await ping(options.webhookUrl, privateKey))) { + logger.error("exiting from invalid PING response"); + process.exit(1); } let session: SessionInit; emitter.on("session", (data) => { diff --git a/package.json b/package.json index d85e18e..61b4fa3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.8.5", + "version": "0.8.6", "name": "substreams-sink-webhook", "description": "Substreams Sink Webhook", "type": "module", diff --git a/src/auth.ts b/src/auth.ts index 13f5fe4..dd84fca 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -18,7 +18,8 @@ export function checkKey(key: Hex, type: "public" | "private") { // TweetNaCl.js private key (includes public key) // split the private key from the public key -export function parsePrivateKey(privateKey: string) { +export function parsePrivateKey(privateKey?: string) { + if ( !privateKey ) return; if (typeof privateKey === "string" && privateKey.length === 128) { return privateKey.slice(0, 64); } diff --git a/src/ping.ts b/src/ping.ts index cdc2bf1..4f6d4f7 100644 --- a/src/ping.ts +++ b/src/ping.ts @@ -2,7 +2,7 @@ import { Hex } from "@noble/curves/abstract/utils"; import { keyPair } from "./auth.js"; import { postWebhook } from "./postWebhook.js"; -export async function ping(url: string, privateKey: Hex) { +export async function ping(url: string, privateKey?: Hex) { const body = JSON.stringify({ message: "PING" }); const invalidprivateKey = keyPair().privateKey; @@ -13,6 +13,11 @@ export async function ping(url: string, privateKey: Hex) { return false; } + // if no private key is provided, we can't send an invalid signature + // so we can't test the response to an invalid signature + // so we return true + if (!privateKey) return true; + // send invalid signature (must NOT respond with 200) try { await postWebhook(url, body, invalidprivateKey, { maximumAttempts: 0 }); diff --git a/src/postWebhook.ts b/src/postWebhook.ts index c84e58a..e69a874 100644 --- a/src/postWebhook.ts +++ b/src/postWebhook.ts @@ -11,13 +11,12 @@ interface PostWebhookOptions { disableSignature?: string; } -export async function postWebhook(url: string, body: string, secretKey: Hex, options: PostWebhookOptions = {}) { +export async function postWebhook(url: string, body: string, secretKey?: Hex, options: PostWebhookOptions = {}) { // Retry Policy const initialInterval = 1000; // 1s const maximumAttempts = options.maximumAttempts ?? 100 * initialInterval; const maximumInterval = 100 * initialInterval; const backoffCoefficient = 2; - const disableSignature = options.disableSignature === "true"; let attempts = 0; while (true) { @@ -40,16 +39,20 @@ export async function postWebhook(url: string, body: string, secretKey: Hex, opt try { const timestamp = createTimestamp(); - const signature = disableSignature ? "" : sign(timestamp, body, secretKey); + const headers = new Headers([ + ["content-type", "application/json"], + ["x-signature-timestamp", String(timestamp)] + ]); + + // optional signature + if ( secretKey) { + headers.set("x-signature-ed25519", sign(timestamp, body, secretKey)); + } const response = await fetch(url, { body, method: "POST", - headers: { - "content-type": "application/json", - "x-signature-ed25519": signature, - "x-signature-timestamp": String(timestamp), - }, + headers, }); const status = response.status;