Repo cleanup and updates

This commit is contained in:
Darren Clarke 2025-11-10 14:55:22 +01:00 committed by GitHub
parent 3a1063e40e
commit 99f8d7e2eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 11857 additions and 16439 deletions

View file

@ -1,11 +1,17 @@
import { NextRequest, NextResponse } from "next/server";
import { createLogger } from "@link-stack/logger";
import { getWorkerUtils } from "@link-stack/bridge-common";
import { timingSafeEqual } from "crypto";
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
const logger = createLogger('formstack-webhook');
export async function POST(req: NextRequest): Promise<NextResponse> {
try {
const clientIp = req.headers.get('x-forwarded-for') || req.headers.get('x-real-ip') || 'unknown';
// Get the shared secret from environment variable
const expectedSecret = process.env.FORMSTACK_SHARED_SECRET;
@ -21,19 +27,47 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
const body = await req.json();
const receivedSecret = body.HandshakeKey;
// Verify the shared secret
if (receivedSecret !== expectedSecret) {
logger.warn({ receivedSecret }, 'Invalid shared secret received');
// Validate that secret is provided
if (!receivedSecret || typeof receivedSecret !== 'string') {
logger.warn({ clientIp }, 'Missing or invalid HandshakeKey');
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// Log the entire webhook payload to see the data structure
// Use timing-safe comparison to prevent timing attacks
const expectedBuffer = Buffer.from(expectedSecret);
const receivedBuffer = Buffer.from(receivedSecret);
let secretsMatch = false;
if (expectedBuffer.length === receivedBuffer.length) {
try {
secretsMatch = timingSafeEqual(expectedBuffer, receivedBuffer);
} catch (e) {
secretsMatch = false;
}
}
if (!secretsMatch) {
logger.warn({
secretMatch: false,
timestamp: new Date().toISOString(),
userAgent: req.headers.get('user-agent'),
clientIp
}, 'Invalid shared secret received');
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// Log webhook receipt with non-PII metadata only
logger.info({
payload: body,
headers: Object.fromEntries(req.headers.entries()),
formId: body.FormID,
uniqueId: body.UniqueID,
timestamp: new Date().toISOString(),
fieldCount: Object.keys(body).length
}, 'Received Formstack webhook');
// Enqueue a bridge-worker task to process this form submission