Files
alighasami 3d5eaf9445
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
first commit
2026-03-17 16:22:57 +03:30

75 lines
2.0 KiB
TypeScript

import { createHmac, timingSafeEqual } from 'crypto';
import type { IWebhookFunctions } from 'n8n-workflow';
export async function verifySignature(this: IWebhookFunctions): Promise<boolean> {
const credential = await this.getCredentials('stripeApi');
if (!credential?.signatureSecret) {
return true; // No signature secret provided, skip verification
}
const req = this.getRequestObject();
const signature = req.header('stripe-signature');
if (!signature) {
return false;
}
// Parse the Stripe signature header
const elements = signature.split(',');
let timestamp: string | undefined;
let signatureValue: string | undefined;
for (const element of elements) {
if (element.startsWith('t=')) {
timestamp = element.substring(2);
} else if (element.startsWith('v1=')) {
signatureValue = element.substring(3);
}
}
if (!timestamp || !signatureValue) {
return false;
}
// Verify timestamp
const currentTimestamp = Math.floor(Date.now() / 1000);
const webhookTimestamp = parseInt(timestamp, 10);
const TIMESTAMP_TOLERANCE_SECONDS = 300; // 5 minutes
if (Math.abs(currentTimestamp - webhookTimestamp) > TIMESTAMP_TOLERANCE_SECONDS) {
return false;
}
try {
if (typeof credential.signatureSecret !== 'string') {
return false;
}
if (!req.rawBody) {
return false;
}
let rawBodyString: string;
if (Buffer.isBuffer(req.rawBody)) {
rawBodyString = req.rawBody.toString();
} else {
rawBodyString = typeof req.rawBody === 'string' ? req.rawBody : JSON.stringify(req.rawBody);
}
const signedPayload = `${timestamp}.${rawBodyString}`;
const hmac = createHmac('sha256', credential.signatureSecret);
hmac.update(signedPayload);
const computedSignature = hmac.digest('hex');
const computedBuffer = Buffer.from(computedSignature);
const providedBuffer = Buffer.from(signatureValue);
return (
computedBuffer.length === providedBuffer.length &&
timingSafeEqual(computedBuffer, providedBuffer)
);
} catch (error) {
return false;
}
}