@shipfox/api-integration-github 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +138 -0
- package/LICENSE +21 -0
- package/dist/api/client.d.ts +90 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +306 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/installation-token-envelope.d.ts +36 -0
- package/dist/api/installation-token-envelope.d.ts.map +1 -0
- package/dist/api/installation-token-envelope.js +123 -0
- package/dist/api/installation-token-envelope.js.map +1 -0
- package/dist/api/installation-token-provider.d.ts +23 -0
- package/dist/api/installation-token-provider.d.ts.map +1 -0
- package/dist/api/installation-token-provider.js +130 -0
- package/dist/api/installation-token-provider.js.map +1 -0
- package/dist/api/shared-installation-token-cache.d.ts +43 -0
- package/dist/api/shared-installation-token-cache.d.ts.map +1 -0
- package/dist/api/shared-installation-token-cache.js +224 -0
- package/dist/api/shared-installation-token-cache.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/core/actions-logs.d.ts +47 -0
- package/dist/core/actions-logs.d.ts.map +1 -0
- package/dist/core/actions-logs.js +134 -0
- package/dist/core/actions-logs.js.map +1 -0
- package/dist/core/agent-tool-selection.d.ts +4 -0
- package/dist/core/agent-tool-selection.d.ts.map +1 -0
- package/dist/core/agent-tool-selection.js +38 -0
- package/dist/core/agent-tool-selection.js.map +1 -0
- package/dist/core/agent-tools.d.ts +50 -0
- package/dist/core/agent-tools.d.ts.map +1 -0
- package/dist/core/agent-tools.js +1122 -0
- package/dist/core/agent-tools.js.map +1 -0
- package/dist/core/errors.d.ts +15 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +22 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/install.d.ts +38 -0
- package/dist/core/install.d.ts.map +1 -0
- package/dist/core/install.js +64 -0
- package/dist/core/install.js.map +1 -0
- package/dist/core/source-control.d.ts +15 -0
- package/dist/core/source-control.d.ts.map +1 -0
- package/dist/core/source-control.js +175 -0
- package/dist/core/source-control.js.map +1 -0
- package/dist/core/state.d.ts +12 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +54 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/webhook.d.ts +20 -0
- package/dist/core/webhook.d.ts.map +1 -0
- package/dist/core/webhook.js +216 -0
- package/dist/core/webhook.js.map +1 -0
- package/dist/db/db.d.ts +433 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +18 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/installation-token-lock.d.ts +8 -0
- package/dist/db/installation-token-lock.d.ts.map +1 -0
- package/dist/db/installation-token-lock.js +37 -0
- package/dist/db/installation-token-lock.js.map +1 -0
- package/dist/db/installations.d.ts +33 -0
- package/dist/db/installations.d.ts.map +1 -0
- package/dist/db/installations.js +56 -0
- package/dist/db/installations.js.map +1 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +5 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/installations.d.ts +218 -0
- package/dist/db/schema/installations.d.ts.map +1 -0
- package/dist/db/schema/installations.js +46 -0
- package/dist/db/schema/installations.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +14 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +75 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/integrations.d.ts +15 -0
- package/dist/presentation/dto/integrations.d.ts.map +1 -0
- package/dist/presentation/dto/integrations.js +10 -0
- package/dist/presentation/dto/integrations.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-connection.d.ts +11 -0
- package/dist/presentation/e2eRoutes/create-connection.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-connection.js +57 -0
- package/dist/presentation/e2eRoutes/create-connection.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +5 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +11 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/routes/errors.d.ts +2 -0
- package/dist/presentation/routes/errors.d.ts.map +1 -0
- package/dist/presentation/routes/errors.js +46 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/install.d.ts +13 -0
- package/dist/presentation/routes/install.d.ts.map +1 -0
- package/dist/presentation/routes/install.js +77 -0
- package/dist/presentation/routes/install.js.map +1 -0
- package/dist/presentation/routes/webhooks.d.ts +16 -0
- package/dist/presentation/routes/webhooks.d.ts.map +1 -0
- package/dist/presentation/routes/webhooks.js +110 -0
- package/dist/presentation/routes/webhooks.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +17 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +71 -0
- package/src/api/client.test.ts +94 -0
- package/src/api/client.ts +525 -0
- package/src/api/installation-token-envelope.ts +171 -0
- package/src/api/installation-token-provider.test.ts +241 -0
- package/src/api/installation-token-provider.ts +224 -0
- package/src/api/shared-installation-token-cache.test.ts +294 -0
- package/src/api/shared-installation-token-cache.ts +346 -0
- package/src/config.test.ts +14 -0
- package/src/config.ts +47 -0
- package/src/connection-external-url.test.ts +78 -0
- package/src/core/actions-logs.test.ts +156 -0
- package/src/core/actions-logs.ts +186 -0
- package/src/core/agent-tool-selection.test.ts +41 -0
- package/src/core/agent-tool-selection.ts +42 -0
- package/src/core/agent-tools.test.ts +539 -0
- package/src/core/agent-tools.ts +1269 -0
- package/src/core/errors.ts +23 -0
- package/src/core/install.test.ts +238 -0
- package/src/core/install.ts +119 -0
- package/src/core/source-control.test.ts +327 -0
- package/src/core/source-control.ts +223 -0
- package/src/core/state.test.ts +43 -0
- package/src/core/state.ts +85 -0
- package/src/core/webhook.test.ts +522 -0
- package/src/core/webhook.ts +296 -0
- package/src/db/db.ts +18 -0
- package/src/db/installation-token-lock.test.ts +68 -0
- package/src/db/installation-token-lock.ts +39 -0
- package/src/db/installations.test.ts +67 -0
- package/src/db/installations.ts +108 -0
- package/src/db/migrations.ts +4 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/installations.ts +46 -0
- package/src/index.ts +124 -0
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.ts +82 -0
- package/src/presentation/dto/integrations.ts +8 -0
- package/src/presentation/e2eRoutes/create-connection.ts +70 -0
- package/src/presentation/e2eRoutes/index.test.ts +126 -0
- package/src/presentation/e2eRoutes/index.ts +14 -0
- package/src/presentation/routes/errors.ts +43 -0
- package/src/presentation/routes/install.test.ts +279 -0
- package/src/presentation/routes/install.ts +93 -0
- package/src/presentation/routes/webhooks.test.ts +560 -0
- package/src/presentation/routes/webhooks.ts +115 -0
- package/test/env.ts +16 -0
- package/test/factories/github-installation.ts +35 -0
- package/test/factories/index.ts +1 -0
- package/test/fixtures/github-webhook.ts +20 -0
- package/test/globalSetup.ts +14 -0
- package/test/index.ts +2 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +9 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { toIntegrationConnectionDto } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import { createE2eGithubConnectionBodySchema, createE2eGithubConnectionResponseSchema } from '@shipfox/api-integration-github-dto';
|
|
3
|
+
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
4
|
+
export function createE2eGithubConnectionRoute(options) {
|
|
5
|
+
return defineRoute({
|
|
6
|
+
method: 'POST',
|
|
7
|
+
path: '/github-connections',
|
|
8
|
+
description: 'Create a synthetic GitHub connection for E2E tests.',
|
|
9
|
+
schema: {
|
|
10
|
+
body: createE2eGithubConnectionBodySchema,
|
|
11
|
+
response: {
|
|
12
|
+
201: createE2eGithubConnectionResponseSchema
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
handler: async (request, reply)=>{
|
|
16
|
+
const body = request.body;
|
|
17
|
+
const installationId = String(body.installation_id);
|
|
18
|
+
const existing = await options.getExistingGithubConnection({
|
|
19
|
+
installationId
|
|
20
|
+
});
|
|
21
|
+
if (existing && existing.workspaceId !== body.workspace_id) {
|
|
22
|
+
throw new ClientError('GitHub installation is already connected to another workspace', 'github-connection-workspace-mismatch', {
|
|
23
|
+
status: 409
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const connection = existing ?? await options.connectGithubInstallation({
|
|
27
|
+
workspaceId: body.workspace_id,
|
|
28
|
+
installationId,
|
|
29
|
+
displayName: body.display_name,
|
|
30
|
+
installerUserId: body.installer_user_id,
|
|
31
|
+
installation: {
|
|
32
|
+
installationId,
|
|
33
|
+
accountLogin: body.account_login,
|
|
34
|
+
accountType: 'Organization',
|
|
35
|
+
repositorySelection: 'all',
|
|
36
|
+
suspendedAt: null,
|
|
37
|
+
deletedAt: null,
|
|
38
|
+
latestEvent: {
|
|
39
|
+
id: body.installation_id,
|
|
40
|
+
account: {
|
|
41
|
+
login: body.account_login,
|
|
42
|
+
type: 'Organization'
|
|
43
|
+
},
|
|
44
|
+
repository_selection: 'all'
|
|
45
|
+
},
|
|
46
|
+
installerUserId: body.installer_user_id
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
reply.code(201);
|
|
50
|
+
return toIntegrationConnectionDto(connection, {
|
|
51
|
+
capabilities: options.connectionCapabilities
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=create-connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/e2eRoutes/create-connection.ts"],"sourcesContent":["import type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {toIntegrationConnectionDto} from '@shipfox/api-integration-core-dto';\nimport {\n createE2eGithubConnectionBodySchema,\n createE2eGithubConnectionResponseSchema,\n} from '@shipfox/api-integration-github-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport type {ConnectGithubInstallationInput} from '#core/install.js';\n\nexport interface CreateE2eGithubConnectionRouteOptions {\n getExistingGithubConnection: (input: {\n installationId: string;\n }) => Promise<IntegrationConnection<'github'> | undefined>;\n connectGithubInstallation: (\n input: ConnectGithubInstallationInput,\n ) => Promise<IntegrationConnection<'github'>>;\n connectionCapabilities: IntegrationCapability[];\n}\n\nexport function createE2eGithubConnectionRoute(options: CreateE2eGithubConnectionRouteOptions) {\n return defineRoute({\n method: 'POST',\n path: '/github-connections',\n description: 'Create a synthetic GitHub connection for E2E tests.',\n schema: {\n body: createE2eGithubConnectionBodySchema,\n response: {201: createE2eGithubConnectionResponseSchema},\n },\n handler: async (request, reply) => {\n const body = request.body;\n const installationId = String(body.installation_id);\n const existing = await options.getExistingGithubConnection({installationId});\n if (existing && existing.workspaceId !== body.workspace_id) {\n throw new ClientError(\n 'GitHub installation is already connected to another workspace',\n 'github-connection-workspace-mismatch',\n {status: 409},\n );\n }\n\n const connection =\n existing ??\n (await options.connectGithubInstallation({\n workspaceId: body.workspace_id,\n installationId,\n displayName: body.display_name,\n installerUserId: body.installer_user_id,\n installation: {\n installationId,\n accountLogin: body.account_login,\n accountType: 'Organization',\n repositorySelection: 'all',\n suspendedAt: null,\n deletedAt: null,\n latestEvent: {\n id: body.installation_id,\n account: {login: body.account_login, type: 'Organization'},\n repository_selection: 'all',\n },\n installerUserId: body.installer_user_id,\n },\n }));\n\n reply.code(201);\n return toIntegrationConnectionDto(connection, {\n capabilities: options.connectionCapabilities,\n });\n },\n });\n}\n"],"names":["toIntegrationConnectionDto","createE2eGithubConnectionBodySchema","createE2eGithubConnectionResponseSchema","ClientError","defineRoute","createE2eGithubConnectionRoute","options","method","path","description","schema","body","response","handler","request","reply","installationId","String","installation_id","existing","getExistingGithubConnection","workspaceId","workspace_id","status","connection","connectGithubInstallation","displayName","display_name","installerUserId","installer_user_id","installation","accountLogin","account_login","accountType","repositorySelection","suspendedAt","deletedAt","latestEvent","id","account","login","type","repository_selection","code","capabilities","connectionCapabilities"],"mappings":"AACA,SAAQA,0BAA0B,QAAO,oCAAoC;AAC7E,SACEC,mCAAmC,EACnCC,uCAAuC,QAClC,sCAAsC;AAC7C,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAa/D,OAAO,SAASC,+BAA+BC,OAA8C;IAC3F,OAAOF,YAAY;QACjBG,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,MAAMV;YACNW,UAAU;gBAAC,KAAKV;YAAuC;QACzD;QACAW,SAAS,OAAOC,SAASC;YACvB,MAAMJ,OAAOG,QAAQH,IAAI;YACzB,MAAMK,iBAAiBC,OAAON,KAAKO,eAAe;YAClD,MAAMC,WAAW,MAAMb,QAAQc,2BAA2B,CAAC;gBAACJ;YAAc;YAC1E,IAAIG,YAAYA,SAASE,WAAW,KAAKV,KAAKW,YAAY,EAAE;gBAC1D,MAAM,IAAInB,YACR,iEACA,wCACA;oBAACoB,QAAQ;gBAAG;YAEhB;YAEA,MAAMC,aACJL,YACC,MAAMb,QAAQmB,yBAAyB,CAAC;gBACvCJ,aAAaV,KAAKW,YAAY;gBAC9BN;gBACAU,aAAaf,KAAKgB,YAAY;gBAC9BC,iBAAiBjB,KAAKkB,iBAAiB;gBACvCC,cAAc;oBACZd;oBACAe,cAAcpB,KAAKqB,aAAa;oBAChCC,aAAa;oBACbC,qBAAqB;oBACrBC,aAAa;oBACbC,WAAW;oBACXC,aAAa;wBACXC,IAAI3B,KAAKO,eAAe;wBACxBqB,SAAS;4BAACC,OAAO7B,KAAKqB,aAAa;4BAAES,MAAM;wBAAc;wBACzDC,sBAAsB;oBACxB;oBACAd,iBAAiBjB,KAAKkB,iBAAiB;gBACzC;YACF;YAEFd,MAAM4B,IAAI,CAAC;YACX,OAAO3C,2BAA2BwB,YAAY;gBAC5CoB,cAActC,QAAQuC,sBAAsB;YAC9C;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RouteGroup } from '@shipfox/node-fastify';
|
|
2
|
+
import { type CreateE2eGithubConnectionRouteOptions } from './create-connection.js';
|
|
3
|
+
export type CreateGithubE2eRoutesOptions = CreateE2eGithubConnectionRouteOptions;
|
|
4
|
+
export declare function createGithubE2eRoutes(options: CreateGithubE2eRoutesOptions): RouteGroup;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACtD,OAAO,EACL,KAAK,qCAAqC,EAE3C,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,4BAA4B,GAAG,qCAAqC,CAAC;AAEjF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,UAAU,CAKvF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createE2eGithubConnectionRoute } from './create-connection.js';
|
|
2
|
+
export function createGithubE2eRoutes(options) {
|
|
3
|
+
return {
|
|
4
|
+
prefix: '/integrations',
|
|
5
|
+
routes: [
|
|
6
|
+
createE2eGithubConnectionRoute(options)
|
|
7
|
+
]
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/e2eRoutes/index.ts"],"sourcesContent":["import type {RouteGroup} from '@shipfox/node-fastify';\nimport {\n type CreateE2eGithubConnectionRouteOptions,\n createE2eGithubConnectionRoute,\n} from './create-connection.js';\n\nexport type CreateGithubE2eRoutesOptions = CreateE2eGithubConnectionRouteOptions;\n\nexport function createGithubE2eRoutes(options: CreateGithubE2eRoutesOptions): RouteGroup {\n return {\n prefix: '/integrations',\n routes: [createE2eGithubConnectionRoute(options)],\n };\n}\n"],"names":["createE2eGithubConnectionRoute","createGithubE2eRoutes","options","prefix","routes"],"mappings":"AACA,SAEEA,8BAA8B,QACzB,yBAAyB;AAIhC,OAAO,SAASC,sBAAsBC,OAAqC;IACzE,OAAO;QACLC,QAAQ;QACRC,QAAQ;YAACJ,+BAA+BE;SAAS;IACnD;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/errors.ts"],"names":[],"mappings":"AAmBA,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAuB7D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ConnectionSlugConflictError } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import { ClientError } from '@shipfox/node-fastify';
|
|
3
|
+
import { GithubInstallationAlreadyLinkedError, GithubInstallationNotAuthorizedError, GithubInstallStateActorMismatchError, GithubInstallStateError, GithubIntegrationProviderError } from '#core/errors.js';
|
|
4
|
+
function providerStatus(reason) {
|
|
5
|
+
if (reason === 'rate-limited') return 429;
|
|
6
|
+
if (reason === 'timeout' || reason === 'provider-unavailable') return 503;
|
|
7
|
+
return 422;
|
|
8
|
+
}
|
|
9
|
+
export function githubRouteErrorHandler(error) {
|
|
10
|
+
if (error instanceof GithubInstallStateError) {
|
|
11
|
+
throw new ClientError(error.message, 'invalid-github-install-state', {
|
|
12
|
+
status: 400
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (error instanceof GithubInstallStateActorMismatchError) {
|
|
16
|
+
throw new ClientError(error.message, 'github-install-state-actor-mismatch', {
|
|
17
|
+
status: 403
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (error instanceof GithubInstallationNotAuthorizedError) {
|
|
21
|
+
throw new ClientError(error.message, 'github-installation-not-authorized', {
|
|
22
|
+
status: 403
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (error instanceof GithubInstallationAlreadyLinkedError) {
|
|
26
|
+
throw new ClientError(error.message, 'github-installation-already-linked', {
|
|
27
|
+
status: 409
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (error instanceof ConnectionSlugConflictError) {
|
|
31
|
+
throw new ClientError(error.message, 'slug-conflict', {
|
|
32
|
+
status: 409
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (error instanceof GithubIntegrationProviderError) {
|
|
36
|
+
throw new ClientError(error.message, error.reason, {
|
|
37
|
+
details: {
|
|
38
|
+
retry_after_seconds: error.retryAfterSeconds
|
|
39
|
+
},
|
|
40
|
+
status: providerStatus(error.reason)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/errors.ts"],"sourcesContent":["import {\n ConnectionSlugConflictError,\n type IntegrationProviderErrorReason,\n} from '@shipfox/api-integration-core-dto';\nimport {ClientError} from '@shipfox/node-fastify';\nimport {\n GithubInstallationAlreadyLinkedError,\n GithubInstallationNotAuthorizedError,\n GithubInstallStateActorMismatchError,\n GithubInstallStateError,\n GithubIntegrationProviderError,\n} from '#core/errors.js';\n\nfunction providerStatus(reason: IntegrationProviderErrorReason): number {\n if (reason === 'rate-limited') return 429;\n if (reason === 'timeout' || reason === 'provider-unavailable') return 503;\n return 422;\n}\n\nexport function githubRouteErrorHandler(error: unknown): never {\n if (error instanceof GithubInstallStateError) {\n throw new ClientError(error.message, 'invalid-github-install-state', {status: 400});\n }\n if (error instanceof GithubInstallStateActorMismatchError) {\n throw new ClientError(error.message, 'github-install-state-actor-mismatch', {status: 403});\n }\n if (error instanceof GithubInstallationNotAuthorizedError) {\n throw new ClientError(error.message, 'github-installation-not-authorized', {status: 403});\n }\n if (error instanceof GithubInstallationAlreadyLinkedError) {\n throw new ClientError(error.message, 'github-installation-already-linked', {status: 409});\n }\n if (error instanceof ConnectionSlugConflictError) {\n throw new ClientError(error.message, 'slug-conflict', {status: 409});\n }\n if (error instanceof GithubIntegrationProviderError) {\n throw new ClientError(error.message, error.reason, {\n details: {retry_after_seconds: error.retryAfterSeconds},\n status: providerStatus(error.reason),\n });\n }\n throw error;\n}\n"],"names":["ConnectionSlugConflictError","ClientError","GithubInstallationAlreadyLinkedError","GithubInstallationNotAuthorizedError","GithubInstallStateActorMismatchError","GithubInstallStateError","GithubIntegrationProviderError","providerStatus","reason","githubRouteErrorHandler","error","message","status","details","retry_after_seconds","retryAfterSeconds"],"mappings":"AAAA,SACEA,2BAA2B,QAEtB,oCAAoC;AAC3C,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SACEC,oCAAoC,EACpCC,oCAAoC,EACpCC,oCAAoC,EACpCC,uBAAuB,EACvBC,8BAA8B,QACzB,kBAAkB;AAEzB,SAASC,eAAeC,MAAsC;IAC5D,IAAIA,WAAW,gBAAgB,OAAO;IACtC,IAAIA,WAAW,aAAaA,WAAW,wBAAwB,OAAO;IACtE,OAAO;AACT;AAEA,OAAO,SAASC,wBAAwBC,KAAc;IACpD,IAAIA,iBAAiBL,yBAAyB;QAC5C,MAAM,IAAIJ,YAAYS,MAAMC,OAAO,EAAE,gCAAgC;YAACC,QAAQ;QAAG;IACnF;IACA,IAAIF,iBAAiBN,sCAAsC;QACzD,MAAM,IAAIH,YAAYS,MAAMC,OAAO,EAAE,uCAAuC;YAACC,QAAQ;QAAG;IAC1F;IACA,IAAIF,iBAAiBP,sCAAsC;QACzD,MAAM,IAAIF,YAAYS,MAAMC,OAAO,EAAE,sCAAsC;YAACC,QAAQ;QAAG;IACzF;IACA,IAAIF,iBAAiBR,sCAAsC;QACzD,MAAM,IAAID,YAAYS,MAAMC,OAAO,EAAE,sCAAsC;YAACC,QAAQ;QAAG;IACzF;IACA,IAAIF,iBAAiBV,6BAA6B;QAChD,MAAM,IAAIC,YAAYS,MAAMC,OAAO,EAAE,iBAAiB;YAACC,QAAQ;QAAG;IACpE;IACA,IAAIF,iBAAiBJ,gCAAgC;QACnD,MAAM,IAAIL,YAAYS,MAAMC,OAAO,EAAED,MAAMF,MAAM,EAAE;YACjDK,SAAS;gBAACC,qBAAqBJ,MAAMK,iBAAiB;YAAA;YACtDH,QAAQL,eAAeG,MAAMF,MAAM;QACrC;IACF;IACA,MAAME;AACR"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IntegrationConnection } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import { type RouteGroup } from '@shipfox/node-fastify';
|
|
3
|
+
import type { GithubApiClient } from '#api/client.js';
|
|
4
|
+
import { type ConnectGithubInstallationInput } from '#core/install.js';
|
|
5
|
+
export interface CreateGithubIntegrationRoutesOptions {
|
|
6
|
+
github: GithubApiClient;
|
|
7
|
+
getExistingGithubConnection: (input: {
|
|
8
|
+
installationId: string;
|
|
9
|
+
}) => Promise<IntegrationConnection<'github'> | undefined>;
|
|
10
|
+
connectGithubInstallation: (input: ConnectGithubInstallationInput) => Promise<IntegrationConnection<'github'>>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createGithubIntegrationRoutes({ github, getExistingGithubConnection, connectGithubInstallation, }: CreateGithubIntegrationRoutesOptions): RouteGroup;
|
|
13
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAQ7E,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAC,KAAK,8BAA8B,EAAuB,MAAM,kBAAkB,CAAC;AAK3F,MAAM,WAAW,oCAAoC;IACnD,MAAM,EAAE,eAAe,CAAC;IACxB,2BAA2B,EAAE,CAAC,KAAK,EAAE;QACnC,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3D,yBAAyB,EAAE,CACzB,KAAK,EAAE,8BAA8B,KAClC,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC;CAC/C;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,MAAM,EACN,2BAA2B,EAC3B,yBAAyB,GAC1B,EAAE,oCAAoC,GAAG,UAAU,CA6DnD"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AUTH_USER, requireUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
|
|
2
|
+
import { createGithubInstallBodySchema, createGithubInstallResponseSchema, githubCallbackQuerySchema, githubCallbackResponseSchema } from '@shipfox/api-integration-github-dto';
|
|
3
|
+
import { requireWorkspaceMembership } from '@shipfox/api-workspaces';
|
|
4
|
+
import { defineRoute } from '@shipfox/node-fastify';
|
|
5
|
+
import { config } from '#config.js';
|
|
6
|
+
import { handleGithubCallback } from '#core/install.js';
|
|
7
|
+
import { signGithubInstallState } from '#core/state.js';
|
|
8
|
+
import { toIntegrationConnectionDto } from '#presentation/dto/integrations.js';
|
|
9
|
+
import { githubRouteErrorHandler } from './errors.js';
|
|
10
|
+
export function createGithubIntegrationRoutes({ github, getExistingGithubConnection, connectGithubInstallation }) {
|
|
11
|
+
const createInstallRoute = defineRoute({
|
|
12
|
+
method: 'POST',
|
|
13
|
+
path: '/install',
|
|
14
|
+
auth: AUTH_USER,
|
|
15
|
+
description: 'Create a GitHub App installation URL for a workspace.',
|
|
16
|
+
schema: {
|
|
17
|
+
body: createGithubInstallBodySchema,
|
|
18
|
+
response: {
|
|
19
|
+
200: createGithubInstallResponseSchema
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
handler: (request)=>{
|
|
23
|
+
const { workspace_id: workspaceId } = request.body;
|
|
24
|
+
const actor = requireUserContext(request);
|
|
25
|
+
requireWorkspaceAccess({
|
|
26
|
+
request,
|
|
27
|
+
workspaceId
|
|
28
|
+
});
|
|
29
|
+
const state = signGithubInstallState({
|
|
30
|
+
workspaceId,
|
|
31
|
+
userId: actor.userId
|
|
32
|
+
});
|
|
33
|
+
const installUrl = new URL(`https://github.com/apps/${config.GITHUB_APP_SLUG}/installations/new`);
|
|
34
|
+
installUrl.searchParams.set('state', state);
|
|
35
|
+
return {
|
|
36
|
+
install_url: installUrl.toString()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const callbackApiRoute = defineRoute({
|
|
41
|
+
method: 'GET',
|
|
42
|
+
path: '/callback/api',
|
|
43
|
+
auth: AUTH_USER,
|
|
44
|
+
description: 'Handle the GitHub App installation callback.',
|
|
45
|
+
schema: {
|
|
46
|
+
querystring: githubCallbackQuerySchema,
|
|
47
|
+
response: {
|
|
48
|
+
200: githubCallbackResponseSchema
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
errorHandler: githubRouteErrorHandler,
|
|
52
|
+
handler: async (request)=>{
|
|
53
|
+
const actor = requireUserContext(request);
|
|
54
|
+
const connection = await handleGithubCallback({
|
|
55
|
+
github,
|
|
56
|
+
code: request.query.code,
|
|
57
|
+
installationId: request.query.installation_id,
|
|
58
|
+
state: request.query.state,
|
|
59
|
+
sessionUserId: actor.userId,
|
|
60
|
+
sessionMemberships: actor.memberships,
|
|
61
|
+
requireWorkspaceMembership,
|
|
62
|
+
getExistingGithubConnection,
|
|
63
|
+
connectGithubInstallation
|
|
64
|
+
});
|
|
65
|
+
return toIntegrationConnectionDto(connection);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
prefix: '/integrations/github',
|
|
70
|
+
routes: [
|
|
71
|
+
createInstallRoute,
|
|
72
|
+
callbackApiRoute
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/install.ts"],"sourcesContent":["import {AUTH_USER, requireUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport type {IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {\n createGithubInstallBodySchema,\n createGithubInstallResponseSchema,\n githubCallbackQuerySchema,\n githubCallbackResponseSchema,\n} from '@shipfox/api-integration-github-dto';\nimport {requireWorkspaceMembership} from '@shipfox/api-workspaces';\nimport {defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport type {GithubApiClient} from '#api/client.js';\nimport {config} from '#config.js';\nimport {type ConnectGithubInstallationInput, handleGithubCallback} from '#core/install.js';\nimport {signGithubInstallState} from '#core/state.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\nimport {githubRouteErrorHandler} from './errors.js';\n\nexport interface CreateGithubIntegrationRoutesOptions {\n github: GithubApiClient;\n getExistingGithubConnection: (input: {\n installationId: string;\n }) => Promise<IntegrationConnection<'github'> | undefined>;\n connectGithubInstallation: (\n input: ConnectGithubInstallationInput,\n ) => Promise<IntegrationConnection<'github'>>;\n}\n\nexport function createGithubIntegrationRoutes({\n github,\n getExistingGithubConnection,\n connectGithubInstallation,\n}: CreateGithubIntegrationRoutesOptions): RouteGroup {\n const createInstallRoute = defineRoute({\n method: 'POST',\n path: '/install',\n auth: AUTH_USER,\n description: 'Create a GitHub App installation URL for a workspace.',\n schema: {\n body: createGithubInstallBodySchema,\n response: {\n 200: createGithubInstallResponseSchema,\n },\n },\n handler: (request) => {\n const {workspace_id: workspaceId} = request.body;\n const actor = requireUserContext(request);\n\n requireWorkspaceAccess({request, workspaceId});\n const state = signGithubInstallState({workspaceId, userId: actor.userId});\n const installUrl = new URL(\n `https://github.com/apps/${config.GITHUB_APP_SLUG}/installations/new`,\n );\n installUrl.searchParams.set('state', state);\n\n return {install_url: installUrl.toString()};\n },\n });\n\n const callbackApiRoute = defineRoute({\n method: 'GET',\n path: '/callback/api',\n auth: AUTH_USER,\n description: 'Handle the GitHub App installation callback.',\n schema: {\n querystring: githubCallbackQuerySchema,\n response: {\n 200: githubCallbackResponseSchema,\n },\n },\n errorHandler: githubRouteErrorHandler,\n handler: async (request) => {\n const actor = requireUserContext(request);\n const connection = await handleGithubCallback({\n github,\n code: request.query.code,\n installationId: request.query.installation_id,\n state: request.query.state,\n sessionUserId: actor.userId,\n sessionMemberships: actor.memberships,\n requireWorkspaceMembership,\n getExistingGithubConnection,\n connectGithubInstallation,\n });\n\n return toIntegrationConnectionDto(connection);\n },\n });\n\n return {\n prefix: '/integrations/github',\n routes: [createInstallRoute, callbackApiRoute],\n };\n}\n"],"names":["AUTH_USER","requireUserContext","requireWorkspaceAccess","createGithubInstallBodySchema","createGithubInstallResponseSchema","githubCallbackQuerySchema","githubCallbackResponseSchema","requireWorkspaceMembership","defineRoute","config","handleGithubCallback","signGithubInstallState","toIntegrationConnectionDto","githubRouteErrorHandler","createGithubIntegrationRoutes","github","getExistingGithubConnection","connectGithubInstallation","createInstallRoute","method","path","auth","description","schema","body","response","handler","request","workspace_id","workspaceId","actor","state","userId","installUrl","URL","GITHUB_APP_SLUG","searchParams","set","install_url","toString","callbackApiRoute","querystring","errorHandler","connection","code","query","installationId","installation_id","sessionUserId","sessionMemberships","memberships","prefix","routes"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,kBAAkB,EAAEC,sBAAsB,QAAO,4BAA4B;AAEhG,SACEC,6BAA6B,EAC7BC,iCAAiC,EACjCC,yBAAyB,EACzBC,4BAA4B,QACvB,sCAAsC;AAC7C,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,WAAW,QAAwB,wBAAwB;AAEnE,SAAQC,MAAM,QAAO,aAAa;AAClC,SAA6CC,oBAAoB,QAAO,mBAAmB;AAC3F,SAAQC,sBAAsB,QAAO,iBAAiB;AACtD,SAAQC,0BAA0B,QAAO,oCAAoC;AAC7E,SAAQC,uBAAuB,QAAO,cAAc;AAYpD,OAAO,SAASC,8BAA8B,EAC5CC,MAAM,EACNC,2BAA2B,EAC3BC,yBAAyB,EACY;IACrC,MAAMC,qBAAqBV,YAAY;QACrCW,QAAQ;QACRC,MAAM;QACNC,MAAMrB;QACNsB,aAAa;QACbC,QAAQ;YACNC,MAAMrB;YACNsB,UAAU;gBACR,KAAKrB;YACP;QACF;QACAsB,SAAS,CAACC;YACR,MAAM,EAACC,cAAcC,WAAW,EAAC,GAAGF,QAAQH,IAAI;YAChD,MAAMM,QAAQ7B,mBAAmB0B;YAEjCzB,uBAAuB;gBAACyB;gBAASE;YAAW;YAC5C,MAAME,QAAQpB,uBAAuB;gBAACkB;gBAAaG,QAAQF,MAAME,MAAM;YAAA;YACvE,MAAMC,aAAa,IAAIC,IACrB,CAAC,wBAAwB,EAAEzB,OAAO0B,eAAe,CAAC,kBAAkB,CAAC;YAEvEF,WAAWG,YAAY,CAACC,GAAG,CAAC,SAASN;YAErC,OAAO;gBAACO,aAAaL,WAAWM,QAAQ;YAAE;QAC5C;IACF;IAEA,MAAMC,mBAAmBhC,YAAY;QACnCW,QAAQ;QACRC,MAAM;QACNC,MAAMrB;QACNsB,aAAa;QACbC,QAAQ;YACNkB,aAAapC;YACboB,UAAU;gBACR,KAAKnB;YACP;QACF;QACAoC,cAAc7B;QACda,SAAS,OAAOC;YACd,MAAMG,QAAQ7B,mBAAmB0B;YACjC,MAAMgB,aAAa,MAAMjC,qBAAqB;gBAC5CK;gBACA6B,MAAMjB,QAAQkB,KAAK,CAACD,IAAI;gBACxBE,gBAAgBnB,QAAQkB,KAAK,CAACE,eAAe;gBAC7ChB,OAAOJ,QAAQkB,KAAK,CAACd,KAAK;gBAC1BiB,eAAelB,MAAME,MAAM;gBAC3BiB,oBAAoBnB,MAAMoB,WAAW;gBACrC3C;gBACAS;gBACAC;YACF;YAEA,OAAOL,2BAA2B+B;QACpC;IACF;IAEA,OAAO;QACLQ,QAAQ;QACRC,QAAQ;YAAClC;YAAoBsB;SAAiB;IAChD;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GetIntegrationConnectionByIdFn, PublishIntegrationEventReceivedFn, PublishSourcePushFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import { type RouteGroup } from '@shipfox/node-fastify';
|
|
3
|
+
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
4
|
+
export interface CreateGithubWebhookRoutesOptions {
|
|
5
|
+
coreDb: () => NodePgDatabase<Record<string, unknown>>;
|
|
6
|
+
publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
|
|
7
|
+
publishSourcePush: PublishSourcePushFn;
|
|
8
|
+
recordDeliveryOnly: RecordDeliveryOnlyFn;
|
|
9
|
+
getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
|
|
10
|
+
deleteInstallationTokenSecret?: ((params: {
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
installationId: number;
|
|
13
|
+
}) => Promise<unknown>) | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function createGithubWebhookRoutes(options: CreateGithubWebhookRoutesOptions): RouteGroup;
|
|
16
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/webhooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,8BAA8B,EAC9B,iCAAiC,EACjC,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;IAC7D,6BAA6B,CAAC,EAC1B,CAAC,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAC7E,SAAS,CAAC;CACf;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,UAAU,CAgF/F"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { Webhooks } from '@octokit/webhooks';
|
|
3
|
+
import { defineRoute, rawBodyPlugin, WEBHOOK_BODY_LIMIT } from '@shipfox/node-fastify';
|
|
4
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
5
|
+
import { config } from '#config.js';
|
|
6
|
+
import { handleGithubEvent } from '#core/webhook.js';
|
|
7
|
+
const SIGNATURE_HEADER = 'x-hub-signature-256';
|
|
8
|
+
const EVENT_HEADER = 'x-github-event';
|
|
9
|
+
const DELIVERY_HEADER = 'x-github-delivery';
|
|
10
|
+
export function createGithubWebhookRoutes(options) {
|
|
11
|
+
const webhooks = new Webhooks({
|
|
12
|
+
secret: config.GITHUB_APP_WEBHOOK_SECRET
|
|
13
|
+
});
|
|
14
|
+
const pushRoute = defineRoute({
|
|
15
|
+
method: 'POST',
|
|
16
|
+
path: '/',
|
|
17
|
+
auth: [],
|
|
18
|
+
description: 'GitHub App webhook receiver.',
|
|
19
|
+
options: {
|
|
20
|
+
bodyLimit: WEBHOOK_BODY_LIMIT
|
|
21
|
+
},
|
|
22
|
+
handler: async (request, reply)=>{
|
|
23
|
+
const deliveryId = request.headers[DELIVERY_HEADER];
|
|
24
|
+
const signature = request.headers[SIGNATURE_HEADER];
|
|
25
|
+
const event = request.headers[EVENT_HEADER];
|
|
26
|
+
if (typeof deliveryId !== 'string' || !deliveryId) {
|
|
27
|
+
reply.code(400);
|
|
28
|
+
return {
|
|
29
|
+
error: 'missing X-GitHub-Delivery header'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (typeof signature !== 'string' || !signature) {
|
|
33
|
+
reply.code(401);
|
|
34
|
+
return {
|
|
35
|
+
error: 'missing X-Hub-Signature-256 header'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (typeof event !== 'string' || !event) {
|
|
39
|
+
reply.code(400);
|
|
40
|
+
return {
|
|
41
|
+
error: 'missing X-GitHub-Event header'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const body = request.body;
|
|
45
|
+
if (!Buffer.isBuffer(body)) {
|
|
46
|
+
reply.code(400);
|
|
47
|
+
return {
|
|
48
|
+
error: 'expected raw JSON body'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const rawBody = body.toString('utf8');
|
|
52
|
+
let verified;
|
|
53
|
+
try {
|
|
54
|
+
verified = await webhooks.verify(rawBody, signature);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger().warn({
|
|
57
|
+
deliveryId,
|
|
58
|
+
err: error
|
|
59
|
+
}, 'github webhook signature verification threw');
|
|
60
|
+
verified = false;
|
|
61
|
+
}
|
|
62
|
+
if (!verified) {
|
|
63
|
+
reply.code(401);
|
|
64
|
+
return {
|
|
65
|
+
error: 'invalid signature'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
let parsedJson;
|
|
69
|
+
try {
|
|
70
|
+
parsedJson = JSON.parse(rawBody);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger().warn({
|
|
73
|
+
deliveryId,
|
|
74
|
+
err: error
|
|
75
|
+
}, 'github webhook payload JSON parse failed');
|
|
76
|
+
reply.code(400);
|
|
77
|
+
return {
|
|
78
|
+
error: 'malformed JSON'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
await options.coreDb().transaction(async (tx)=>{
|
|
82
|
+
await handleGithubEvent({
|
|
83
|
+
tx,
|
|
84
|
+
deliveryId,
|
|
85
|
+
event,
|
|
86
|
+
payload: parsedJson,
|
|
87
|
+
publishIntegrationEventReceived: options.publishIntegrationEventReceived,
|
|
88
|
+
publishSourcePush: options.publishSourcePush,
|
|
89
|
+
recordDeliveryOnly: options.recordDeliveryOnly,
|
|
90
|
+
getIntegrationConnectionById: options.getIntegrationConnectionById,
|
|
91
|
+
deleteInstallationTokenSecret: options.deleteInstallationTokenSecret
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
reply.code(204);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
prefix: '/webhooks/integrations/github',
|
|
100
|
+
auth: [],
|
|
101
|
+
plugins: [
|
|
102
|
+
rawBodyPlugin
|
|
103
|
+
],
|
|
104
|
+
routes: [
|
|
105
|
+
pushRoute
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {Webhooks} from '@octokit/webhooks';\nimport type {\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {\n defineRoute,\n type RouteGroup,\n rawBodyPlugin,\n WEBHOOK_BODY_LIMIT,\n} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {handleGithubEvent} from '#core/webhook.js';\n\nconst SIGNATURE_HEADER = 'x-hub-signature-256';\nconst EVENT_HEADER = 'x-github-event';\nconst DELIVERY_HEADER = 'x-github-delivery';\n\nexport interface CreateGithubWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n deleteInstallationTokenSecret?:\n | ((params: {workspaceId: string; installationId: number}) => Promise<unknown>)\n | undefined;\n}\n\nexport function createGithubWebhookRoutes(options: CreateGithubWebhookRoutesOptions): RouteGroup {\n const webhooks = new Webhooks({secret: config.GITHUB_APP_WEBHOOK_SECRET});\n\n const pushRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'GitHub App webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const deliveryId = request.headers[DELIVERY_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n const event = request.headers[EVENT_HEADER];\n\n if (typeof deliveryId !== 'string' || !deliveryId) {\n reply.code(400);\n return {error: 'missing X-GitHub-Delivery header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing X-Hub-Signature-256 header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing X-GitHub-Event header'};\n }\n\n const body = request.body;\n if (!Buffer.isBuffer(body)) {\n reply.code(400);\n return {error: 'expected raw JSON body'};\n }\n const rawBody = body.toString('utf8');\n\n let verified: boolean;\n try {\n verified = await webhooks.verify(rawBody, signature);\n } catch (error) {\n logger().warn({deliveryId, err: error}, 'github webhook signature verification threw');\n verified = false;\n }\n if (!verified) {\n reply.code(401);\n return {error: 'invalid signature'};\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(rawBody);\n } catch (error) {\n logger().warn({deliveryId, err: error}, 'github webhook payload JSON parse failed');\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n\n await options.coreDb().transaction(async (tx) => {\n await handleGithubEvent({\n tx,\n deliveryId,\n event,\n payload: parsedJson,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n deleteInstallationTokenSecret: options.deleteInstallationTokenSecret,\n });\n });\n\n reply.code(204);\n return null;\n },\n });\n\n return {\n prefix: '/webhooks/integrations/github',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [pushRoute],\n };\n}\n"],"names":["Buffer","Webhooks","defineRoute","rawBodyPlugin","WEBHOOK_BODY_LIMIT","logger","config","handleGithubEvent","SIGNATURE_HEADER","EVENT_HEADER","DELIVERY_HEADER","createGithubWebhookRoutes","options","webhooks","secret","GITHUB_APP_WEBHOOK_SECRET","pushRoute","method","path","auth","description","bodyLimit","handler","request","reply","deliveryId","headers","signature","event","code","error","body","isBuffer","rawBody","toString","verified","verify","warn","err","parsedJson","JSON","parse","coreDb","transaction","tx","payload","publishIntegrationEventReceived","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","deleteInstallationTokenSecret","prefix","plugins","routes"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SAAQC,QAAQ,QAAO,oBAAoB;AAO3C,SACEC,WAAW,EAEXC,aAAa,EACbC,kBAAkB,QACb,wBAAwB;AAC/B,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,iBAAiB,QAAO,mBAAmB;AAEnD,MAAMC,mBAAmB;AACzB,MAAMC,eAAe;AACrB,MAAMC,kBAAkB;AAaxB,OAAO,SAASC,0BAA0BC,OAAyC;IACjF,MAAMC,WAAW,IAAIZ,SAAS;QAACa,QAAQR,OAAOS,yBAAyB;IAAA;IAEvE,MAAMC,YAAYd,YAAY;QAC5Be,QAAQ;QACRC,MAAM;QACNC,MAAM,EAAE;QACRC,aAAa;QACbR,SAAS;YAACS,WAAWjB;QAAkB;QACvCkB,SAAS,OAAOC,SAASC;YACvB,MAAMC,aAAaF,QAAQG,OAAO,CAAChB,gBAAgB;YACnD,MAAMiB,YAAYJ,QAAQG,OAAO,CAAClB,iBAAiB;YACnD,MAAMoB,QAAQL,QAAQG,OAAO,CAACjB,aAAa;YAE3C,IAAI,OAAOgB,eAAe,YAAY,CAACA,YAAY;gBACjDD,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAkC;YACnD;YACA,IAAI,OAAOH,cAAc,YAAY,CAACA,WAAW;gBAC/CH,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAoC;YACrD;YACA,IAAI,OAAOF,UAAU,YAAY,CAACA,OAAO;gBACvCJ,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAA+B;YAChD;YAEA,MAAMC,OAAOR,QAAQQ,IAAI;YACzB,IAAI,CAAC/B,OAAOgC,QAAQ,CAACD,OAAO;gBAC1BP,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAwB;YACzC;YACA,MAAMG,UAAUF,KAAKG,QAAQ,CAAC;YAE9B,IAAIC;YACJ,IAAI;gBACFA,WAAW,MAAMtB,SAASuB,MAAM,CAACH,SAASN;YAC5C,EAAE,OAAOG,OAAO;gBACdzB,SAASgC,IAAI,CAAC;oBAACZ;oBAAYa,KAAKR;gBAAK,GAAG;gBACxCK,WAAW;YACb;YACA,IAAI,CAACA,UAAU;gBACbX,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAmB;YACpC;YAEA,IAAIS;YACJ,IAAI;gBACFA,aAAaC,KAAKC,KAAK,CAACR;YAC1B,EAAE,OAAOH,OAAO;gBACdzB,SAASgC,IAAI,CAAC;oBAACZ;oBAAYa,KAAKR;gBAAK,GAAG;gBACxCN,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAgB;YACjC;YAEA,MAAMlB,QAAQ8B,MAAM,GAAGC,WAAW,CAAC,OAAOC;gBACxC,MAAMrC,kBAAkB;oBACtBqC;oBACAnB;oBACAG;oBACAiB,SAASN;oBACTO,iCAAiClC,QAAQkC,+BAA+B;oBACxEC,mBAAmBnC,QAAQmC,iBAAiB;oBAC5CC,oBAAoBpC,QAAQoC,kBAAkB;oBAC9CC,8BAA8BrC,QAAQqC,4BAA4B;oBAClEC,+BAA+BtC,QAAQsC,6BAA6B;gBACtE;YACF;YAEA1B,MAAMK,IAAI,CAAC;YACX,OAAO;QACT;IACF;IAEA,OAAO;QACLsB,QAAQ;QACRhC,MAAM,EAAE;QACRiC,SAAS;YAACjD;SAAc;QACxBkD,QAAQ;YAACrC;SAAU;IACrB;AACF"}
|