@shipfox/api-integration-linear 5.0.0 → 6.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.
@@ -1,14 +1,12 @@
1
- import { Buffer } from 'node:buffer';
2
- import { LINEAR_PROVIDER, linearWebhookBaseEnvelopeSchema } from '@shipfox/api-integration-linear-dto';
3
- import { defineRoute, rawBodyPlugin, verifyHexHmacSignature, WEBHOOK_BODY_LIMIT } from '@shipfox/node-fastify';
4
- import { logger } from '@shipfox/node-opentelemetry';
5
- import { config } from '#config.js';
6
- import { handleLinearWebhook } from '#core/webhook.js';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createStoredWebhookRequest, WEBHOOK_MAX_RAW_BODY_BYTES } from '@shipfox/api-integration-core-dto';
3
+ import { ClientError, defineRoute, rawBodyPlugin, WEBHOOK_BODY_LIMIT } from '@shipfox/node-fastify';
4
+ import { createLinearWebhookProcessor } from '#core/webhook-processor.js';
7
5
  const DELIVERY_HEADER = 'linear-delivery';
8
6
  const EVENT_HEADER = 'linear-event';
9
7
  const SIGNATURE_HEADER = 'linear-signature';
10
- const WEBHOOK_REPLAY_WINDOW_MS = 60_000;
11
8
  export function createLinearWebhookRoutes(options) {
9
+ const processor = options.processor ?? createLinearWebhookProcessor(options);
12
10
  const webhookRoute = defineRoute({
13
11
  method: 'POST',
14
12
  path: '/',
@@ -18,6 +16,7 @@ export function createLinearWebhookRoutes(options) {
18
16
  bodyLimit: WEBHOOK_BODY_LIMIT
19
17
  },
20
18
  handler: async (request, reply)=>{
19
+ const receivedAt = new Date().toISOString();
21
20
  const deliveryHeader = request.headers[DELIVERY_HEADER];
22
21
  const event = request.headers[EVENT_HEADER];
23
22
  const signature = request.headers[SIGNATURE_HEADER];
@@ -40,76 +39,18 @@ export function createLinearWebhookRoutes(options) {
40
39
  };
41
40
  }
42
41
  const body = request.body;
43
- if (!Buffer.isBuffer(body)) {
42
+ if (!(body instanceof Uint8Array)) {
44
43
  reply.code(400);
45
44
  return {
46
45
  error: 'expected raw JSON body'
47
46
  };
48
47
  }
49
- const rawBody = body.toString('utf8');
50
- if (!verifyHexHmacSignature({
51
- rawBody,
52
- signature,
53
- secret: config.LINEAR_WEBHOOK_SIGNING_SECRET
54
- })) {
55
- reply.code(401);
56
- return {
57
- error: 'invalid signature'
58
- };
59
- }
60
- let parsedJson;
61
- try {
62
- parsedJson = JSON.parse(rawBody);
63
- } catch (error) {
64
- logger().warn({
65
- deliveryId: deliveryHeader,
66
- err: error
67
- }, 'linear webhook payload JSON parse failed');
68
- reply.code(400);
69
- return {
70
- error: 'malformed JSON'
71
- };
72
- }
73
- const payload = linearWebhookBaseEnvelopeSchema.safeParse(parsedJson);
74
- if (!payload.success) {
75
- logger().warn({
76
- deliveryId: deliveryHeader,
77
- issues: payload.error.issues
78
- }, 'linear webhook envelope failed schema validation');
79
- await recordSignedDeliveryOnly(options, deliveryHeader);
80
- reply.code(200);
81
- return null;
82
- }
83
- if (Math.abs(Date.now() - payload.data.webhookTimestamp) > WEBHOOK_REPLAY_WINDOW_MS) {
84
- reply.code(401);
85
- return {
86
- error: 'stale webhook timestamp'
87
- };
88
- }
89
- if (event !== payload.data.type) {
90
- logger().warn({
91
- deliveryId: deliveryHeader,
92
- event,
93
- type: payload.data.type
94
- }, 'linear webhook event header did not match payload type');
95
- await recordSignedDeliveryOnly(options, deliveryHeader);
96
- reply.code(200);
97
- return null;
98
- }
99
- await options.coreDb().transaction(async (tx)=>{
100
- await handleLinearWebhook({
101
- tx,
102
- // Linear documents this header as the delivery UUID; the signed timestamp is per-send.
103
- deliveryId: deliveryHeader,
104
- payload: payload.data,
105
- rawPayload: parsedJson,
106
- publishIntegrationEventReceived: options.publishIntegrationEventReceived,
107
- recordDeliveryOnly: options.recordDeliveryOnly,
108
- getIntegrationConnectionById: options.getIntegrationConnectionById
109
- });
110
- });
111
- reply.code(200);
112
- return null;
48
+ const result = await processor.process(createLinearStoredWebhookRequest({
49
+ body,
50
+ headers: request.headers,
51
+ rawQueryString: request.raw.url?.split('?')[1] ?? ''
52
+ }, receivedAt));
53
+ return sendLinearWebhookResponse(reply, result);
113
54
  }
114
55
  });
115
56
  return {
@@ -123,14 +64,69 @@ export function createLinearWebhookRoutes(options) {
123
64
  ]
124
65
  };
125
66
  }
