177
src/routes/webhooks/github/events.ts
Normal file
177
src/routes/webhooks/github/events.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { getDomain, getTeam, getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import cuid from 'cuid';
|
||||
import crypto from 'crypto';
|
||||
import { buildQueue } from '$lib/queues';
|
||||
import { checkContainer, removeProxyConfiguration } from '$lib/haproxy';
|
||||
|
||||
export const options: RequestHandler = async () => {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
try {
|
||||
const buildId = cuid();
|
||||
const allowedGithubEvents = ['push', 'pull_request'];
|
||||
const allowedActions = ['opened', 'reopened', 'synchronize', 'closed'];
|
||||
const githubEvent = event.request.headers.get('x-github-event').toLowerCase();
|
||||
const githubSignature = event.request.headers.get('x-hub-signature-256').toLowerCase();
|
||||
if (!allowedGithubEvents.includes(githubEvent)) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Event not allowed.'
|
||||
}
|
||||
};
|
||||
}
|
||||
let repository, projectId, branch;
|
||||
const body = await event.request.json();
|
||||
|
||||
if (githubEvent === 'push') {
|
||||
repository = body.repository;
|
||||
projectId = repository.id;
|
||||
branch = body.ref.split('/')[2];
|
||||
} else if (githubEvent === 'pull_request') {
|
||||
repository = body.pull_request.head.repo;
|
||||
projectId = repository.id;
|
||||
branch = body.pull_request.head.ref.split('/')[2];
|
||||
}
|
||||
|
||||
const applicationFound = await db.getApplicationWebhook({ projectId, branch });
|
||||
if (applicationFound) {
|
||||
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret;
|
||||
const hmac = crypto.createHmac('sha256', webhookSecret);
|
||||
const digest = Buffer.from(
|
||||
'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'),
|
||||
'utf8'
|
||||
);
|
||||
const checksum = Buffer.from(githubSignature, 'utf8');
|
||||
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'SHA256 checksum failed. Are you doing something fishy?'
|
||||
}
|
||||
};
|
||||
}
|
||||
if (githubEvent === 'push') {
|
||||
if (!applicationFound.configHash) {
|
||||
const configHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: applicationFound.buildPack,
|
||||
port: applicationFound.port,
|
||||
installCommand: applicationFound.installCommand,
|
||||
buildCommand: applicationFound.buildCommand,
|
||||
startCommand: applicationFound.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await db.prisma.application.updateMany({
|
||||
where: { branch, projectId },
|
||||
data: { configHash }
|
||||
});
|
||||
}
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'webhook_commit',
|
||||
...applicationFound
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Queued. Thank you!'
|
||||
}
|
||||
};
|
||||
} else if (githubEvent === 'pull_request') {
|
||||
const pullmergeRequestId = body.number;
|
||||
const pullmergeRequestAction = body.action;
|
||||
const sourceBranch = body.pull_request.head.ref;
|
||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Action not allowed.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (applicationFound.settings.previews) {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const isRunning = await checkContainer(
|
||||
applicationFound.destinationDocker.engine,
|
||||
applicationFound.id
|
||||
);
|
||||
if (!isRunning) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Application not running.'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if (pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') {
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'webhook_pr',
|
||||
...applicationFound,
|
||||
sourceBranch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Queued. Thank you!'
|
||||
}
|
||||
};
|
||||
} else if (pullmergeRequestAction === 'closed') {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const domain = getDomain(applicationFound.fqdn);
|
||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
||||
const engine = applicationFound.destinationDocker.engine;
|
||||
await removeDestinationDocker({ id, engine });
|
||||
await removeProxyConfiguration({ domain: `${pullmergeRequestId}.${domain}` });
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Removed preview. Thank you!'
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Pull request previews are not enabled.'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Not handled event.'
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: err.message
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
41
src/routes/webhooks/github/index.ts
Normal file
41
src/routes/webhooks/github/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { dev } from '$app/env';
|
||||
import { getTeam } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const options = async () => {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const get: RequestHandler = async (request) => {
|
||||
const teamId = undefined;
|
||||
const code = request.url.searchParams.get('code');
|
||||
const state = request.url.searchParams.get('state');
|
||||
try {
|
||||
const { apiUrl } = await db.getSource({ id: state, teamId });
|
||||
const response = await fetch(`${apiUrl}/app-manifests/${code}/conversions`, { method: 'POST' });
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
return {
|
||||
status: 500,
|
||||
body: { ...error }
|
||||
};
|
||||
}
|
||||
const { id, client_id, slug, client_secret, pem, webhook_secret } = await response.json();
|
||||
await db.createGithubApp({ id, client_id, slug, client_secret, pem, webhook_secret, state });
|
||||
return {
|
||||
status: 302,
|
||||
headers: { Location: `/webhooks/success` }
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
}
|
||||
};
|
29
src/routes/webhooks/github/install.ts
Normal file
29
src/routes/webhooks/github/install.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const options = async () => {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const get: RequestHandler = async (request) => {
|
||||
const gitSourceId = request.url.searchParams.get('gitSourceId');
|
||||
const installation_id = request.url.searchParams.get('installation_id');
|
||||
|
||||
try {
|
||||
await db.addInstallation({ gitSourceId, installation_id });
|
||||
return {
|
||||
status: 302,
|
||||
headers: { Location: `/webhooks/success` }
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
}
|
||||
};
|
182
src/routes/webhooks/gitlab/events.ts
Normal file
182
src/routes/webhooks/gitlab/events.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { getTeam, getUserDetails, getDomain, removeDestinationDocker } from '$lib/common';
|
||||
import { checkContainer, removeProxyConfiguration } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import cuid from 'cuid';
|
||||
import crypto from 'crypto';
|
||||
import { buildQueue } from '$lib/queues';
|
||||
import { dev } from '$app/env';
|
||||
|
||||
export const options: RequestHandler = async () => {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
||||
const body = await event.request.json();
|
||||
const buildId = cuid();
|
||||
try {
|
||||
const { object_kind: objectKind } = body;
|
||||
if (objectKind === 'push') {
|
||||
const { ref } = body;
|
||||
const projectId = Number(body['project_id']);
|
||||
const branch = ref.split('/')[2];
|
||||
const applicationFound = await db.getApplicationWebhook({ projectId, branch });
|
||||
if (applicationFound) {
|
||||
if (!applicationFound.configHash) {
|
||||
const configHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: applicationFound.buildPack,
|
||||
port: applicationFound.port,
|
||||
installCommand: applicationFound.installCommand,
|
||||
buildCommand: applicationFound.buildCommand,
|
||||
startCommand: applicationFound.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await db.prisma.application.updateMany({
|
||||
where: { branch, projectId },
|
||||
data: { configHash }
|
||||
});
|
||||
}
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'webhook_commit',
|
||||
...applicationFound
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Queued. Thank you!'
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (objectKind === 'merge_request') {
|
||||
const webhookToken = event.request.headers.get('x-gitlab-token');
|
||||
if (!webhookToken) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Ooops, something is not okay, are you okay?'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const isDraft = body.object_attributes.work_in_progress;
|
||||
const action = body.object_attributes.action;
|
||||
const projectId = Number(body.project.id);
|
||||
const sourceBranch = body.object_attributes.source_branch;
|
||||
const targetBranch = body.object_attributes.target_branch;
|
||||
const pullmergeRequestId = body.object_attributes.iid;
|
||||
if (!allowedActions.includes(action)) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Action not allowed.'
|
||||
}
|
||||
};
|
||||
}
|
||||
if (isDraft) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Draft MR, do nothing.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const applicationFound = await db.getApplicationWebhook({ projectId, branch: targetBranch });
|
||||
if (applicationFound) {
|
||||
if (applicationFound.settings.previews) {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const isRunning = await checkContainer(
|
||||
applicationFound.destinationDocker.engine,
|
||||
applicationFound.id
|
||||
);
|
||||
if (!isRunning) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Application not running.'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!dev && applicationFound.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Ooops, something is not okay, are you okay?'
|
||||
}
|
||||
};
|
||||
}
|
||||
if (
|
||||
action === 'opened' ||
|
||||
action === 'reopen' ||
|
||||
action === 'open' ||
|
||||
action === 'update'
|
||||
) {
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'webhook_mr',
|
||||
...applicationFound,
|
||||
sourceBranch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Queued. Thank you!'
|
||||
}
|
||||
};
|
||||
} else if (action === 'close') {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const domain = getDomain(applicationFound.fqdn);
|
||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
||||
const engine = applicationFound.destinationDocker.engine;
|
||||
await removeProxyConfiguration({ domain: `${pullmergeRequestId}.${domain}` });
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Removed preview. Thank you!'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Merge request previews are not enabled.'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Not handled event.'
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: err.message
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
59
src/routes/webhooks/gitlab/index.ts
Normal file
59
src/routes/webhooks/gitlab/index.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { dev } from '$app/env';
|
||||
import { getTeam } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import got from 'got';
|
||||
import cookie from 'cookie';
|
||||
|
||||
export const options: RequestHandler = async () => {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const teamId = undefined;
|
||||
const code = event.url.searchParams.get('code');
|
||||
const state = event.url.searchParams.get('state');
|
||||
try {
|
||||
const application = await db.getApplication({ id: state, teamId });
|
||||
const { fqdn } = application;
|
||||
const { appId, appSecret } = application.gitSource.gitlabApp;
|
||||
const { htmlUrl } = application.gitSource;
|
||||
|
||||
let domain = `http://${event.url.host}`;
|
||||
if (fqdn) domain = fqdn;
|
||||
|
||||
const { access_token } = await got
|
||||
.post(`${htmlUrl}/oauth/token`, {
|
||||
searchParams: {
|
||||
client_id: appId,
|
||||
client_secret: appSecret,
|
||||
code,
|
||||
state,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${domain}/webhooks/gitlab`
|
||||
}
|
||||
})
|
||||
.json();
|
||||
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: `/webhooks/success`,
|
||||
'Set-Cookie': [`gitlabToken=${access_token}; HttpOnly; Path=/; Max-Age=15778800;`]
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return {
|
||||
status: 500,
|
||||
body: err.message
|
||||
};
|
||||
}
|
||||
};
|
3
src/routes/webhooks/success/index.svelte
Normal file
3
src/routes/webhooks/success/index.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<script lang="ts">
|
||||
window.close();
|
||||
</script>
|
Reference in New Issue
Block a user