"content": "import * as crypto from 'crypto';\nimport * as orvanta from \"@orvanta/client\";\n\nconst SECRET_KEY_VARIABLE_PATH = \"secret_key_path\";\n\n/**\n * Trigger Preprocessor\n *\n * Orvanta allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).\n * The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication\n * before the 'main' logic is executed in your flow.\n *\n * In this example:\n * - The trigger kind is `http`, which means we have access to HTTP-specific metadata.\n * - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:\n * - `x-signature` for verifying the integrity and authenticity of the request.\n * - `x-timestamp` to guard against replay attacks by validating the request time.\n * - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.\n *\n * ⚠️ **Important:** `raw_string` is required for signature verification in this example. \n * Make sure the **\"raw body\"** option is enabled in your HTTP route configuration. \n * If it's not enabled, `raw_string` will be undefined and the script will throw an error.\n *\n * Once the signature and timestamp are verified, we parse the raw JSON body and return the parsed payload as `body`,\n * which is passed as input to the flow's next step.\n *\n * Learn more: https://www.orvanta.dev/docs/core_concepts/preprocessors\n */\nexport async function preprocessor(\n event: {\n kind: 'http';\n body: any;\n raw_string: string | null;\n route: string;\n path: string;\n method: string;\n params: Record<string, string>;\n query: Record<string, string>;\n headers: Record<string, string>;\n },\n) {\n if (event.kind === 'http') {\n\n if (!event.raw_string) {\n throw new Error(\"Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.\");\n }\n\n // Extract signature from headers\n const signature = event.headers['x-signature'] || event.headers['signature'];\n if (!signature) {\n throw new Error('Missing signature in request headers.');\n }\n\n // Check timestamp if present to prevent replay attacks\n const timestamp = event.headers['x-timestamp'] || event.headers['timestamp'];\n if (timestamp) {\n const timestampValue = parseInt(timestamp, 10);\n const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds\n const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes\n\n if (isNaN(timestampValue)) {\n throw new Error('Invalid timestamp format.');\n }\n\n if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {\n throw new Error('Request timestamp is outside the acceptable time window.');\n }\n }\n\n // Verify the signature\n const isValidSignature = await verifySignature(signature, event.raw_string, timestamp);\n if (!isValidSignature) {\n throw new Error('Invalid signature.');\n }\n\n // Parse the body if it's JSON (with error handling)\n let parsedBody: any = {};\n try {\n parsedBody = JSON.parse(event.raw_string);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';\n throw new Error(`Failed to parse request body: ${errorMessage}`);\n }\n\n // Return the parsed body as input to the next step\n return {\n body: parsedBody\n };\n }\n\n throw new Error(`Expected trigger of kind 'http', but received: ${event.kind}`);\n}\n\n/**\n * Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.\n *\n * @param signature - The signature from request headers\n * @param body - Raw request body as a string\n * @param timestamp - Optional timestamp from request headers\n * @returns boolean indicating if the signature is valid\n */\nasync function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {\n const dataToVerify = timestamp\n ? `${body || ''}${timestamp}`\n : (body || '');\n\n const secretKey = await orvanta.getVariable(SECRET_KEY_VARIABLE_PATH);\n\n const expectedSignature = crypto\n .createHmac('sha256', secretKey || '')\n .update(dataToVerify)\n .digest('hex');\n\n try {\n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature)\n );\n } catch (error: unknown) {\n console.error('Signature comparison error:', error);\n return false;\n }\n\n // NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)\n}", |