@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,54 @@
|
|
|
1
|
+
import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { config } from '#config.js';
|
|
3
|
+
import { GithubInstallStateError } from './errors.js';
|
|
4
|
+
const STATE_TTL_SECONDS = 30 * 60;
|
|
5
|
+
export function signGithubInstallState(params) {
|
|
6
|
+
const now = params.now ?? new Date();
|
|
7
|
+
const payload = {
|
|
8
|
+
workspaceId: params.workspaceId,
|
|
9
|
+
userId: params.userId,
|
|
10
|
+
nonce: params.nonce ?? randomUUID(),
|
|
11
|
+
expiresAt: Math.floor(now.getTime() / 1000) + STATE_TTL_SECONDS
|
|
12
|
+
};
|
|
13
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
14
|
+
return `${encodedPayload}.${sign(encodedPayload)}`;
|
|
15
|
+
}
|
|
16
|
+
export function verifyGithubInstallState(state, now = new Date()) {
|
|
17
|
+
const [encodedPayload, signature, extra] = state.split('.');
|
|
18
|
+
if (!encodedPayload || !signature || extra !== undefined) {
|
|
19
|
+
throw new GithubInstallStateError('Invalid GitHub install state');
|
|
20
|
+
}
|
|
21
|
+
if (!constantTimeEqual(signature, sign(encodedPayload))) {
|
|
22
|
+
throw new GithubInstallStateError('Invalid GitHub install state signature');
|
|
23
|
+
}
|
|
24
|
+
const payload = parsePayload(encodedPayload);
|
|
25
|
+
if (payload.expiresAt < Math.floor(now.getTime() / 1000)) {
|
|
26
|
+
throw new GithubInstallStateError('Expired GitHub install state');
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
workspaceId: payload.workspaceId,
|
|
30
|
+
userId: payload.userId
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function sign(encodedPayload) {
|
|
34
|
+
return createHmac('sha256', config.GITHUB_INSTALL_STATE_SECRET).update(encodedPayload).digest('base64url');
|
|
35
|
+
}
|
|
36
|
+
function constantTimeEqual(a, b) {
|
|
37
|
+
const left = Buffer.from(a);
|
|
38
|
+
const right = Buffer.from(b);
|
|
39
|
+
if (left.length !== right.length) return false;
|
|
40
|
+
return timingSafeEqual(left, right);
|
|
41
|
+
}
|
|
42
|
+
function parsePayload(encodedPayload) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
|
|
45
|
+
if (typeof parsed.workspaceId !== 'string' || typeof parsed.userId !== 'string' || typeof parsed.nonce !== 'string' || typeof parsed.expiresAt !== 'number') {
|
|
46
|
+
throw new Error('Invalid payload shape');
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
} catch (_error) {
|
|
50
|
+
throw new GithubInstallStateError('Invalid GitHub install state payload');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/state.ts"],"sourcesContent":["import {createHmac, randomUUID, timingSafeEqual} from 'node:crypto';\nimport {config} from '#config.js';\nimport {GithubInstallStateError} from './errors.js';\n\nconst STATE_TTL_SECONDS = 30 * 60;\n\ninterface GithubInstallStatePayload {\n workspaceId: string;\n userId: string;\n nonce: string;\n expiresAt: number;\n}\n\nexport interface GithubInstallStateClaims {\n workspaceId: string;\n userId: string;\n}\n\nexport function signGithubInstallState(params: {\n workspaceId: string;\n userId: string;\n nonce?: string | undefined;\n now?: Date | undefined;\n}): string {\n const now = params.now ?? new Date();\n const payload: GithubInstallStatePayload = {\n workspaceId: params.workspaceId,\n userId: params.userId,\n nonce: params.nonce ?? randomUUID(),\n expiresAt: Math.floor(now.getTime() / 1000) + STATE_TTL_SECONDS,\n };\n const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');\n return `${encodedPayload}.${sign(encodedPayload)}`;\n}\n\nexport function verifyGithubInstallState(\n state: string,\n now: Date = new Date(),\n): GithubInstallStateClaims {\n const [encodedPayload, signature, extra] = state.split('.');\n if (!encodedPayload || !signature || extra !== undefined) {\n throw new GithubInstallStateError('Invalid GitHub install state');\n }\n\n if (!constantTimeEqual(signature, sign(encodedPayload))) {\n throw new GithubInstallStateError('Invalid GitHub install state signature');\n }\n\n const payload = parsePayload(encodedPayload);\n if (payload.expiresAt < Math.floor(now.getTime() / 1000)) {\n throw new GithubInstallStateError('Expired GitHub install state');\n }\n\n return {workspaceId: payload.workspaceId, userId: payload.userId};\n}\n\nfunction sign(encodedPayload: string): string {\n return createHmac('sha256', config.GITHUB_INSTALL_STATE_SECRET)\n .update(encodedPayload)\n .digest('base64url');\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a);\n const right = Buffer.from(b);\n if (left.length !== right.length) return false;\n return timingSafeEqual(left, right);\n}\n\nfunction parsePayload(encodedPayload: string): GithubInstallStatePayload {\n try {\n const parsed = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));\n if (\n typeof parsed.workspaceId !== 'string' ||\n typeof parsed.userId !== 'string' ||\n typeof parsed.nonce !== 'string' ||\n typeof parsed.expiresAt !== 'number'\n ) {\n throw new Error('Invalid payload shape');\n }\n return parsed;\n } catch (_error) {\n throw new GithubInstallStateError('Invalid GitHub install state payload');\n }\n}\n"],"names":["createHmac","randomUUID","timingSafeEqual","config","GithubInstallStateError","STATE_TTL_SECONDS","signGithubInstallState","params","now","Date","payload","workspaceId","userId","nonce","expiresAt","Math","floor","getTime","encodedPayload","Buffer","from","JSON","stringify","toString","sign","verifyGithubInstallState","state","signature","extra","split","undefined","constantTimeEqual","parsePayload","GITHUB_INSTALL_STATE_SECRET","update","digest","a","b","left","right","length","parsed","parse","Error","_error"],"mappings":"AAAA,SAAQA,UAAU,EAAEC,UAAU,EAAEC,eAAe,QAAO,cAAc;AACpE,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,uBAAuB,QAAO,cAAc;AAEpD,MAAMC,oBAAoB,KAAK;AAc/B,OAAO,SAASC,uBAAuBC,MAKtC;IACC,MAAMC,MAAMD,OAAOC,GAAG,IAAI,IAAIC;IAC9B,MAAMC,UAAqC;QACzCC,aAAaJ,OAAOI,WAAW;QAC/BC,QAAQL,OAAOK,MAAM;QACrBC,OAAON,OAAOM,KAAK,IAAIZ;QACvBa,WAAWC,KAAKC,KAAK,CAACR,IAAIS,OAAO,KAAK,QAAQZ;IAChD;IACA,MAAMa,iBAAiBC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACZ,UAAUa,QAAQ,CAAC;IACrE,OAAO,GAAGL,eAAe,CAAC,EAAEM,KAAKN,iBAAiB;AACpD;AAEA,OAAO,SAASO,yBACdC,KAAa,EACblB,MAAY,IAAIC,MAAM;IAEtB,MAAM,CAACS,gBAAgBS,WAAWC,MAAM,GAAGF,MAAMG,KAAK,CAAC;IACvD,IAAI,CAACX,kBAAkB,CAACS,aAAaC,UAAUE,WAAW;QACxD,MAAM,IAAI1B,wBAAwB;IACpC;IAEA,IAAI,CAAC2B,kBAAkBJ,WAAWH,KAAKN,kBAAkB;QACvD,MAAM,IAAId,wBAAwB;IACpC;IAEA,MAAMM,UAAUsB,aAAad;IAC7B,IAAIR,QAAQI,SAAS,GAAGC,KAAKC,KAAK,CAACR,IAAIS,OAAO,KAAK,OAAO;QACxD,MAAM,IAAIb,wBAAwB;IACpC;IAEA,OAAO;QAACO,aAAaD,QAAQC,WAAW;QAAEC,QAAQF,QAAQE,MAAM;IAAA;AAClE;AAEA,SAASY,KAAKN,cAAsB;IAClC,OAAOlB,WAAW,UAAUG,OAAO8B,2BAA2B,EAC3DC,MAAM,CAAChB,gBACPiB,MAAM,CAAC;AACZ;AAEA,SAASJ,kBAAkBK,CAAS,EAAEC,CAAS;IAC7C,MAAMC,OAAOnB,OAAOC,IAAI,CAACgB;IACzB,MAAMG,QAAQpB,OAAOC,IAAI,CAACiB;IAC1B,IAAIC,KAAKE,MAAM,KAAKD,MAAMC,MAAM,EAAE,OAAO;IACzC,OAAOtC,gBAAgBoC,MAAMC;AAC/B;AAEA,SAASP,aAAad,cAAsB;IAC1C,IAAI;QACF,MAAMuB,SAASpB,KAAKqB,KAAK,CAACvB,OAAOC,IAAI,CAACF,gBAAgB,aAAaK,QAAQ,CAAC;QAC5E,IACE,OAAOkB,OAAO9B,WAAW,KAAK,YAC9B,OAAO8B,OAAO7B,MAAM,KAAK,YACzB,OAAO6B,OAAO5B,KAAK,KAAK,YACxB,OAAO4B,OAAO3B,SAAS,KAAK,UAC5B;YACA,MAAM,IAAI6B,MAAM;QAClB;QACA,OAAOF;IACT,EAAE,OAAOG,QAAQ;QACf,MAAM,IAAIxC,wBAAwB;IACpC;AACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type GetIntegrationConnectionByIdFn, type IntegrationTx, type PublishIntegrationEventReceivedFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
export interface HandleGithubEventParams {
|
|
3
|
+
tx: IntegrationTx;
|
|
4
|
+
deliveryId: string;
|
|
5
|
+
event: string;
|
|
6
|
+
payload: unknown;
|
|
7
|
+
publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
|
|
8
|
+
publishSourcePush: PublishSourcePushFn;
|
|
9
|
+
recordDeliveryOnly: RecordDeliveryOnlyFn;
|
|
10
|
+
getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
|
|
11
|
+
deleteInstallationTokenSecret?: ((params: {
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
installationId: number;
|
|
14
|
+
}) => Promise<unknown>) | undefined;
|
|
15
|
+
}
|
|
16
|
+
export type HandleGithubEventOutcome = 'published' | 'duplicate' | 'published-envelope' | 'duplicate-envelope' | 'published-push-envelope-only' | 'duplicate-push-envelope-only' | 'unknown-installation' | 'missing-connection' | 'inactive-connection' | 'no-installation-id';
|
|
17
|
+
export declare function handleGithubEvent(params: HandleGithubEventParams): Promise<{
|
|
18
|
+
outcome: HandleGithubEventOutcome;
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;AAe3C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,+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,MAAM,MAAM,wBAAwB,GAChC,WAAW,GACX,WAAW,GACX,oBAAoB,GACpB,oBAAoB,GACpB,8BAA8B,GAC9B,8BAA8B,GAC9B,sBAAsB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,oBAAoB,CAAC;AAMzB,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC;IAAC,OAAO,EAAE,wBAAwB,CAAA;CAAC,CAAC,CAmH9C"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { buildProviderRepositoryId } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import { githubPushPayloadSchema, githubWebhookActionSchema, githubWebhookInstallationSchema } from '@shipfox/api-integration-github-dto';
|
|
3
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
4
|
+
import { getGithubInstallationByInstallationId } from '#db/installations.js';
|
|
5
|
+
const REFS_HEADS_PREFIX = 'refs/heads/';
|
|
6
|
+
const GITHUB_SOURCE = 'github';
|
|
7
|
+
// GitHub sends a `push` webhook for a branch deletion with `after` set to this all-zero SHA.
|
|
8
|
+
const DELETED_BRANCH_SHA = '0'.repeat(40);
|
|
9
|
+
function isBranchDeletion(after) {
|
|
10
|
+
return after === DELETED_BRANCH_SHA;
|
|
11
|
+
}
|
|
12
|
+
export async function handleGithubEvent(params) {
|
|
13
|
+
const actionEnvelope = githubWebhookActionSchema.safeParse(params.payload);
|
|
14
|
+
const action = actionEnvelope.success ? actionEnvelope.data.action : undefined;
|
|
15
|
+
const installationEnvelope = githubWebhookInstallationSchema.safeParse(params.payload);
|
|
16
|
+
const installationId = installationEnvelope.success ? installationEnvelope.data.installation?.id : undefined;
|
|
17
|
+
if (installationId === undefined) {
|
|
18
|
+
await params.recordDeliveryOnly({
|
|
19
|
+
tx: params.tx,
|
|
20
|
+
provider: GITHUB_SOURCE,
|
|
21
|
+
deliveryId: params.deliveryId
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
outcome: 'no-installation-id'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const installation = await getGithubInstallationByInstallationId(String(installationId), {
|
|
28
|
+
tx: params.tx
|
|
29
|
+
});
|
|
30
|
+
if (!installation) {
|
|
31
|
+
logger().warn({
|
|
32
|
+
deliveryId: params.deliveryId,
|
|
33
|
+
installationId
|
|
34
|
+
}, 'github webhook: unknown installation, dropping');
|
|
35
|
+
await params.recordDeliveryOnly({
|
|
36
|
+
tx: params.tx,
|
|
37
|
+
provider: GITHUB_SOURCE,
|
|
38
|
+
deliveryId: params.deliveryId
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
outcome: 'unknown-installation'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const connection = await params.getIntegrationConnectionById(installation.connectionId, {
|
|
45
|
+
tx: params.tx
|
|
46
|
+
});
|
|
47
|
+
if (!connection) {
|
|
48
|
+
logger().warn({
|
|
49
|
+
deliveryId: params.deliveryId,
|
|
50
|
+
installationId,
|
|
51
|
+
connectionId: installation.connectionId
|
|
52
|
+
}, 'github webhook: installation has no connection, dropping');
|
|
53
|
+
await params.recordDeliveryOnly({
|
|
54
|
+
tx: params.tx,
|
|
55
|
+
provider: GITHUB_SOURCE,
|
|
56
|
+
deliveryId: params.deliveryId
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
outcome: 'missing-connection'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (connection.lifecycleStatus !== 'active') {
|
|
63
|
+
const logContext = {
|
|
64
|
+
deliveryId: params.deliveryId,
|
|
65
|
+
installationId,
|
|
66
|
+
connectionId: connection.id,
|
|
67
|
+
workspaceId: connection.workspaceId,
|
|
68
|
+
lifecycleStatus: connection.lifecycleStatus
|
|
69
|
+
};
|
|
70
|
+
// `disabled` is an expected steady state; only `error` is anomalous.
|
|
71
|
+
if (connection.lifecycleStatus === 'error') {
|
|
72
|
+
logger().warn(logContext, 'github webhook: connection in error state, dropping');
|
|
73
|
+
} else {
|
|
74
|
+
logger().info(logContext, 'github webhook: connection disabled, dropping');
|
|
75
|
+
}
|
|
76
|
+
await params.recordDeliveryOnly({
|
|
77
|
+
tx: params.tx,
|
|
78
|
+
provider: GITHUB_SOURCE,
|
|
79
|
+
deliveryId: params.deliveryId
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
outcome: 'inactive-connection'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (params.event === 'push') {
|
|
86
|
+
const validated = githubPushPayloadSchema.safeParse(params.payload);
|
|
87
|
+
if (!validated.success) {
|
|
88
|
+
logger().warn({
|
|
89
|
+
deliveryId: params.deliveryId,
|
|
90
|
+
issues: validated.error.issues
|
|
91
|
+
}, 'github webhook push payload failed schema validation; publishing generic envelope only');
|
|
92
|
+
return publishGithubEnvelopeOnly({
|
|
93
|
+
tx: params.tx,
|
|
94
|
+
deliveryId: params.deliveryId,
|
|
95
|
+
payload: params.payload,
|
|
96
|
+
publishIntegrationEventReceived: params.publishIntegrationEventReceived,
|
|
97
|
+
connection,
|
|
98
|
+
event: 'push'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return publishGithubPush({
|
|
102
|
+
...params,
|
|
103
|
+
eventPayload: validated.data,
|
|
104
|
+
rawPayload: params.payload,
|
|
105
|
+
connection
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const eventName = action ? `${params.event}.${action}` : params.event;
|
|
109
|
+
const result = await publishGithubEnvelopeOnly({
|
|
110
|
+
tx: params.tx,
|
|
111
|
+
deliveryId: params.deliveryId,
|
|
112
|
+
payload: params.payload,
|
|
113
|
+
publishIntegrationEventReceived: params.publishIntegrationEventReceived,
|
|
114
|
+
connection,
|
|
115
|
+
event: eventName
|
|
116
|
+
});
|
|
117
|
+
if (result.outcome === 'published-envelope') {
|
|
118
|
+
await deleteInstallationTokenSecretBestEffort({
|
|
119
|
+
deleteInstallationTokenSecret: params.deleteInstallationTokenSecret,
|
|
120
|
+
event: params.event,
|
|
121
|
+
action,
|
|
122
|
+
deliveryId: params.deliveryId,
|
|
123
|
+
workspaceId: connection.workspaceId,
|
|
124
|
+
installationId
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
function shouldDeleteInstallationTokenSecret(event, action) {
|
|
130
|
+
return event === 'installation' && (action === 'deleted' || action === 'suspend');
|
|
131
|
+
}
|
|
132
|
+
async function deleteInstallationTokenSecretBestEffort(params) {
|
|
133
|
+
if (!shouldDeleteInstallationTokenSecret(params.event, params.action)) return;
|
|
134
|
+
try {
|
|
135
|
+
await params.deleteInstallationTokenSecret?.({
|
|
136
|
+
workspaceId: params.workspaceId,
|
|
137
|
+
installationId: params.installationId
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logger().warn({
|
|
141
|
+
deliveryId: params.deliveryId,
|
|
142
|
+
installationId: params.installationId,
|
|
143
|
+
workspaceId: params.workspaceId,
|
|
144
|
+
error
|
|
145
|
+
}, 'github webhook installation token cleanup failed');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function publishGithubPush(params) {
|
|
149
|
+
if (isBranchDeletion(params.eventPayload.after)) {
|
|
150
|
+
const result = await params.publishIntegrationEventReceived({
|
|
151
|
+
tx: params.tx,
|
|
152
|
+
event: {
|
|
153
|
+
provider: GITHUB_SOURCE,
|
|
154
|
+
source: params.connection.slug,
|
|
155
|
+
event: 'push',
|
|
156
|
+
workspaceId: params.connection.workspaceId,
|
|
157
|
+
connectionId: params.connection.id,
|
|
158
|
+
connectionName: params.connection.displayName,
|
|
159
|
+
deliveryId: params.deliveryId,
|
|
160
|
+
receivedAt: new Date().toISOString(),
|
|
161
|
+
payload: params.rawPayload
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
outcome: result.published ? 'published-push-envelope-only' : 'duplicate-push-envelope-only'
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const ref = stripRefsHeads(params.eventPayload.ref);
|
|
169
|
+
const defaultBranch = params.eventPayload.repository.default_branch;
|
|
170
|
+
const push = {
|
|
171
|
+
externalRepositoryId: buildProviderRepositoryId(GITHUB_SOURCE, String(params.eventPayload.repository.id)),
|
|
172
|
+
ref,
|
|
173
|
+
headCommitSha: params.eventPayload.after,
|
|
174
|
+
defaultBranch,
|
|
175
|
+
isDefaultBranch: ref === defaultBranch
|
|
176
|
+
};
|
|
177
|
+
const result = await params.publishSourcePush({
|
|
178
|
+
tx: params.tx,
|
|
179
|
+
provider: GITHUB_SOURCE,
|
|
180
|
+
source: params.connection.slug,
|
|
181
|
+
workspaceId: params.connection.workspaceId,
|
|
182
|
+
connectionId: params.connection.id,
|
|
183
|
+
connectionName: params.connection.displayName,
|
|
184
|
+
deliveryId: params.deliveryId,
|
|
185
|
+
receivedAt: new Date().toISOString(),
|
|
186
|
+
rawPayload: params.rawPayload,
|
|
187
|
+
push
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
outcome: result.published ? 'published' : 'duplicate'
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async function publishGithubEnvelopeOnly(params) {
|
|
194
|
+
const result = await params.publishIntegrationEventReceived({
|
|
195
|
+
tx: params.tx,
|
|
196
|
+
event: {
|
|
197
|
+
provider: GITHUB_SOURCE,
|
|
198
|
+
source: params.connection.slug,
|
|
199
|
+
event: params.event,
|
|
200
|
+
workspaceId: params.connection.workspaceId,
|
|
201
|
+
connectionId: params.connection.id,
|
|
202
|
+
connectionName: params.connection.displayName,
|
|
203
|
+
deliveryId: params.deliveryId,
|
|
204
|
+
receivedAt: new Date().toISOString(),
|
|
205
|
+
payload: params.payload
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
outcome: result.published ? 'published-envelope' : 'duplicate-envelope'
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function stripRefsHeads(ref) {
|
|
213
|
+
return ref.startsWith(REFS_HEADS_PREFIX) ? ref.slice(REFS_HEADS_PREFIX.length) : ref;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n buildProviderRepositoryId,\n type GetIntegrationConnectionByIdFn,\n type IntegrationTx,\n type PublishIntegrationEventReceivedFn,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type SourcePushPayload,\n} from '@shipfox/api-integration-core-dto';\nimport {\n type GithubPushPayloadDto,\n githubPushPayloadSchema,\n githubWebhookActionSchema,\n githubWebhookInstallationSchema,\n} from '@shipfox/api-integration-github-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {getGithubInstallationByInstallationId} from '#db/installations.js';\n\nconst REFS_HEADS_PREFIX = 'refs/heads/';\nconst GITHUB_SOURCE = 'github';\n// GitHub sends a `push` webhook for a branch deletion with `after` set to this all-zero SHA.\nconst DELETED_BRANCH_SHA = '0'.repeat(40);\n\nexport interface HandleGithubEventParams {\n tx: IntegrationTx;\n deliveryId: string;\n event: string;\n payload: 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 type HandleGithubEventOutcome =\n | 'published'\n | 'duplicate'\n | 'published-envelope'\n | 'duplicate-envelope'\n | 'published-push-envelope-only'\n | 'duplicate-push-envelope-only'\n | 'unknown-installation'\n | 'missing-connection'\n | 'inactive-connection'\n | 'no-installation-id';\n\nfunction isBranchDeletion(after: string): boolean {\n return after === DELETED_BRANCH_SHA;\n}\n\nexport async function handleGithubEvent(\n params: HandleGithubEventParams,\n): Promise<{outcome: HandleGithubEventOutcome}> {\n const actionEnvelope = githubWebhookActionSchema.safeParse(params.payload);\n const action = actionEnvelope.success ? actionEnvelope.data.action : undefined;\n const installationEnvelope = githubWebhookInstallationSchema.safeParse(params.payload);\n const installationId = installationEnvelope.success\n ? installationEnvelope.data.installation?.id\n : undefined;\n if (installationId === undefined) {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'no-installation-id'};\n }\n\n const installation = await getGithubInstallationByInstallationId(String(installationId), {\n tx: params.tx,\n });\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, installationId},\n 'github webhook: unknown installation, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'unknown-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {deliveryId: params.deliveryId, installationId, connectionId: installation.connectionId},\n 'github webhook: installation has no connection, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n const logContext = {\n deliveryId: params.deliveryId,\n installationId,\n connectionId: connection.id,\n workspaceId: connection.workspaceId,\n lifecycleStatus: connection.lifecycleStatus,\n };\n // `disabled` is an expected steady state; only `error` is anomalous.\n if (connection.lifecycleStatus === 'error') {\n logger().warn(logContext, 'github webhook: connection in error state, dropping');\n } else {\n logger().info(logContext, 'github webhook: connection disabled, dropping');\n }\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'inactive-connection'};\n }\n\n if (params.event === 'push') {\n const validated = githubPushPayloadSchema.safeParse(params.payload);\n if (!validated.success) {\n logger().warn(\n {deliveryId: params.deliveryId, issues: validated.error.issues},\n 'github webhook push payload failed schema validation; publishing generic envelope only',\n );\n return publishGithubEnvelopeOnly({\n tx: params.tx,\n deliveryId: params.deliveryId,\n payload: params.payload,\n publishIntegrationEventReceived: params.publishIntegrationEventReceived,\n connection,\n event: 'push',\n });\n }\n\n return publishGithubPush({\n ...params,\n eventPayload: validated.data,\n rawPayload: params.payload,\n connection,\n });\n }\n\n const eventName = action ? `${params.event}.${action}` : params.event;\n const result = await publishGithubEnvelopeOnly({\n tx: params.tx,\n deliveryId: params.deliveryId,\n payload: params.payload,\n publishIntegrationEventReceived: params.publishIntegrationEventReceived,\n connection,\n event: eventName,\n });\n if (result.outcome === 'published-envelope') {\n await deleteInstallationTokenSecretBestEffort({\n deleteInstallationTokenSecret: params.deleteInstallationTokenSecret,\n event: params.event,\n action,\n deliveryId: params.deliveryId,\n workspaceId: connection.workspaceId,\n installationId,\n });\n }\n return result;\n}\n\nfunction shouldDeleteInstallationTokenSecret(event: string, action: string | undefined): boolean {\n return event === 'installation' && (action === 'deleted' || action === 'suspend');\n}\n\nasync function deleteInstallationTokenSecretBestEffort(params: {\n deleteInstallationTokenSecret:\n | ((params: {workspaceId: string; installationId: number}) => Promise<unknown>)\n | undefined;\n event: string;\n action: string | undefined;\n deliveryId: string;\n workspaceId: string;\n installationId: number;\n}): Promise<void> {\n if (!shouldDeleteInstallationTokenSecret(params.event, params.action)) return;\n\n try {\n await params.deleteInstallationTokenSecret?.({\n workspaceId: params.workspaceId,\n installationId: params.installationId,\n });\n } catch (error) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n installationId: params.installationId,\n workspaceId: params.workspaceId,\n error,\n },\n 'github webhook installation token cleanup failed',\n );\n }\n}\n\nasync function publishGithubPush(params: {\n tx: IntegrationTx;\n deliveryId: string;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n publishSourcePush: PublishSourcePushFn;\n eventPayload: GithubPushPayloadDto;\n rawPayload: unknown;\n connection: {\n id: string;\n workspaceId: string;\n slug: string;\n displayName: string;\n };\n}): Promise<{outcome: HandleGithubEventOutcome}> {\n if (isBranchDeletion(params.eventPayload.after)) {\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: GITHUB_SOURCE,\n source: params.connection.slug,\n event: 'push',\n workspaceId: params.connection.workspaceId,\n connectionId: params.connection.id,\n connectionName: params.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: params.rawPayload,\n },\n });\n return {\n outcome: result.published ? 'published-push-envelope-only' : 'duplicate-push-envelope-only',\n };\n }\n\n const ref = stripRefsHeads(params.eventPayload.ref);\n const defaultBranch = params.eventPayload.repository.default_branch;\n const push: SourcePushPayload = {\n externalRepositoryId: buildProviderRepositoryId(\n GITHUB_SOURCE,\n String(params.eventPayload.repository.id),\n ),\n ref,\n headCommitSha: params.eventPayload.after,\n defaultBranch,\n isDefaultBranch: ref === defaultBranch,\n };\n const result = await params.publishSourcePush({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n source: params.connection.slug,\n workspaceId: params.connection.workspaceId,\n connectionId: params.connection.id,\n connectionName: params.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n rawPayload: params.rawPayload,\n push,\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nasync function publishGithubEnvelopeOnly(params: {\n tx: IntegrationTx;\n deliveryId: string;\n payload: unknown;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n connection: {id: string; workspaceId: string; slug: string; displayName: string};\n event: string;\n}): Promise<{outcome: HandleGithubEventOutcome}> {\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: GITHUB_SOURCE,\n source: params.connection.slug,\n event: params.event,\n workspaceId: params.connection.workspaceId,\n connectionId: params.connection.id,\n connectionName: params.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: params.payload,\n },\n });\n return {outcome: result.published ? 'published-envelope' : 'duplicate-envelope'};\n}\n\nfunction stripRefsHeads(ref: string): string {\n return ref.startsWith(REFS_HEADS_PREFIX) ? ref.slice(REFS_HEADS_PREFIX.length) : ref;\n}\n"],"names":["buildProviderRepositoryId","githubPushPayloadSchema","githubWebhookActionSchema","githubWebhookInstallationSchema","logger","getGithubInstallationByInstallationId","REFS_HEADS_PREFIX","GITHUB_SOURCE","DELETED_BRANCH_SHA","repeat","isBranchDeletion","after","handleGithubEvent","params","actionEnvelope","safeParse","payload","action","success","data","undefined","installationEnvelope","installationId","installation","id","recordDeliveryOnly","tx","provider","deliveryId","outcome","String","warn","connection","getIntegrationConnectionById","connectionId","lifecycleStatus","logContext","workspaceId","info","event","validated","issues","error","publishGithubEnvelopeOnly","publishIntegrationEventReceived","publishGithubPush","eventPayload","rawPayload","eventName","result","deleteInstallationTokenSecretBestEffort","deleteInstallationTokenSecret","shouldDeleteInstallationTokenSecret","source","slug","connectionName","displayName","receivedAt","Date","toISOString","published","ref","stripRefsHeads","defaultBranch","repository","default_branch","push","externalRepositoryId","headCommitSha","isDefaultBranch","publishSourcePush","startsWith","slice","length"],"mappings":"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"}
|