Files
dockhand/routes/api/git/stacks/[id]/webhook/+server.ts
Jarek Krochmalski 62e3c6439e Initial commit
2025-12-28 21:16:03 +01:00

98 lines
3.0 KiB
TypeScript

import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getGitStack } from '$lib/server/db';
import { deployGitStack } from '$lib/server/git';
import crypto from 'node:crypto';
function verifySignature(payload: string, signature: string | null, secret: string): boolean {
if (!signature) return false;
// Support both GitHub and GitLab webhook signatures
// GitHub: sha256=<hash>
// GitLab: just the token value in X-Gitlab-Token header
if (signature.startsWith('sha256=')) {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// GitLab uses X-Gitlab-Token which should match exactly
return signature === secret;
}
export const POST: RequestHandler = async ({ params, request }) => {
try {
const id = parseInt(params.id);
if (isNaN(id)) {
return json({ error: 'Invalid stack ID' }, { status: 400 });
}
const gitStack = await getGitStack(id);
if (!gitStack) {
return json({ error: 'Git stack not found' }, { status: 404 });
}
if (!gitStack.webhookEnabled) {
return json({ error: 'Webhook is not enabled for this stack' }, { status: 403 });
}
// Verify webhook secret if set
if (gitStack.webhookSecret) {
const payload = await request.text();
const githubSignature = request.headers.get('x-hub-signature-256');
const gitlabToken = request.headers.get('x-gitlab-token');
const signature = githubSignature || gitlabToken;
if (!verifySignature(payload, signature, gitStack.webhookSecret)) {
return json({ error: 'Invalid webhook signature' }, { status: 401 });
}
}
// Deploy the git stack (syncs and deploys only if there are changes)
const result = await deployGitStack(id, { force: false });
return json(result);
} catch (error: any) {
console.error('Webhook error:', error);
return json({ success: false, error: error.message }, { status: 500 });
}
};
// Also support GET for simple polling/manual triggers
export const GET: RequestHandler = async ({ params, url }) => {
try {
const id = parseInt(params.id);
if (isNaN(id)) {
return json({ error: 'Invalid stack ID' }, { status: 400 });
}
const gitStack = await getGitStack(id);
if (!gitStack) {
return json({ error: 'Git stack not found' }, { status: 404 });
}
if (!gitStack.webhookEnabled) {
return json({ error: 'Webhook is not enabled for this stack' }, { status: 403 });
}
// Verify secret via query parameter for GET requests
const secret = url.searchParams.get('secret');
if (gitStack.webhookSecret && secret !== gitStack.webhookSecret) {
return json({ error: 'Invalid webhook secret' }, { status: 401 });
}
// Deploy the git stack (syncs and deploys only if there are changes)
const result = await deployGitStack(id, { force: false });
return json(result);
} catch (error: any) {
console.error('Webhook GET error:', error);
return json({ success: false, error: error.message }, { status: 500 });
}
};