mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-04 05:29:06 +00:00
98 lines
3.0 KiB
TypeScript
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 });
|
|
}
|
|
};
|