import * as crypto from 'crypto';
import * as wmill from "windmill-client";
const SECRET_KEY_VARIABLE_PATH = "secret_key_path";
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
*
* ⚠️ **Important:** `raw_string` is required for signature verification in this example.
* Make sure the **"raw body"** option is enabled in your HTTP route configuration.
* If it's not enabled, `raw_string` will be undefined and the script will throw an error.
*
* Once the signature and timestamp are verified, we parse the raw JSON body and return the parsed payload as `body`, which is passed directly into the `main()` function as a named argument.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
event: {
kind: 'http';
body: any;
raw_string: string | null;
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
) {
if (event.kind === 'http') {
if (!event.raw_string) {
throw new Error("Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.");
}
// Extract signature from headers
const signature = event.headers['x-signature'] || event.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers.');
}
// Check timestamp if present to prevent replay attacks
const timestamp = event.headers['x-timestamp'] || event.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format.');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window.');
}
}
// Verify the signature
const isValidSignature = await verifySignature(signature, event.raw_string, timestamp);
if (!isValidSignature) {
throw new Error('Invalid signature.');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(event.raw_string);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
body: parsedBody
};
}
throw new Error(`Expected trigger of kind 'http', but received: ${event.kind}`);
}
/**
* Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.
*
* @param signature - The signature from request headers
* @param body - Raw request body as a string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if the signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
const dataToVerify = timestamp
? `${body || ''}${timestamp}`
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param body - Parsed request body
*/
export async function main(body: any) {
// At this point, the request has been authenticated (signature + timestamp) and body safely parsed
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
}
};
}Submitted by hugo697 361 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client";
const SECRET_KEY_VARIABLE_PATH = "secret_key_path";
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
*
* ⚠️ **Important:** `raw_string` is required for signature verification in this example.
* Make sure the **"raw body"** option is enabled in your HTTP route configuration.
* If it's not enabled, `raw_string` will be undefined and the script will throw an error.
*
* Once the signature and timestamp are verified, we parse the raw JSON body and return the parsed payload as `body`, which is passed directly into the `main()` function as a named argument.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string?: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
if (!raw_string) {
throw new Error("Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.");
}
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers.');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format.');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window.');
}
}
// Verify the signature
const isValidSignature = await verifySignature(signature, raw_string, timestamp);
if (!isValidSignature) {
throw new Error('Invalid signature.');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
body: parsedBody
};
}
throw new Error(`Expected trigger of kind 'http', but received: ${wm_trigger.kind}`);
}
/**
* Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.
*
* @param signature - The signature from request headers
* @param body - Raw request body as a string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if the signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
const dataToVerify = timestamp
? `${body || ''}${timestamp}`
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param body - Parsed request body
*/
export async function main(body: any) {
// At this point, the request has been authenticated (signature + timestamp) and body safely parsed
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client";
const SECRET_KEY_VARIABLE_PATH = "secret_key_path";
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
*
* ⚠️ **Important:** `raw_string` is required for signature verification in this example.
* Make sure the **"raw body"** option is enabled in your HTTP route configuration.
* If it's not enabled, `raw_string` will be undefined and the script will throw an error.
*
* Once the signature and timestamp are verified, we parse the raw JSON body and return the parsed payload as `body`, which is passed directly into the `main()` function as a named argument.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string?: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
if (!raw_string) {
throw new Error("Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.");
}
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers.');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format.');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window.');
}
}
// Verify the signature
const isValidSignature = await verifySignature(signature, raw_string, timestamp);
if (!isValidSignature) {
throw new Error('Invalid signature.');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
body: parsedBody
};
}
throw new Error(`Expected trigger of kind 'http', but received: ${wm_trigger.kind}`);
}
/**
* Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.
*
* @param signature - The signature from request headers
* @param body - Raw request body as a string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if the signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
const dataToVerify = timestamp
? `${body || ''}${timestamp}`
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(body: any) {
// At this point, the request has been authenticated (signature + timestamp) and body safely parsed
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client";
const SECRET_KEY_VARIABLE_PATH = "secret_key_path";
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* ✅ In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
*
* ⚠️ **Important:** `raw_string` is required for signature verification in this example.
* Make sure the **"raw body"** option is enabled in your HTTP route configuration.
* If it's not enabled, `raw_string` will be undefined and the script will throw an error.
*
* - Once the signature and timestamp are verified, we parse the raw JSON body and return structured data to `main()`.
*
* ✅ The result of this preprocessor is passed directly into the `main()` function as named arguments:
* - `http`: contains the HTTP request metadata, including headers, path, method, etc.
* - `body`: the parsed JSON payload from the request body
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string?: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
if (!raw_string) {
throw new Error("Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.");
}
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers.');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format.');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window.');
}
}
// Verify the signature
const isValidSignature = await verifySignature(signature, raw_string, timestamp);
if (!isValidSignature) {
throw new Error('Invalid signature.');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected trigger of kind 'http', but received: ${wm_trigger.kind}`);
}
/**
* Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.
*
* @param signature - The signature from request headers
* @param body - Raw request body as a string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if the signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
const dataToVerify = timestamp
? `${body || ''}${timestamp}`
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// At this point, the request has been authenticated (signature + timestamp) and body safely parsed
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client";
const SECRET_KEY_VARIABLE_PATH = "secret_key_path";
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* ✅ In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
*
* ⚠️ **Important:** `raw_string` is required for signature verification in this example.
* Make sure the **"raw body"** option is enabled in your HTTP route configuration.
* If it's not enabled, `raw_string` will be undefined and the script will throw an error.
*
* - Once the signature and timestamp are verified, we parse the raw JSON body and return structured data to `main()`.
*
* The result of this preprocessor is passed directly into the `main()` function as named arguments.
* This ensures that your business logic only runs if the request is authenticated and well-formed.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string?: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
if (!raw_string) {
throw new Error("Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.");
}
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers.');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format.');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window.');
}
}
// Verify the signature
const isValidSignature = await verifySignature(signature, raw_string, timestamp);
if (!isValidSignature) {
throw new Error('Invalid signature.');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected trigger of kind 'http', but received: ${wm_trigger.kind}`);
}
/**
* Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.
*
* @param signature - The signature from request headers
* @param body - Raw request body as a string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if the signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
const dataToVerify = timestamp
? `${body || ''}${timestamp}`
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// At this point, the request has been authenticated (signature + timestamp) and body safely parsed
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client";
const SECRET_KEY_VARIABLE_PATH = "secret_key_path";
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* ✅ In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
* ⚠️ Note: `raw_string` is only available if the **"raw body"** option is enabled in the HTTP route configuration.
* If it's not enabled, `raw_string` will be empty.
* - Once the signature and timestamp are verified, we parse the raw JSON body and return structured data to `main()`.
*
* The result of this preprocessor is passed directly into the `main()` function as named arguments.
* This ensures that your business logic only runs if the request is authenticated and well-formed.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string?: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
if (!raw_string) {
throw new Error("Missing raw body. Ensure the 'raw body' option is enabled in the HTTP route configuration.");
}
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers.');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format.');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window.');
}
}
// Verify the signature
const isValidSignature = await verifySignature(signature, raw_string, timestamp);
if (!isValidSignature) {
throw new Error('Invalid signature.');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected trigger of kind 'http', but received: ${wm_trigger.kind}`);
}
/**
* Verifies the HMAC-SHA256 signature against the raw body and optional timestamp.
*
* @param signature - The signature from request headers
* @param body - Raw request body as a string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if the signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
const dataToVerify = timestamp
? `${body || ''}${timestamp}`
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Modify this logic if your provider uses a different signing mechanism (e.g., Base64, RSA, etc.)
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// At this point, the request has been authenticated (signature + timestamp) and body safely parsed
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client"
const SECRET_KEY_VARIABLE_PATH = "secret_key_path"
/**
* Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* Windmill allows you to define a `preprocessor` for any trigger type (HTTP, WebSocket, Kafka, Email, etc.).
* The preprocessor gives you the ability to perform custom logic such as validation, transformation, or authentication
* before the `main()` function is executed.
*
* ✅ In this example:
* - The trigger kind is `http`, which means we have access to HTTP-specific metadata.
* - We use `wm_trigger.http.headers` to extract custom headers from the incoming HTTP request, such as:
* - `x-signature` for verifying the integrity and authenticity of the request.
* - `x-timestamp` to guard against replay attacks by validating the request time.
* - The `raw_string` argument contains the **raw JSON body** of the request as a string — which is crucial for verifying the HMAC signature.
* - Once the signature and timestamp are verified, we parse the raw JSON body and return structured data to `main()`.
*
* The result of this preprocessor is passed directly into the `main()` function as named arguments.
* This ensures that your business logic only runs if the request is authenticated and well-formed.
*
* 🛡️ Use preprocessors to separate security/validation logic from your core processing logic for better maintainability and safety.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
// Check if timestamp is within acceptable window (e.g., 5 minutes)
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window');
}
}
// Verify the signature
if (!(await verifySignature(signature, raw_string, timestamp))) {
throw new Error('Invalid signature');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
// Properly type the error
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected http trigger kind, got: ${wm_trigger.kind}`);
}
/**
* Verify signature against request body and timestamp
*
* @param signature - The signature from request headers
* @param body - Raw request body as string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
// IMPORTANT: This is an example implementation and should be updated
// with your specific provider's signature verification logic
// Example HMAC-SHA256 signature verification:
const dataToVerify = timestamp
? `${body || ''}${timestamp}` // Include timestamp in verification if present
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
// This can happen if the signatures are different lengths
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Each provider may have different signature formats and verification methods.
// For example:
// - Some use Base64 encoding instead of hex
// - Some require specific header ordering in the signature calculation
// - Some use different algorithms (RSA, Ed25519, etc.)
// Update this function according to your provider's documentation
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// Implement the main function logic here
// At this point, the signature has been verified and timestamp checked in the preprocessor
// Example response
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client"
const SECRET_KEY_VARIABLE_PATH = "secret_key_path"
/**
* Trigger preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* This example shows how to handle and validate an incoming HTTP request using signature-based verification.
*
* The logic demonstrates:
* - Ensuring the trigger is of kind `http` and extracting relevant metadata via `wm_trigger`.
* - Retrieving header fields like `x-signature` and `x-timestamp` for signature verification and replay attack protection.
* - Using `raw_string`, which contains the **raw body** of the request, as a crucial component in constructing the payload for signature verification (ensuring it hasn't been tampered with).
* - If the signature and timestamp checks pass, the request body is safely parsed and passed to the `main()` function.
*
* This separation of concerns (validation in preprocessor, business logic in main) helps keep the auto-generated UI clean and maintainable.
*
* The preprocessor receives the same data `main` would if no preprocessor was used,
* plus trigger metadata in the `wm_trigger` object:
* - Webhook/HTTP: `{ wm_trigger, bodyKey1, bodyKey2, ... }`
* - Postgres: `{ transaction_type, schema_name, table_name, row, wm_trigger }`
* - WebSocket/Kafka/NATS/SQS/MQTT: `{ msg, wm_trigger }`
* - Email: `{ raw_email, parsed_email, wm_trigger }`
*
* The returned object defines the parameter values passed to `main()`.
* e.g., { b: 1, a: 2 } → Calls `main(2, 1)`, assuming `main` is defined as `main(a: number, b: number)`.
* Ensure that the parameter names in `main` match the keys in the returned object.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
// Check if timestamp is within acceptable window (e.g., 5 minutes)
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window');
}
}
// Verify the signature
if (!(await verifySignature(signature, raw_string, timestamp))) {
throw new Error('Invalid signature');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
// Properly type the error
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected http trigger kind, got: ${wm_trigger.kind}`);
}
/**
* Verify signature against request body and timestamp
*
* @param signature - The signature from request headers
* @param body - Raw request body as string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
// IMPORTANT: This is an example implementation and should be updated
// with your specific provider's signature verification logic
// Example HMAC-SHA256 signature verification:
const dataToVerify = timestamp
? `${body || ''}${timestamp}` // Include timestamp in verification if present
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
// This can happen if the signatures are different lengths
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Each provider may have different signature formats and verification methods.
// For example:
// - Some use Base64 encoding instead of hex
// - Some require specific header ordering in the signature calculation
// - Some use different algorithms (RSA, Ed25519, etc.)
// Update this function according to your provider's documentation
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// Implement the main function logic here
// At this point, the signature has been verified and timestamp checked in the preprocessor
// Example response
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client"
const SECRET_KEY_VARIABLE_PATH = "secret_key_path"
/**
* Trigger preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* This function processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, Postgres, or email)
* before passing it to \`main\`. This helps separate trigger logic from the main logic and keeps the auto-generated UI clean.
*
* In this implementation:
*
* This script validates incoming HTTP requests by performing signature verification and timestamp validation to prevent replay attacks.
* It retrieves the actual HTTP request headers using \`wm_trigger.http.headers\`, which contain critical information such as the signature and timestamp.
* The \`raw_string\` (representing the raw JSON body) is also available, which is essential for tasks like signature validation.
* The signature is validated using HMAC-SHA256, and if a timestamp is provided, it ensures the request falls within an acceptable time window.
* If all checks pass, the body is parsed, and both the HTTP details and the parsed body are passed to the \`main\` function for further processing.
*
* The returned object defines the parameters that will be passed to \`main()\`.
* For example, \`{ b: 1, a: 2 }\` → Calls \`main(2, 1)\`, assuming \`main\` is defined as \`main(a: number, b: number)\`.
* Ensure the parameter names in \`main\` match the keys in the returned object.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
// Check if timestamp is within acceptable window (e.g., 5 minutes)
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window');
}
}
// Verify the signature
if (!(await verifySignature(signature, raw_string, timestamp))) {
throw new Error('Invalid signature');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
// Properly type the error
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected http trigger kind, got: ${wm_trigger.kind}`);
}
/**
* Verify signature against request body and timestamp
*
* @param signature - The signature from request headers
* @param body - Raw request body as string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
// IMPORTANT: This is an example implementation and should be updated
// with your specific provider's signature verification logic
// Example HMAC-SHA256 signature verification:
const dataToVerify = timestamp
? `${body || ''}${timestamp}` // Include timestamp in verification if present
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
// This can happen if the signatures are different lengths
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Each provider may have different signature formats and verification methods.
// For example:
// - Some use Base64 encoding instead of hex
// - Some require specific header ordering in the signature calculation
// - Some use different algorithms (RSA, Ed25519, etc.)
// Update this function according to your provider's documentation
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// Implement the main function logic here
// At this point, the signature has been verified and timestamp checked in the preprocessor
// Example response
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client"
const SECRET_KEY_VARIABLE_PATH = "secret_key_path"
/**
* Trigger preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* This function processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, Postgres, or email)
* before passing it to \`main\`. This separates the trigger logic from the main logic and keeps the auto-generated runnable UI clean.
*
* The preprocessor receives the same data \`main\` would if no preprocessor was used,
* plus trigger metadata in the \`wm_trigger\` object:
* - Webhook/HTTP: \`{ wm_trigger, bodyKey1, bodyKey2, ... }\`
* - Postgres: \`{ transaction_type, schema_name, table_name, row, wm_trigger }\`
* - WebSocket/Kafka/NATS/SQS/MQTT: \`{ msg, wm_trigger }\`
* - Email: \`{ raw_email, parsed_email, wm_trigger }\`
*
* The returned object defines the parameter values passed to \`main()\`.
* e.g., { b: 1, a: 2 } → Calls \`main(2, 1)\`, assuming \`main\` is defined as \`main(a: number, b: number)\`.
* Ensure that the parameter names in \`main\` match the keys in the returned object.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*
* In this implementation:
*
* This script validates incoming HTTP requests by performing signature verification and timestamp validation to prevent replay attacks.
* It retrieves the headers of the actual HTTP request using \`wm_trigger.http.headers\`, which contain critical information such as the signature and timestamp.
* The \`raw_string\` (representing the raw JSON body) is also available, which is essential for verification processes like signature validation.
* The signature is validated using HMAC-SHA256, and if a timestamp is provided, it ensures that the request falls within an acceptable time window.
* If all checks pass, the body is parsed, and both the HTTP details and the parsed body are passed to the \`main\` function for further processing.
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
// Check if timestamp is within acceptable window (e.g., 5 minutes)
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window');
}
}
// Verify the signature
if (!(await verifySignature(signature, raw_string, timestamp))) {
throw new Error('Invalid signature');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
// Properly type the error
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected http trigger kind, got: ${wm_trigger.kind}`);
}
/**
* Verify signature against request body and timestamp
*
* @param signature - The signature from request headers
* @param body - Raw request body as string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
// IMPORTANT: This is an example implementation and should be updated
// with your specific provider's signature verification logic
// Example HMAC-SHA256 signature verification:
const dataToVerify = timestamp
? `${body || ''}${timestamp}` // Include timestamp in verification if present
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
// This can happen if the signatures are different lengths
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Each provider may have different signature formats and verification methods.
// For example:
// - Some use Base64 encoding instead of hex
// - Some require specific header ordering in the signature calculation
// - Some use different algorithms (RSA, Ed25519, etc.)
// Update this function according to your provider's documentation
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// Implement the main function logic here
// At this point, the signature has been verified and timestamp checked in the preprocessor
// Example response
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client"
const SECRET_KEY_VARIABLE_PATH = "secret_key_path"
/**
* General Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* It processes raw trigger data (e.g., MQTT, HTTP, SQS, WebSocket, Kafka, NATS) before passing it to `main()`.
*
* The returned object determines `main()` parameters:
* - `{a: 1, b: 2}` → `main(a, b)`
* - `{http}` → `main(http)`
*
* @param wm_trigger - Trigger details (e.g., MQTT, HTTP, SQS, WebSocket, Kafka, NATS)
* @param raw_string - Raw request body as string
* @returns Processed data for `main()`
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
// Check if timestamp is within acceptable window (e.g., 5 minutes)
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window');
}
}
// Verify the signature
if (!(await verifySignature(signature, raw_string, timestamp))) {
throw new Error('Invalid signature');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
// Properly type the error
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected http trigger kind, got: ${wm_trigger.kind}`);
}
/**
* Verify signature against request body and timestamp
*
* @param signature - The signature from request headers
* @param body - Raw request body as string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
// IMPORTANT: This is an example implementation and should be updated
// with your specific provider's signature verification logic
// Example HMAC-SHA256 signature verification:
const dataToVerify = timestamp
? `${body || ''}${timestamp}` // Include timestamp in verification if present
: (body || '');
const secretKey = await wmill.getVariable(SECRET_KEY_VARIABLE_PATH);
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
// This can happen if the signatures are different lengths
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Each provider may have different signature formats and verification methods.
// For example:
// - Some use Base64 encoding instead of hex
// - Some require specific header ordering in the signature calculation
// - Some use different algorithms (RSA, Ed25519, etc.)
// Update this function according to your provider's documentation
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// Implement the main function logic here
// At this point, the signature has been verified and timestamp checked in the preprocessor
// Example response
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago
import * as crypto from 'crypto';
import * as wmill from "windmill-client"
/**
* General Trigger Preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* It processes raw trigger data (e.g., MQTT, HTTP, SQS, WebSocket, Kafka, NATS) before passing it to `main()`.
* Common tasks:
* - Convert binary payloads to string/JSON
* - Extract metadata
* - Filter messages
* - Add timestamps/context
* - Verify signatures and authenticate requests
*
* The returned object determines `main()` parameters:
* - `{a: 1, b: 2}` → `main(a, b)`
* - `{http}` → `main(http)`
*
* @param wm_trigger - Trigger details (e.g., MQTT, HTTP, SQS, WebSocket, Kafka, NATS)
* @param raw_string - Raw request body as string
* @returns Processed data for `main()`
*/
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
http?: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
};
},
raw_string: string
) {
if (wm_trigger.kind === 'http' && wm_trigger.http) {
// Extract signature from headers
const signature = wm_trigger.http.headers['x-signature'] || wm_trigger.http.headers['signature'];
if (!signature) {
throw new Error('Missing signature in request headers');
}
// Check timestamp if present to prevent replay attacks
const timestamp = wm_trigger.http.headers['x-timestamp'] || wm_trigger.http.headers['timestamp'];
if (timestamp) {
const timestampValue = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
// Check if timestamp is within acceptable window (e.g., 5 minutes)
const TIME_WINDOW_SECONDS = 5 * 60; // 5 minutes
if (isNaN(timestampValue)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(currentTime - timestampValue) > TIME_WINDOW_SECONDS) {
throw new Error('Request timestamp is outside the acceptable time window');
}
}
// Verify the signature
if (!(await verifySignature(signature, raw_string, timestamp))) {
throw new Error('Invalid signature');
}
// Parse the body if it's JSON (with error handling)
let parsedBody: any = {};
try {
parsedBody = JSON.parse(raw_string);
} catch (error: unknown) {
// Properly type the error
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
throw new Error(`Failed to parse request body: ${errorMessage}`);
}
// Return both HTTP details and the parsed body to main
return {
http: wm_trigger.http,
body: parsedBody
};
}
throw new Error(`Expected http trigger kind, got: ${wm_trigger.kind}`);
}
/**
* Verify signature against request body and timestamp
*
* @param signature - The signature from request headers
* @param body - Raw request body as string
* @param timestamp - Optional timestamp from request headers
* @returns boolean indicating if signature is valid
*/
async function verifySignature(signature: string, body?: string, timestamp?: string): Promise<boolean> {
// IMPORTANT: This is an example implementation and should be updated
// with your specific provider's signature verification logic
// Example HMAC-SHA256 signature verification:
const dataToVerify = timestamp
? `${body || ''}${timestamp}` // Include timestamp in verification if present
: (body || '');
const secretKey = await wmill.getVariable('u/production/stripe_secret_key');
const expectedSignature = crypto
.createHmac('sha256', secretKey || '')
.update(dataToVerify)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (error: unknown) {
// This can happen if the signatures are different lengths
console.error('Signature comparison error:', error);
return false;
}
// NOTE: Each provider may have different signature formats and verification methods.
// For example:
// - Some use Base64 encoding instead of hex
// - Some require specific header ordering in the signature calculation
// - Some use different algorithms (RSA, Ed25519, etc.)
// Update this function according to your provider's documentation
}
/**
* Main Function - Handles processed trigger events
*
* ⚠️ Called AFTER `preprocessor()`, with its return values.
*
* @param http - HTTP request details
* @param body - Parsed request body
*/
export async function main(
http: {
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
},
body: any
) {
// Implement the main function logic here
// At this point, the signature has been verified and timestamp checked in the preprocessor
// Example response
return {
statusCode: 200,
body: {
message: "Request authenticated successfully",
receivedData: body,
timestamp: http.headers['x-timestamp'] || http.headers['timestamp']
}
};
}Submitted by dieriba.pro916 417 days ago