126
- async function recordSignedDeliveryOnly(options, deliveryId) {
127
- await options.coreDb().transaction(async (tx)=>{
128
- await options.recordDeliveryOnly({
129
- tx,
130
- provider: LINEAR_PROVIDER,
131
- deliveryId
67
+ function createLinearStoredWebhookRequest(input, receivedAt) {
68
+ if (input.body.byteLength > WEBHOOK_MAX_RAW_BODY_BYTES) {
69
+ throw new ClientError('Webhook request body is too large', 'body-too-large', {
70
+ status: 413
132
71
  });
133
- });
72
+ }
73
+ try {
74
+ return createStoredWebhookRequest({
75
+ requestId: randomUUID(),
76
+ routeId: 'linear',
77
+ receivedAt,
78
+ rawQueryString: input.rawQueryString,
79
+ headers: linearWebhookHeaders(input.headers),
80
+ body: input.body
81
+ });
82
+ } catch (error) {
83
+ throw new ClientError('Webhook request metadata is invalid', 'invalid-webhook-request', {
84
+ cause: error
85
+ });
86
+ }
87
+ }
88
+ function linearWebhookHeaders(headers) {
89
+ return Object.fromEntries([
90
+ 'content-type',
91
+ DELIVERY_HEADER,
92
+ EVENT_HEADER,
93
+ SIGNATURE_HEADER,
94
+ 'linear-timestamp'
95
+ ].flatMap((name)=>{
96
+ const value = headers[name];
97
+ return typeof value === 'string' ? [
98
+ [
99
+ name,
100
+ value
101
+ ]
102
+ ] : [];
103
+ }));
104
+ }
105
+ function sendLinearWebhookResponse(reply, result) {
106
+ if (result.outcome !== 'discarded') {
107
+ reply.code(200);
108
+ return null;
109
+ }
110
+ if (result.reason === 'invalid_signature') {
111
+ reply.code(401);
112
+ return {
113
+ error: 'invalid signature'
114
+ };
115
+ }
116
+ if (result.reason === 'stale_at_receipt') {
117
+ reply.code(401);
118
+ return {
119
+ error: 'stale webhook timestamp'
120
+ };
121
+ }
122
+ if (result.reason === 'malformed_payload') {
123
+ reply.code(400);
124
+ return {
125
+ error: 'malformed JSON'
126
+ };
127
+ }
128
+ reply.code(200);
129
+ return null;
134
130
  }
135
131
 
136
132
  //# sourceMappingURL=webhooks.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport type {\n GetIntegrationConnectionByIdFn,\n IntegrationTx,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {\n LINEAR_PROVIDER,\n linearWebhookBaseEnvelopeSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport {\n defineRoute,\n type RouteGroup,\n rawBodyPlugin,\n verifyHexHmacSignature,\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 {handleLinearWebhook} from '#core/webhook.js';\n\nconst DELIVERY_HEADER = 'linear-delivery';\nconst EVENT_HEADER = 'linear-event';\nconst SIGNATURE_HEADER = 'linear-signature';\nconst WEBHOOK_REPLAY_WINDOW_MS = 60_000;\n\nexport interface CreateLinearWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport function createLinearWebhookRoutes(options: CreateLinearWebhookRoutesOptions): RouteGroup {\n const webhookRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'Linear webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const deliveryHeader = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n\n if (typeof deliveryHeader !== 'string' || !deliveryHeader) {\n reply.code(400);\n return {error: 'missing Linear-Delivery header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing Linear-Event header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing Linear-Signature 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 if (\n !verifyHexHmacSignature({\n rawBody,\n signature,\n secret: config.LINEAR_WEBHOOK_SIGNING_SECRET,\n })\n ) {\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(\n {deliveryId: deliveryHeader, err: error},\n 'linear webhook payload JSON parse failed',\n );\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n\n const payload = linearWebhookBaseEnvelopeSchema.safeParse(parsedJson);\n if (!payload.success) {\n logger().warn(\n {deliveryId: deliveryHeader, issues: payload.error.issues},\n 'linear webhook envelope failed schema validation',\n );\n await recordSignedDeliveryOnly(options, deliveryHeader);\n reply.code(200);\n return null;\n }\n\n if (Math.abs(Date.now() - payload.data.webhookTimestamp) > WEBHOOK_REPLAY_WINDOW_MS) {\n reply.code(401);\n return {error: 'stale webhook timestamp'};\n }\n\n if (event !== payload.data.type) {\n logger().warn(\n {deliveryId: deliveryHeader, event, type: payload.data.type},\n 'linear webhook event header did not match payload type',\n );\n await recordSignedDeliveryOnly(options, deliveryHeader);\n reply.code(200);\n return null;\n }\n\n await options.coreDb().transaction(async (tx) => {\n await handleLinearWebhook({\n tx,\n // Linear documents this header as the delivery UUID; the signed timestamp is per-send.\n deliveryId: deliveryHeader,\n payload: payload.data,\n rawPayload: parsedJson,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n });\n });\n\n reply.code(200);\n return null;\n },\n });\n\n return {\n prefix: '/webhooks/integrations/linear',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [webhookRoute],\n };\n}\n\nasync function recordSignedDeliveryOnly(\n options: Pick<CreateLinearWebhookRoutesOptions, 'coreDb' | 'recordDeliveryOnly'>,\n deliveryId: string,\n): Promise<void> {\n await options.coreDb().transaction(async (tx: IntegrationTx) => {\n await options.recordDeliveryOnly({\n tx,\n provider: LINEAR_PROVIDER,\n deliveryId,\n });\n });\n}\n"],"names":["Buffer","LINEAR_PROVIDER","linearWebhookBaseEnvelopeSchema","defineRoute","rawBodyPlugin","verifyHexHmacSignature","WEBHOOK_BODY_LIMIT","logger","config","handleLinearWebhook","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","WEBHOOK_REPLAY_WINDOW_MS","createLinearWebhookRoutes","options","webhookRoute","method","path","auth","description","bodyLimit","handler","request","reply","deliveryHeader","headers","event","signature","code","error","body","isBuffer","rawBody","toString","secret","LINEAR_WEBHOOK_SIGNING_SECRET","parsedJson","JSON","parse","warn","deliveryId","err","payload","safeParse","success","issues","recordSignedDeliveryOnly","Math","abs","Date","now","data","webhookTimestamp","type","coreDb","transaction","tx","rawPayload","publishIntegrationEventReceived","recordDeliveryOnly","getIntegrationConnectionById","prefix","plugins","routes","provider"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport type {\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n StoredWebhookRequest,\n WebhookProcessingResult,\n} from '@shipfox/api-integration-core-dto';\nimport {\n createStoredWebhookRequest,\n WEBHOOK_MAX_RAW_BODY_BYTES,\n} from '@shipfox/api-integration-core-dto';\nimport {\n ClientError,\n defineRoute,\n type RouteGroup,\n rawBodyPlugin,\n WEBHOOK_BODY_LIMIT,\n} from '@shipfox/node-fastify';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {\n createLinearWebhookProcessor,\n type LinearWebhookProcessor,\n} from '#core/webhook-processor.js';\n\nconst DELIVERY_HEADER = 'linear-delivery';\nconst EVENT_HEADER = 'linear-event';\nconst SIGNATURE_HEADER = 'linear-signature';\n\nexport interface CreateLinearWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n processor?: LinearWebhookProcessor | undefined;\n}\n\nexport function createLinearWebhookRoutes(options: CreateLinearWebhookRoutesOptions): RouteGroup {\n const processor = options.processor ?? createLinearWebhookProcessor(options);\n const webhookRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'Linear webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const receivedAt = new Date().toISOString();\n const deliveryHeader = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n\n if (typeof deliveryHeader !== 'string' || !deliveryHeader) {\n reply.code(400);\n return {error: 'missing Linear-Delivery header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing Linear-Event header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing Linear-Signature header'};\n }\n\n const body = request.body;\n if (!(body instanceof Uint8Array)) {\n reply.code(400);\n return {error: 'expected raw JSON body'};\n }\n const result = await processor.process(\n createLinearStoredWebhookRequest(\n {\n body,\n headers: request.headers,\n rawQueryString: request.raw.url?.split('?')[1] ?? '',\n },\n receivedAt,\n ),\n );\n\n return sendLinearWebhookResponse(reply, result);\n },\n });\n\n return {\n prefix: '/webhooks/integrations/linear',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [webhookRoute],\n };\n}\n\nfunction createLinearStoredWebhookRequest(\n input: {\n body: Uint8Array;\n headers: Record<string, string | string[] | undefined>;\n rawQueryString: string;\n },\n receivedAt: string,\n): StoredWebhookRequest {\n if (input.body.byteLength > WEBHOOK_MAX_RAW_BODY_BYTES) {\n throw new ClientError('Webhook request body is too large', 'body-too-large', {status: 413});\n }\n\n try {\n return createStoredWebhookRequest({\n requestId: randomUUID(),\n routeId: 'linear',\n receivedAt,\n rawQueryString: input.rawQueryString,\n headers: linearWebhookHeaders(input.headers),\n body: input.body,\n });\n } catch (error) {\n throw new ClientError('Webhook request metadata is invalid', 'invalid-webhook-request', {\n cause: error,\n });\n }\n}\n\nfunction linearWebhookHeaders(headers: Record<string, string | string[] | undefined>) {\n return Object.fromEntries(\n ['content-type', DELIVERY_HEADER, EVENT_HEADER, SIGNATURE_HEADER, 'linear-timestamp'].flatMap(\n (name) => {\n const value = headers[name];\n return typeof value === 'string' ? [[name, value]] : [];\n },\n ),\n );\n}\n\nfunction sendLinearWebhookResponse(\n reply: {code(statusCode: number): void},\n result: WebhookProcessingResult,\n) {\n if (result.outcome !== 'discarded') {\n reply.code(200);\n return null;\n }\n\n if (result.reason === 'invalid_signature') {\n reply.code(401);\n return {error: 'invalid signature'};\n }\n if (result.reason === 'stale_at_receipt') {\n reply.code(401);\n return {error: 'stale webhook timestamp'};\n }\n if (result.reason === 'malformed_payload') {\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n\n reply.code(200);\n return null;\n}\n"],"names":["randomUUID","createStoredWebhookRequest","WEBHOOK_MAX_RAW_BODY_BYTES","ClientError","defineRoute","rawBodyPlugin","WEBHOOK_BODY_LIMIT","createLinearWebhookProcessor","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createLinearWebhookRoutes","options","processor","webhookRoute","method","path","auth","description","bodyLimit","handler","request","reply","receivedAt","Date","toISOString","deliveryHeader","headers","event","signature","code","error","body","Uint8Array","result","process","createLinearStoredWebhookRequest","rawQueryString","raw","url","split","sendLinearWebhookResponse","prefix","plugins","routes","input","byteLength","status","requestId","routeId","linearWebhookHeaders","cause","Object","fromEntries","flatMap","name","value","outcome","reason"],"mappings":"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"}