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 +1,4 @@
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
export { receiveMessage as POST } from "@link-stack/bridge-ui";

View file

@ -1 +1,4 @@
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
export { relinkBot as POST } from "@link-stack/bridge-ui";

View file

@ -1 +1,4 @@
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
export { getBot as GET } from "@link-stack/bridge-ui";

View file

@ -1 +1,4 @@
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
export { sendMessage as POST } from "@link-stack/bridge-ui";

View file

@ -1,3 +1,6 @@
import { handleWebhook } from "@link-stack/bridge-ui";
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
export { handleWebhook as GET, handleWebhook as POST };

View file

@ -1,6 +1,9 @@
import NextAuth from "next-auth";
import { authOptions } from "@/app/_lib/authentication";
// Force this route to be dynamic (not statically generated at build time)
export const dynamic = 'force-dynamic';
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

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

View file

@ -1,12 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { Redis } from "ioredis";
import { getToken } from "next-auth/jwt";
export async function POST(request: NextRequest) {
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
});
const allCookies = request.cookies.getAll();
const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080";
const signOutURL = `${zammadURL}/api/v1/signout`;
@ -18,7 +12,21 @@ export async function POST(request: NextRequest) {
.join("; "),
};
await fetch(signOutURL, { headers });
// Add timeout to prevent hanging requests
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout
try {
await fetch(signOutURL, {
headers,
signal: controller.signal
});
} catch (error) {
// Log but don't fail logout if Zammad signout fails
console.error('Zammad signout failed:', error);
} finally {
clearTimeout(timeout);
}
const cookiePrefixesToRemove = ["_zammad"];
const response = NextResponse.json({ message: "ok" });
@ -31,8 +39,5 @@ export async function POST(request: NextRequest) {
}
}
const redis = new Redis(process.env.REDIS_URL);
await redis.setex(`invalidated:${token.sub}`, 24 * 60 * 60, "1");
return response;
}