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= // 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 }); } };