@shipfox/api-integration-slack 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.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +35 -0
  3. package/dist/core/signature.d.ts +2 -1
  4. package/dist/core/signature.d.ts.map +1 -1
  5. package/dist/core/signature.js +10 -6
  6. package/dist/core/signature.js.map +1 -1
  7. package/dist/core/webhook-processor.d.ts +15 -0
  8. package/dist/core/webhook-processor.d.ts.map +1 -0
  9. package/dist/core/webhook-processor.js +127 -0
  10. package/dist/core/webhook-processor.js.map +1 -0
  11. package/dist/index.d.ts +7 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +24 -6
  14. package/dist/index.js.map +1 -1
  15. package/dist/presentation/routes/errors.d.ts.map +1 -1
  16. package/dist/presentation/routes/errors.js +22 -0
  17. package/dist/presentation/routes/errors.js.map +1 -1
  18. package/dist/presentation/routes/install.d.ts +6 -1
  19. package/dist/presentation/routes/install.d.ts.map +1 -1
  20. package/dist/presentation/routes/install.js +6 -4
  21. package/dist/presentation/routes/install.js.map +1 -1
  22. package/dist/presentation/routes/webhooks.d.ts +2 -0
  23. package/dist/presentation/routes/webhooks.d.ts.map +1 -1
  24. package/dist/presentation/routes/webhooks.js +94 -93
  25. package/dist/presentation/routes/webhooks.js.map +1 -1
  26. package/dist/tsconfig.test.tsbuildinfo +1 -1
  27. package/package.json +10 -10
  28. package/src/core/disconnect.test.ts +47 -0
  29. package/src/core/signature.test.ts +3 -3
  30. package/src/core/signature.ts +19 -9
  31. package/src/core/tokens.test.ts +16 -2
  32. package/src/core/webhook-processor.test.ts +106 -0
  33. package/src/core/webhook-processor.ts +152 -0
  34. package/src/index.ts +19 -3
  35. package/src/presentation/routes/errors.test.ts +25 -0
  36. package/src/presentation/routes/errors.ts +20 -0
  37. package/src/presentation/routes/install.test.ts +2 -4
  38. package/src/presentation/routes/install.ts +18 -3
  39. package/src/presentation/routes/webhooks.test.ts +18 -0
  40. package/src/presentation/routes/webhooks.ts +125 -98
  41. package/test/globalSetup.ts +0 -9
  42. package/tsconfig.build.tsbuildinfo +1 -1
  43. package/turbo.json +1 -1
  44. package/test/api-secrets.d.ts +0 -26
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 24 files with swc (243.83ms)
2
+ Successfully compiled: 25 files with swc (476.32ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @shipfox/api-integration-slack
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 40a6e0f: Adds shared processing for signed Slack event and command webhook deliveries.
8
+ - f262539: Adds a composed webhook processor and optional provider-neutral delivery source for hosted API runtimes.
9
+ - a42b575: Exposes Secrets through its inter-module contract and migrates Agent, integrations, and Workflows consumers.
10
+
11
+ ### Patch Changes
12
+
13
+ - 3bb4e26: Fixes composed webhook processing and exposes Slack URL-verification responses through the shared contract.
14
+ - f73da5d: Enforces bounded API context imports and routes inter-module consumers through producer contracts.
15
+ - 326f4c0: Exposes Workspaces inter-module operations and moves Auth and OAuth providers onto injected clients.
16
+ - 1820feb: Adds Slack Settings installation and callback recovery while returning stable workspace access errors.
17
+ - Updated dependencies [0bb82a4]
18
+ - Updated dependencies [7ac43a4]
19
+ - Updated dependencies [f262539]
20
+ - Updated dependencies [3bb4e26]
21
+ - Updated dependencies [1b79cda]
22
+ - Updated dependencies [c2db8c3]
23
+ - Updated dependencies [8bdc149]
24
+ - Updated dependencies [b00ed29]
25
+ - Updated dependencies [8aa7cd3]
26
+ - Updated dependencies [326f4c0]
27
+ - Updated dependencies [4a91956]
28
+ - Updated dependencies [81f9544]
29
+ - Updated dependencies [4604a06]
30
+ - @shipfox/api-integration-core-dto@6.0.0
31
+ - @shipfox/node-drizzle@0.3.2
32
+ - @shipfox/api-workspaces-dto@6.0.0
33
+ - @shipfox/api-auth-context@6.0.0
34
+ - @shipfox/node-fastify@0.2.4
35
+ - @shipfox/inter-module@0.2.0
36
+ - @shipfox/api-integration-slack-dto@6.0.0
37
+
3
38
  ## 5.0.0
4
39
 
5
40
  ### Minor Changes
@@ -2,9 +2,10 @@ export interface VerifySlackSignatureParams {
2
2
  signingSecret: string;
3
3
  signature: string | undefined;
4
4
  timestamp: string | undefined;
5
- rawBody: string;
5
+ rawBody: Uint8Array;
6
6
  now?: number | undefined;
7
7
  replayWindowMs?: number | undefined;
8
8
  }
9
+ export declare function isSlackTimestampWithinReplayWindow(timestamp: string, now?: number, replayWindowMs?: number): boolean;
9
10
  export declare function verifySlackSignature({ signingSecret, signature, timestamp, rawBody, now, replayWindowMs, }: VerifySlackSignatureParams): boolean;
10
11
  //# sourceMappingURL=signature.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/core/signature.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,SAAS,EACT,SAAS,EACT,OAAO,EACP,GAAgB,EAChB,cAAwB,GACzB,EAAE,0BAA0B,GAAG,OAAO,CAoBtC"}
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/core/signature.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,UAAU,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,MAAM,EACjB,GAAG,SAAa,EAChB,cAAc,SAAU,GACvB,OAAO,CAQT;AAED,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,SAAS,EACT,SAAS,EACT,OAAO,EACP,GAAgB,EAChB,cAAwB,GACzB,EAAE,0BAA0B,GAAG,OAAO,CAgBtC"}
@@ -1,15 +1,19 @@
1
1
  import { createHmac, timingSafeEqual } from 'node:crypto';
2
2
  const unixTimestampPattern = /^\d+$/;
3
- export function verifySlackSignature({ signingSecret, signature, timestamp, rawBody, now = Date.now(), replayWindowMs = 300_000 }) {
4
- if (!signingSecret || !signature || !timestamp || !unixTimestampPattern.test(timestamp)) {
5
- return false;
6
- }
3
+ export function isSlackTimestampWithinReplayWindow(timestamp, now = Date.now(), replayWindowMs = 300_000) {
4
+ if (!unixTimestampPattern.test(timestamp)) return false;
7
5
  const timestampSeconds = Number(timestamp);
8
6
  if (!Number.isSafeInteger(timestampSeconds)) return false;
9
7
  const timestampMilliseconds = timestampSeconds * 1000;
10
8
  if (!Number.isSafeInteger(timestampMilliseconds)) return false;
11
- if (Math.abs(now - timestampMilliseconds) > replayWindowMs) return false;
12
- const expected = `v0=${createHmac('sha256', signingSecret).update(`v0:${timestamp}:${rawBody}`).digest('hex')}`;
9
+ return Math.abs(now - timestampMilliseconds) <= replayWindowMs;
10
+ }
11
+ export function verifySlackSignature({ signingSecret, signature, timestamp, rawBody, now = Date.now(), replayWindowMs = 300_000 }) {
12
+ if (!signingSecret || !signature || !timestamp) {
13
+ return false;
14
+ }
15
+ if (!isSlackTimestampWithinReplayWindow(timestamp, now, replayWindowMs)) return false;
16
+ const expected = `v0=${createHmac('sha256', signingSecret).update(`v0:${timestamp}:`).update(rawBody).digest('hex')}`;
13
17
  const expectedBuffer = Buffer.from(expected, 'utf8');
14
18
  const receivedBuffer = Buffer.from(signature, 'utf8');
15
19
  return expectedBuffer.length === receivedBuffer.length && timingSafeEqual(expectedBuffer, receivedBuffer);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/signature.ts"],"sourcesContent":["import {createHmac, timingSafeEqual} from 'node:crypto';\n\nconst unixTimestampPattern = /^\\d+$/;\n\nexport interface VerifySlackSignatureParams {\n signingSecret: string;\n signature: string | undefined;\n timestamp: string | undefined;\n rawBody: string;\n now?: number | undefined;\n replayWindowMs?: number | undefined;\n}\n\nexport function verifySlackSignature({\n signingSecret,\n signature,\n timestamp,\n rawBody,\n now = Date.now(),\n replayWindowMs = 300_000,\n}: VerifySlackSignatureParams): boolean {\n if (!signingSecret || !signature || !timestamp || !unixTimestampPattern.test(timestamp)) {\n return false;\n }\n\n const timestampSeconds = Number(timestamp);\n if (!Number.isSafeInteger(timestampSeconds)) return false;\n const timestampMilliseconds = timestampSeconds * 1000;\n if (!Number.isSafeInteger(timestampMilliseconds)) return false;\n if (Math.abs(now - timestampMilliseconds) > replayWindowMs) return false;\n\n const expected = `v0=${createHmac('sha256', signingSecret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest('hex')}`;\n const expectedBuffer = Buffer.from(expected, 'utf8');\n const receivedBuffer = Buffer.from(signature, 'utf8');\n return (\n expectedBuffer.length === receivedBuffer.length &&\n timingSafeEqual(expectedBuffer, receivedBuffer)\n );\n}\n"],"names":["createHmac","timingSafeEqual","unixTimestampPattern","verifySlackSignature","signingSecret","signature","timestamp","rawBody","now","Date","replayWindowMs","test","timestampSeconds","Number","isSafeInteger","timestampMilliseconds","Math","abs","expected","update","digest","expectedBuffer","Buffer","from","receivedBuffer","length"],"mappings":"AAAA,SAAQA,UAAU,EAAEC,eAAe,QAAO,cAAc;AAExD,MAAMC,uBAAuB;AAW7B,OAAO,SAASC,qBAAqB,EACnCC,aAAa,EACbC,SAAS,EACTC,SAAS,EACTC,OAAO,EACPC,MAAMC,KAAKD,GAAG,EAAE,EAChBE,iBAAiB,OAAO,EACG;IAC3B,IAAI,CAACN,iBAAiB,CAACC,aAAa,CAACC,aAAa,CAACJ,qBAAqBS,IAAI,CAACL,YAAY;QACvF,OAAO;IACT;IAEA,MAAMM,mBAAmBC,OAAOP;IAChC,IAAI,CAACO,OAAOC,aAAa,CAACF,mBAAmB,OAAO;IACpD,MAAMG,wBAAwBH,mBAAmB;IACjD,IAAI,CAACC,OAAOC,aAAa,CAACC,wBAAwB,OAAO;IACzD,IAAIC,KAAKC,GAAG,CAACT,MAAMO,yBAAyBL,gBAAgB,OAAO;IAEnE,MAAMQ,WAAW,CAAC,GAAG,EAAElB,WAAW,UAAUI,eACzCe,MAAM,CAAC,CAAC,GAAG,EAAEb,UAAU,CAAC,EAAEC,SAAS,EACnCa,MAAM,CAAC,QAAQ;IAClB,MAAMC,iBAAiBC,OAAOC,IAAI,CAACL,UAAU;IAC7C,MAAMM,iBAAiBF,OAAOC,IAAI,CAAClB,WAAW;IAC9C,OACEgB,eAAeI,MAAM,KAAKD,eAAeC,MAAM,IAC/CxB,gBAAgBoB,gBAAgBG;AAEpC"}
1
+ {"version":3,"sources":["../../src/core/signature.ts"],"sourcesContent":["import {createHmac, timingSafeEqual} from 'node:crypto';\n\nconst unixTimestampPattern = /^\\d+$/;\n\nexport interface VerifySlackSignatureParams {\n signingSecret: string;\n signature: string | undefined;\n timestamp: string | undefined;\n rawBody: Uint8Array;\n now?: number | undefined;\n replayWindowMs?: number | undefined;\n}\n\nexport function isSlackTimestampWithinReplayWindow(\n timestamp: string,\n now = Date.now(),\n replayWindowMs = 300_000,\n): boolean {\n if (!unixTimestampPattern.test(timestamp)) return false;\n\n const timestampSeconds = Number(timestamp);\n if (!Number.isSafeInteger(timestampSeconds)) return false;\n const timestampMilliseconds = timestampSeconds * 1000;\n if (!Number.isSafeInteger(timestampMilliseconds)) return false;\n return Math.abs(now - timestampMilliseconds) <= replayWindowMs;\n}\n\nexport function verifySlackSignature({\n signingSecret,\n signature,\n timestamp,\n rawBody,\n now = Date.now(),\n replayWindowMs = 300_000,\n}: VerifySlackSignatureParams): boolean {\n if (!signingSecret || !signature || !timestamp) {\n return false;\n }\n if (!isSlackTimestampWithinReplayWindow(timestamp, now, replayWindowMs)) return false;\n\n const expected = `v0=${createHmac('sha256', signingSecret)\n .update(`v0:${timestamp}:`)\n .update(rawBody)\n .digest('hex')}`;\n const expectedBuffer = Buffer.from(expected, 'utf8');\n const receivedBuffer = Buffer.from(signature, 'utf8');\n return (\n expectedBuffer.length === receivedBuffer.length &&\n timingSafeEqual(expectedBuffer, receivedBuffer)\n );\n}\n"],"names":["createHmac","timingSafeEqual","unixTimestampPattern","isSlackTimestampWithinReplayWindow","timestamp","now","Date","replayWindowMs","test","timestampSeconds","Number","isSafeInteger","timestampMilliseconds","Math","abs","verifySlackSignature","signingSecret","signature","rawBody","expected","update","digest","expectedBuffer","Buffer","from","receivedBuffer","length"],"mappings":"AAAA,SAAQA,UAAU,EAAEC,eAAe,QAAO,cAAc;AAExD,MAAMC,uBAAuB;AAW7B,OAAO,SAASC,mCACdC,SAAiB,EACjBC,MAAMC,KAAKD,GAAG,EAAE,EAChBE,iBAAiB,OAAO;IAExB,IAAI,CAACL,qBAAqBM,IAAI,CAACJ,YAAY,OAAO;IAElD,MAAMK,mBAAmBC,OAAON;IAChC,IAAI,CAACM,OAAOC,aAAa,CAACF,mBAAmB,OAAO;IACpD,MAAMG,wBAAwBH,mBAAmB;IACjD,IAAI,CAACC,OAAOC,aAAa,CAACC,wBAAwB,OAAO;IACzD,OAAOC,KAAKC,GAAG,CAACT,MAAMO,0BAA0BL;AAClD;AAEA,OAAO,SAASQ,qBAAqB,EACnCC,aAAa,EACbC,SAAS,EACTb,SAAS,EACTc,OAAO,EACPb,MAAMC,KAAKD,GAAG,EAAE,EAChBE,iBAAiB,OAAO,EACG;IAC3B,IAAI,CAACS,iBAAiB,CAACC,aAAa,CAACb,WAAW;QAC9C,OAAO;IACT;IACA,IAAI,CAACD,mCAAmCC,WAAWC,KAAKE,iBAAiB,OAAO;IAEhF,MAAMY,WAAW,CAAC,GAAG,EAAEnB,WAAW,UAAUgB,eACzCI,MAAM,CAAC,CAAC,GAAG,EAAEhB,UAAU,CAAC,CAAC,EACzBgB,MAAM,CAACF,SACPG,MAAM,CAAC,QAAQ;IAClB,MAAMC,iBAAiBC,OAAOC,IAAI,CAACL,UAAU;IAC7C,MAAMM,iBAAiBF,OAAOC,IAAI,CAACP,WAAW;IAC9C,OACEK,eAAeI,MAAM,KAAKD,eAAeC,MAAM,IAC/CzB,gBAAgBqB,gBAAgBG;AAEpC"}
@@ -0,0 +1,15 @@
1
+ import type { ClaimWebhookDeliveryFn, GetIntegrationConnectionByIdFn, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn, StoredWebhookRequest, WebhookProcessingResult } from '@shipfox/api-integration-core-dto';
2
+ import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
+ export interface CreateSlackWebhookProcessorOptions {
4
+ coreDb: () => NodePgDatabase<Record<string, unknown>>;
5
+ claimWebhookDelivery: ClaimWebhookDeliveryFn;
6
+ publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
7
+ recordDeliveryOnly: RecordDeliveryOnlyFn;
8
+ getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
9
+ }
10
+ export type SlackWebhookProcessingResult = WebhookProcessingResult;
11
+ export interface SlackWebhookProcessor {
12
+ process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;
13
+ }
14
+ export declare function createSlackWebhookProcessor(options: CreateSlackWebhookProcessorOptions): SlackWebhookProcessor;
15
+ //# sourceMappingURL=webhook-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAC9B,iCAAiC,EACjC,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,mCAAmC,CAAC;AAO3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,oBAAoB,EAAE,sBAAsB,CAAC;IAC7C,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,qBAAqB,CAEvB"}
@@ -0,0 +1,127 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { decodeWebhookBody } from '@shipfox/api-integration-core-dto';
3
+ import { slackEventsRequestSchema, slackSlashCommandSchema } from '@shipfox/api-integration-slack-dto';
4
+ import { logger } from '@shipfox/node-opentelemetry';
5
+ import { config } from '#config.js';
6
+ import { isSlackTimestampWithinReplayWindow, verifySlackSignature } from '#core/signature.js';
7
+ import { handleSlackCommand, handleSlackEvent } from '#core/webhook.js';
8
+ const SIGNATURE_HEADER = 'x-slack-signature';
9
+ const TIMESTAMP_HEADER = 'x-slack-request-timestamp';
10
+ export function createSlackWebhookProcessor(options) {
11
+ return {
12
+ process: (request)=>processSlackWebhookRequest(options, request)
13
+ };
14
+ }
15
+ function processSlackWebhookRequest(options, request) {
16
+ if (request.route_id !== 'slack.event' && request.route_id !== 'slack.command') {
17
+ throw new Error(`Slack processor cannot process ${request.route_id} requests`);
18
+ }
19
+ const signature = request.headers[SIGNATURE_HEADER];
20
+ const timestamp = request.headers[TIMESTAMP_HEADER];
21
+ if (!signature || !timestamp) {
22
+ return Promise.resolve({
23
+ outcome: 'discarded',
24
+ reason: 'missing_required_input'
25
+ });
26
+ }
27
+ const rawBody = decodeWebhookBody(request.body);
28
+ const receiptTime = new Date(request.received_at).getTime();
29
+ if (!isSlackTimestampWithinReplayWindow(timestamp, receiptTime)) {
30
+ return Promise.resolve({
31
+ outcome: 'discarded',
32
+ reason: 'stale_at_receipt'
33
+ });
34
+ }
35
+ if (!verifySlackSignature({
36
+ signingSecret: config.SLACK_SIGNING_SECRET,
37
+ signature,
38
+ timestamp,
39
+ rawBody,
40
+ now: receiptTime
41
+ })) {
42
+ return Promise.resolve({
43
+ outcome: 'discarded',
44
+ reason: 'invalid_signature'
45
+ });
46
+ }
47
+ return request.route_id === 'slack.event' ? processSlackEvent(options, rawBody) : processSlackCommand(options, rawBody);
48
+ }
49
+ async function processSlackEvent(options, rawBody) {
50
+ let rawPayload;
51
+ try {
52
+ rawPayload = JSON.parse(Buffer.from(rawBody).toString('utf8'));
53
+ } catch (error) {
54
+ logger().warn({
55
+ err: error
56
+ }, 'slack events payload JSON parse failed');
57
+ return {
58
+ outcome: 'discarded',
59
+ reason: 'malformed_payload'
60
+ };
61
+ }
62
+ const event = slackEventsRequestSchema.safeParse(rawPayload);
63
+ if (!event.success) {
64
+ logger().warn({
65
+ issues: event.error.issues
66
+ }, 'slack events envelope failed schema validation');
67
+ return {
68
+ outcome: 'discarded',
69
+ reason: 'unsupported_event'
70
+ };
71
+ }
72
+ const envelope = event.data;
73
+ if (envelope.type === 'url_verification') {
74
+ return {
75
+ outcome: 'processed',
76
+ challenge: envelope.challenge
77
+ };
78
+ }
79
+ const result = await options.coreDb().transaction(async (tx)=>handleSlackEvent({
80
+ tx,
81
+ deliveryId: envelope.event_id,
82
+ envelope,
83
+ claimWebhookDelivery: options.claimWebhookDelivery,
84
+ publishIntegrationEventReceived: options.publishIntegrationEventReceived,
85
+ recordDeliveryOnly: options.recordDeliveryOnly,
86
+ getIntegrationConnectionById: options.getIntegrationConnectionById
87
+ }));
88
+ return toProcessingResult(result.outcome, envelope.event_id);
89
+ }
90
+ async function processSlackCommand(options, rawBody) {
91
+ const command = slackSlashCommandSchema.safeParse(Object.fromEntries(new URLSearchParams(Buffer.from(rawBody).toString('utf8'))));
92
+ if (!command.success) {
93
+ logger().warn({
94
+ issues: command.error.issues
95
+ }, 'slack command failed schema validation');
96
+ return {
97
+ outcome: 'discarded',
98
+ reason: 'unsupported_event'
99
+ };
100
+ }
101
+ const result = await options.coreDb().transaction(async (tx)=>handleSlackCommand({
102
+ tx,
103
+ deliveryId: command.data.trigger_id,
104
+ command: command.data,
105
+ publishIntegrationEventReceived: options.publishIntegrationEventReceived,
106
+ recordDeliveryOnly: options.recordDeliveryOnly,
107
+ getIntegrationConnectionById: options.getIntegrationConnectionById
108
+ }));
109
+ return toProcessingResult(result.outcome, command.data.trigger_id);
110
+ }
111
+ function toProcessingResult(outcome, deliveryId) {
112
+ if (outcome === 'published') return {
113
+ outcome: 'processed',
114
+ deliveryId
115
+ };
116
+ if (outcome === 'duplicate') return {
117
+ outcome: 'duplicate',
118
+ deliveryId
119
+ };
120
+ return {
121
+ outcome: 'discarded',
122
+ reason: outcome === 'unsupported-event' ? 'unsupported_event' : 'connection_unavailable',
123
+ deliveryId
124
+ };
125
+ }
126
+
127
+ //# sourceMappingURL=webhook-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport type {\n ClaimWebhookDeliveryFn,\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n StoredWebhookRequest,\n WebhookProcessingResult,\n} from '@shipfox/api-integration-core-dto';\nimport {decodeWebhookBody} from '@shipfox/api-integration-core-dto';\nimport {\n slackEventsRequestSchema,\n slackSlashCommandSchema,\n} from '@shipfox/api-integration-slack-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {isSlackTimestampWithinReplayWindow, verifySlackSignature} from '#core/signature.js';\nimport {handleSlackCommand, handleSlackEvent, type SlackWebhookOutcome} from '#core/webhook.js';\n\nconst SIGNATURE_HEADER = 'x-slack-signature';\nconst TIMESTAMP_HEADER = 'x-slack-request-timestamp';\n\nexport interface CreateSlackWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n claimWebhookDelivery: ClaimWebhookDeliveryFn;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type SlackWebhookProcessingResult = WebhookProcessingResult;\n\nexport interface SlackWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createSlackWebhookProcessor(\n options: CreateSlackWebhookProcessorOptions,\n): SlackWebhookProcessor {\n return {process: (request) => processSlackWebhookRequest(options, request)};\n}\n\nfunction processSlackWebhookRequest(\n options: CreateSlackWebhookProcessorOptions,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'slack.event' && request.route_id !== 'slack.command') {\n throw new Error(`Slack processor cannot process ${request.route_id} requests`);\n }\n\n const signature = request.headers[SIGNATURE_HEADER];\n const timestamp = request.headers[TIMESTAMP_HEADER];\n if (!signature || !timestamp) {\n return Promise.resolve({outcome: 'discarded', reason: 'missing_required_input'});\n }\n\n const rawBody = decodeWebhookBody(request.body);\n const receiptTime = new Date(request.received_at).getTime();\n if (!isSlackTimestampWithinReplayWindow(timestamp, receiptTime)) {\n return Promise.resolve({outcome: 'discarded', reason: 'stale_at_receipt'});\n }\n if (\n !verifySlackSignature({\n signingSecret: config.SLACK_SIGNING_SECRET,\n signature,\n timestamp,\n rawBody,\n now: receiptTime,\n })\n ) {\n return Promise.resolve({outcome: 'discarded', reason: 'invalid_signature'});\n }\n\n return request.route_id === 'slack.event'\n ? processSlackEvent(options, rawBody)\n : processSlackCommand(options, rawBody);\n}\n\nasync function processSlackEvent(\n options: CreateSlackWebhookProcessorOptions,\n rawBody: Uint8Array,\n): Promise<WebhookProcessingResult> {\n let rawPayload: unknown;\n try {\n rawPayload = JSON.parse(Buffer.from(rawBody).toString('utf8'));\n } catch (error) {\n logger().warn({err: error}, 'slack events payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload'};\n }\n\n const event = slackEventsRequestSchema.safeParse(rawPayload);\n if (!event.success) {\n logger().warn({issues: event.error.issues}, 'slack events envelope failed schema validation');\n return {outcome: 'discarded', reason: 'unsupported_event'};\n }\n const envelope = event.data;\n if (envelope.type === 'url_verification') {\n return {outcome: 'processed', challenge: envelope.challenge};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleSlackEvent({\n tx,\n deliveryId: envelope.event_id,\n envelope,\n claimWebhookDelivery: options.claimWebhookDelivery,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return toProcessingResult(result.outcome, envelope.event_id);\n}\n\nasync function processSlackCommand(\n options: CreateSlackWebhookProcessorOptions,\n rawBody: Uint8Array,\n): Promise<WebhookProcessingResult> {\n const command = slackSlashCommandSchema.safeParse(\n Object.fromEntries(new URLSearchParams(Buffer.from(rawBody).toString('utf8'))),\n );\n if (!command.success) {\n logger().warn({issues: command.error.issues}, 'slack command failed schema validation');\n return {outcome: 'discarded', reason: 'unsupported_event'};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleSlackCommand({\n tx,\n deliveryId: command.data.trigger_id,\n command: command.data,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return toProcessingResult(result.outcome, command.data.trigger_id);\n}\n\nfunction toProcessingResult(\n outcome: SlackWebhookOutcome,\n deliveryId: string,\n): WebhookProcessingResult {\n if (outcome === 'published') return {outcome: 'processed', deliveryId};\n if (outcome === 'duplicate') return {outcome: 'duplicate', deliveryId};\n return {\n outcome: 'discarded',\n reason: outcome === 'unsupported-event' ? 'unsupported_event' : 'connection_unavailable',\n deliveryId,\n };\n}\n"],"names":["Buffer","decodeWebhookBody","slackEventsRequestSchema","slackSlashCommandSchema","logger","config","isSlackTimestampWithinReplayWindow","verifySlackSignature","handleSlackCommand","handleSlackEvent","SIGNATURE_HEADER","TIMESTAMP_HEADER","createSlackWebhookProcessor","options","process","request","processSlackWebhookRequest","route_id","Error","signature","headers","timestamp","Promise","resolve","outcome","reason","rawBody","body","receiptTime","Date","received_at","getTime","signingSecret","SLACK_SIGNING_SECRET","now","processSlackEvent","processSlackCommand","rawPayload","JSON","parse","from","toString","error","warn","err","event","safeParse","success","issues","envelope","data","type","challenge","result","coreDb","transaction","tx","deliveryId","event_id","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","getIntegrationConnectionById","toProcessingResult","command","Object","fromEntries","URLSearchParams","trigger_id"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -23,13 +23,15 @@ export type { ConnectSlackInstallationInput, HandleSlackCallbackParams } from '#
23
23
  export { handleSlackCallback, handleSlackOAuthCallbackError } from '#core/install.js';
24
24
  export { assertSlackAuthorizationScopes, formatSlackBotScopes, SLACK_BOT_SCOPES, } from '#core/scopes.js';
25
25
  export type { VerifySlackSignatureParams } from '#core/signature.js';
26
- export { verifySlackSignature } from '#core/signature.js';
26
+ export { isSlackTimestampWithinReplayWindow, verifySlackSignature } from '#core/signature.js';
27
27
  export type { SlackInstallStateClaims } from '#core/state.js';
28
28
  export { signSlackInstallState, verifySlackInstallState } from '#core/state.js';
29
29
  export type { CreateSlackTokenStoreParams, GetSlackAccessTokenParams, SlackConnectionResolverResult, SlackSecretsStore, SlackTokenStore, StoreSlackTokensParams, } from '#core/tokens.js';
30
30
  export { createSlackTokenStore, slackSecretsNamespace } from '#core/tokens.js';
31
31
  export type { HandleSlackCommandParams, HandleSlackEventParams, SlackWebhookOutcome, } from '#core/webhook.js';
32
32
  export { handleSlackCommand, handleSlackEvent, isSelfAuthoredSlackEvent } from '#core/webhook.js';
33
+ export type { CreateSlackWebhookProcessorOptions, SlackWebhookProcessingResult, SlackWebhookProcessor, } from '#core/webhook-processor.js';
34
+ export { createSlackWebhookProcessor } from '#core/webhook-processor.js';
33
35
  export type { SlackInstallation, SlackInstallationStatus, UpsertSlackInstallationParams, } from '#db/installations.js';
34
36
  export { deleteSlackInstallationByConnectionId, getSlackInstallationByConnectionId, getSlackInstallationByTeamId, markSlackInstallationRevoked, upsertSlackInstallation, } from '#db/installations.js';
35
37
  export { type CreateSlackE2eRoutesOptions, createSlackE2eRoutes, } from '#presentation/e2eRoutes/index.js';
@@ -56,6 +58,10 @@ export interface CreateSlackIntegrationProviderOptions {
56
58
  }
57
59
  export declare function createSlackIntegrationProvider(options?: CreateSlackIntegrationProviderOptions): {
58
60
  routes: RouteGroup[];
61
+ webhookProcessors: {
62
+ routeIds: readonly ["slack.event", "slack.command"];
63
+ processor: import("#core/webhook-processor.js").SlackWebhookProcessor;
64
+ }[] | undefined;
59
65
  deleteConnectionRecords?: (connection: {
60
66
  id: string;
61
67
  }, options: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { SLACK_PROVIDER } from '@shipfox/api-integration-slack-dto';
2
2
  import { createSlackApiClient } from '#api/client.js';
3
3
  import { config } from '#config.js';
4
4
  import { SlackAgentToolsProvider } from '#core/agent-tools-provider.js';
5
+ import { createSlackWebhookProcessor } from '#core/webhook-processor.js';
5
6
  import { closeDb, db } from '#db/db.js';
6
7
  import { getSlackInstallationByConnectionId } from '#db/installations.js';
7
8
  import { migrationsPath } from '#db/migrations.js';
@@ -14,10 +15,11 @@ export { disconnectSlackInstallation } from '#core/disconnect.js';
14
15
  export { SlackAccessTokenUnavailableError, SlackAuthorizationScopeMismatchError, SlackBotTokenMissingError, SlackConnectionAlreadyLinkedError, SlackConnectionNotFoundError, SlackEnterpriseInstallUnsupportedError, SlackInstallationAlreadyLinkedError, SlackInstallStateActorMismatchError, SlackInstallStateError, SlackIntegrationProviderError, SlackOAuthCallbackError } from '#core/errors.js';
15
16
  export { handleSlackCallback, handleSlackOAuthCallbackError } from '#core/install.js';
16
17
  export { assertSlackAuthorizationScopes, formatSlackBotScopes, SLACK_BOT_SCOPES } from '#core/scopes.js';
17
- export { verifySlackSignature } from '#core/signature.js';
18
+ export { isSlackTimestampWithinReplayWindow, verifySlackSignature } from '#core/signature.js';
18
19
  export { signSlackInstallState, verifySlackInstallState } from '#core/state.js';
19
20
  export { createSlackTokenStore, slackSecretsNamespace } from '#core/tokens.js';
20
21
  export { handleSlackCommand, handleSlackEvent, isSelfAuthoredSlackEvent } from '#core/webhook.js';
22
+ export { createSlackWebhookProcessor } from '#core/webhook-processor.js';
21
23
  export { deleteSlackInstallationByConnectionId, getSlackInstallationByConnectionId, getSlackInstallationByTeamId, markSlackInstallationRevoked, upsertSlackInstallation } from '#db/installations.js';
22
24
  export { createSlackE2eRoutes } from '#presentation/e2eRoutes/index.js';
23
25
  export { createSlackWebhookRoutes, SLACK_WEBHOOK_BODY_LIMIT, SLASH_COMMAND_ACK } from '#presentation/routes/webhooks.js';
@@ -35,8 +37,9 @@ export function createSlackIntegrationProvider(options = {}) {
35
37
  throw new Error('Slack webhook routes require every core persistence dependency');
36
38
  }
37
39
  const routes = [];
40
+ const webhookProcessor = options.routes && hasSlackWebhookRoutesOptions(options.routes) ? createSlackWebhookProcessor(options.routes) : undefined;
38
41
  if (options.routes && hasSlackInstallationRoutesOptions(options.routes)) {
39
- const { tokenStore, getExistingSlackConnection, connectSlackInstallation, disconnectSlackInstallation } = options.routes;
42
+ const { tokenStore, getExistingSlackConnection, connectSlackInstallation, disconnectSlackInstallation, requireActiveWorkspaceMembership } = options.routes;
40
43
  routes.push(createSlackIntegrationRoutes({
41
44
  slack,
42
45
  connectionCapabilities: adapters.agent_tools ? [
@@ -45,11 +48,17 @@ export function createSlackIntegrationProvider(options = {}) {
45
48
  tokenStore,
46
49
  getExistingSlackConnection,
47
50
  connectSlackInstallation,
48
- disconnectSlackInstallation
51
+ disconnectSlackInstallation,
52
+ ...requireActiveWorkspaceMembership ? {
53
+ requireActiveWorkspaceMembership
54
+ } : {}
49
55
  }));
50
56
  }
51
- if (options.routes && hasSlackWebhookRoutesOptions(options.routes)) {
52
- routes.push(...createSlackWebhookRoutes(options.routes));
57
+ if (options.routes && hasSlackWebhookRoutesOptions(options.routes) && webhookProcessor) {
58
+ routes.push(...createSlackWebhookRoutes({
59
+ ...options.routes,
60
+ processor: webhookProcessor
61
+ }));
53
62
  }
54
63
  return {
55
64
  provider: SLACK_PROVIDER,
@@ -61,7 +70,16 @@ export function createSlackIntegrationProvider(options = {}) {
61
70
  return `https://app.slack.com/client/${encodeURIComponent(installation.teamId)}`;
62
71
  },
63
72
  ...options.cleanup,
64
- routes
73
+ routes,
74
+ webhookProcessors: webhookProcessor ? [
75
+ {
76
+ routeIds: [
77
+ 'slack.event',
78
+ 'slack.command'
79
+ ],
80
+ processor: webhookProcessor
81
+ }
82
+ ] : undefined
65
83
  };
66
84
  }
67
85
  function hasSlackInstallationRoutesOptions(routes) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {SLACK_PROVIDER} from '@shipfox/api-integration-slack-dto';\nimport type {RouteGroup} from '@shipfox/node-fastify';\nimport {createSlackApiClient, type SlackApiClient} from '#api/client.js';\nimport {config} from '#config.js';\nimport {SlackAgentToolsProvider} from '#core/agent-tools-provider.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\nimport {closeDb, db} from '#db/db.js';\nimport {getSlackInstallationByConnectionId} from '#db/installations.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {\n type CreateSlackIntegrationRoutesOptions,\n createSlackIntegrationRoutes,\n} from '#presentation/routes/install.js';\nimport {\n type CreateSlackWebhookRoutesOptions,\n createSlackWebhookRoutes,\n} from '#presentation/routes/webhooks.js';\n\ntype SlackInstallationRouteOptions = Omit<\n CreateSlackIntegrationRoutesOptions,\n 'slack' | 'connectionCapabilities'\n>;\ntype SlackRouteOptions = Partial<SlackInstallationRouteOptions> &\n Partial<CreateSlackWebhookRoutesOptions>;\n\nexport type {SlackProvider} from '@shipfox/api-integration-slack-dto';\nexport type {SlackApiClient, SlackAuthorization, SlackWebApiResponse} from '#api/client.js';\nexport {createSlackApiClient} from '#api/client.js';\nexport type {\n SlackAgentToolCatalogEntry,\n SlackAgentToolId,\n SlackAgentToolRequiredScope,\n} from '#core/agent-tools.js';\nexport {\n SLACK_TOOL_METHODS,\n slackAgentToolCatalog,\n slackAgentToolSelectionCatalog,\n} from '#core/agent-tools.js';\nexport type {\n SlackAgentToolsProviderOptions,\n SlackToolCallResult,\n} from '#core/agent-tools-provider.js';\nexport {SlackAgentToolsProvider} from '#core/agent-tools-provider.js';\nexport {\n type DisconnectSlackInstallationParams,\n disconnectSlackInstallation,\n} from '#core/disconnect.js';\nexport {\n SlackAccessTokenUnavailableError,\n type SlackAccessTokenUnavailableReason,\n SlackAuthorizationScopeMismatchError,\n SlackBotTokenMissingError,\n SlackConnectionAlreadyLinkedError,\n SlackConnectionNotFoundError,\n SlackEnterpriseInstallUnsupportedError,\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackInstallStateError,\n SlackIntegrationProviderError,\n SlackOAuthCallbackError,\n} from '#core/errors.js';\nexport type {ConnectSlackInstallationInput, HandleSlackCallbackParams} from '#core/install.js';\nexport {handleSlackCallback, handleSlackOAuthCallbackError} from '#core/install.js';\nexport {\n assertSlackAuthorizationScopes,\n formatSlackBotScopes,\n SLACK_BOT_SCOPES,\n} from '#core/scopes.js';\nexport type {VerifySlackSignatureParams} from '#core/signature.js';\nexport {verifySlackSignature} from '#core/signature.js';\nexport type {SlackInstallStateClaims} from '#core/state.js';\nexport {signSlackInstallState, verifySlackInstallState} from '#core/state.js';\nexport type {\n CreateSlackTokenStoreParams,\n GetSlackAccessTokenParams,\n SlackConnectionResolverResult,\n SlackSecretsStore,\n SlackTokenStore,\n StoreSlackTokensParams,\n} from '#core/tokens.js';\nexport {createSlackTokenStore, slackSecretsNamespace} from '#core/tokens.js';\nexport type {\n HandleSlackCommandParams,\n HandleSlackEventParams,\n SlackWebhookOutcome,\n} from '#core/webhook.js';\nexport {handleSlackCommand, handleSlackEvent, isSelfAuthoredSlackEvent} from '#core/webhook.js';\nexport type {\n SlackInstallation,\n SlackInstallationStatus,\n UpsertSlackInstallationParams,\n} from '#db/installations.js';\nexport {\n deleteSlackInstallationByConnectionId,\n getSlackInstallationByConnectionId,\n getSlackInstallationByTeamId,\n markSlackInstallationRevoked,\n upsertSlackInstallation,\n} from '#db/installations.js';\nexport {\n type CreateSlackE2eRoutesOptions,\n createSlackE2eRoutes,\n} from '#presentation/e2eRoutes/index.js';\nexport {\n type CreateSlackWebhookRoutesOptions,\n createSlackWebhookRoutes,\n SLACK_WEBHOOK_BODY_LIMIT,\n SLASH_COMMAND_ACK,\n} from '#presentation/routes/webhooks.js';\nexport {closeDb, config, db, migrationsPath};\n\nexport interface CreateSlackIntegrationProviderOptions {\n slack?: SlackApiClient | undefined;\n agentTools?: {tokenStore: Pick<SlackTokenStore, 'getAccessToken'>} | undefined;\n getSlackInstallationByConnectionId?: typeof getSlackInstallationByConnectionId | undefined;\n cleanup?:\n | {\n deleteConnectionRecords?: (\n connection: {id: string},\n options: {tx: unknown},\n ) => Promise<void>;\n deleteConnectionSecrets?: (connection: {id: string; workspaceId: string}) => Promise<void>;\n }\n | undefined;\n routes?: SlackRouteOptions | undefined;\n}\n\nexport function createSlackIntegrationProvider(\n options: CreateSlackIntegrationProviderOptions = {},\n) {\n const slack = options.slack ?? createSlackApiClient();\n const getInstallationByConnectionId =\n options.getSlackInstallationByConnectionId ?? getSlackInstallationByConnectionId;\n const adapters = options.agentTools\n ? {\n agent_tools: new SlackAgentToolsProvider({\n slack,\n tokenStore: options.agentTools.tokenStore,\n }),\n }\n : {};\n if (\n options.routes &&\n !hasSlackInstallationRoutesOptions(options.routes) &&\n !hasSlackWebhookRoutesOptions(options.routes)\n ) {\n throw new Error('Slack webhook routes require every core persistence dependency');\n }\n const routes: RouteGroup[] = [];\n if (options.routes && hasSlackInstallationRoutesOptions(options.routes)) {\n const {\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n } = options.routes;\n routes.push(\n createSlackIntegrationRoutes({\n slack,\n connectionCapabilities: adapters.agent_tools ? ['agent_tools'] : [],\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n }),\n );\n }\n if (options.routes && hasSlackWebhookRoutesOptions(options.routes)) {\n routes.push(...createSlackWebhookRoutes(options.routes));\n }\n\n return {\n provider: SLACK_PROVIDER,\n displayName: 'Slack',\n adapters,\n async connectionExternalUrl(connection: {id: string}): Promise<string | undefined> {\n const installation = await getInstallationByConnectionId(connection.id);\n if (!installation) return undefined;\n return `https://app.slack.com/client/${encodeURIComponent(installation.teamId)}`;\n },\n ...options.cleanup,\n routes,\n };\n}\n\nfunction hasSlackInstallationRoutesOptions(\n routes: SlackRouteOptions,\n): routes is SlackInstallationRouteOptions {\n return (\n routes.tokenStore !== undefined &&\n routes.getExistingSlackConnection !== undefined &&\n routes.connectSlackInstallation !== undefined &&\n routes.disconnectSlackInstallation !== undefined\n );\n}\n\nfunction hasSlackWebhookRoutesOptions(\n routes: SlackRouteOptions,\n): routes is CreateSlackWebhookRoutesOptions {\n return (\n routes.coreDb !== undefined &&\n routes.claimWebhookDelivery !== undefined &&\n routes.publishIntegrationEventReceived !== undefined &&\n routes.recordDeliveryOnly !== undefined &&\n routes.getIntegrationConnectionById !== undefined\n );\n}\n"],"names":["SLACK_PROVIDER","createSlackApiClient","config","SlackAgentToolsProvider","closeDb","db","getSlackInstallationByConnectionId","migrationsPath","createSlackIntegrationRoutes","createSlackWebhookRoutes","SLACK_TOOL_METHODS","slackAgentToolCatalog","slackAgentToolSelectionCatalog","disconnectSlackInstallation","SlackAccessTokenUnavailableError","SlackAuthorizationScopeMismatchError","SlackBotTokenMissingError","SlackConnectionAlreadyLinkedError","SlackConnectionNotFoundError","SlackEnterpriseInstallUnsupportedError","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackInstallStateError","SlackIntegrationProviderError","SlackOAuthCallbackError","handleSlackCallback","handleSlackOAuthCallbackError","assertSlackAuthorizationScopes","formatSlackBotScopes","SLACK_BOT_SCOPES","verifySlackSignature","signSlackInstallState","verifySlackInstallState","createSlackTokenStore","slackSecretsNamespace","handleSlackCommand","handleSlackEvent","isSelfAuthoredSlackEvent","deleteSlackInstallationByConnectionId","getSlackInstallationByTeamId","markSlackInstallationRevoked","upsertSlackInstallation","createSlackE2eRoutes","SLACK_WEBHOOK_BODY_LIMIT","SLASH_COMMAND_ACK","createSlackIntegrationProvider","options","slack","getInstallationByConnectionId","adapters","agentTools","agent_tools","tokenStore","routes","hasSlackInstallationRoutesOptions","hasSlackWebhookRoutesOptions","Error","getExistingSlackConnection","connectSlackInstallation","push","connectionCapabilities","provider","displayName","connectionExternalUrl","connection","installation","id","undefined","encodeURIComponent","teamId","cleanup","coreDb","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","getIntegrationConnectionById"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {SLACK_PROVIDER} from '@shipfox/api-integration-slack-dto';\nimport type {RouteGroup} from '@shipfox/node-fastify';\nimport {createSlackApiClient, type SlackApiClient} from '#api/client.js';\nimport {config} from '#config.js';\nimport {SlackAgentToolsProvider} from '#core/agent-tools-provider.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\nimport {createSlackWebhookProcessor} from '#core/webhook-processor.js';\nimport {closeDb, db} from '#db/db.js';\nimport {getSlackInstallationByConnectionId} from '#db/installations.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {\n type CreateSlackIntegrationRoutesOptions,\n createSlackIntegrationRoutes,\n} from '#presentation/routes/install.js';\nimport {\n type CreateSlackWebhookRoutesOptions,\n createSlackWebhookRoutes,\n} from '#presentation/routes/webhooks.js';\n\ntype SlackInstallationRouteOptions = Omit<\n CreateSlackIntegrationRoutesOptions,\n 'slack' | 'connectionCapabilities'\n>;\ntype SlackRouteOptions = Partial<SlackInstallationRouteOptions> &\n Partial<CreateSlackWebhookRoutesOptions>;\n\nexport type {SlackProvider} from '@shipfox/api-integration-slack-dto';\nexport type {SlackApiClient, SlackAuthorization, SlackWebApiResponse} from '#api/client.js';\nexport {createSlackApiClient} from '#api/client.js';\nexport type {\n SlackAgentToolCatalogEntry,\n SlackAgentToolId,\n SlackAgentToolRequiredScope,\n} from '#core/agent-tools.js';\nexport {\n SLACK_TOOL_METHODS,\n slackAgentToolCatalog,\n slackAgentToolSelectionCatalog,\n} from '#core/agent-tools.js';\nexport type {\n SlackAgentToolsProviderOptions,\n SlackToolCallResult,\n} from '#core/agent-tools-provider.js';\nexport {SlackAgentToolsProvider} from '#core/agent-tools-provider.js';\nexport {\n type DisconnectSlackInstallationParams,\n disconnectSlackInstallation,\n} from '#core/disconnect.js';\nexport {\n SlackAccessTokenUnavailableError,\n type SlackAccessTokenUnavailableReason,\n SlackAuthorizationScopeMismatchError,\n SlackBotTokenMissingError,\n SlackConnectionAlreadyLinkedError,\n SlackConnectionNotFoundError,\n SlackEnterpriseInstallUnsupportedError,\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackInstallStateError,\n SlackIntegrationProviderError,\n SlackOAuthCallbackError,\n} from '#core/errors.js';\nexport type {ConnectSlackInstallationInput, HandleSlackCallbackParams} from '#core/install.js';\nexport {handleSlackCallback, handleSlackOAuthCallbackError} from '#core/install.js';\nexport {\n assertSlackAuthorizationScopes,\n formatSlackBotScopes,\n SLACK_BOT_SCOPES,\n} from '#core/scopes.js';\nexport type {VerifySlackSignatureParams} from '#core/signature.js';\nexport {isSlackTimestampWithinReplayWindow, verifySlackSignature} from '#core/signature.js';\nexport type {SlackInstallStateClaims} from '#core/state.js';\nexport {signSlackInstallState, verifySlackInstallState} from '#core/state.js';\nexport type {\n CreateSlackTokenStoreParams,\n GetSlackAccessTokenParams,\n SlackConnectionResolverResult,\n SlackSecretsStore,\n SlackTokenStore,\n StoreSlackTokensParams,\n} from '#core/tokens.js';\nexport {createSlackTokenStore, slackSecretsNamespace} from '#core/tokens.js';\nexport type {\n HandleSlackCommandParams,\n HandleSlackEventParams,\n SlackWebhookOutcome,\n} from '#core/webhook.js';\nexport {handleSlackCommand, handleSlackEvent, isSelfAuthoredSlackEvent} from '#core/webhook.js';\nexport type {\n CreateSlackWebhookProcessorOptions,\n SlackWebhookProcessingResult,\n SlackWebhookProcessor,\n} from '#core/webhook-processor.js';\nexport {createSlackWebhookProcessor} from '#core/webhook-processor.js';\nexport type {\n SlackInstallation,\n SlackInstallationStatus,\n UpsertSlackInstallationParams,\n} from '#db/installations.js';\nexport {\n deleteSlackInstallationByConnectionId,\n getSlackInstallationByConnectionId,\n getSlackInstallationByTeamId,\n markSlackInstallationRevoked,\n upsertSlackInstallation,\n} from '#db/installations.js';\nexport {\n type CreateSlackE2eRoutesOptions,\n createSlackE2eRoutes,\n} from '#presentation/e2eRoutes/index.js';\nexport {\n type CreateSlackWebhookRoutesOptions,\n createSlackWebhookRoutes,\n SLACK_WEBHOOK_BODY_LIMIT,\n SLASH_COMMAND_ACK,\n} from '#presentation/routes/webhooks.js';\nexport {closeDb, config, db, migrationsPath};\n\nexport interface CreateSlackIntegrationProviderOptions {\n slack?: SlackApiClient | undefined;\n agentTools?: {tokenStore: Pick<SlackTokenStore, 'getAccessToken'>} | undefined;\n getSlackInstallationByConnectionId?: typeof getSlackInstallationByConnectionId | undefined;\n cleanup?:\n | {\n deleteConnectionRecords?: (\n connection: {id: string},\n options: {tx: unknown},\n ) => Promise<void>;\n deleteConnectionSecrets?: (connection: {id: string; workspaceId: string}) => Promise<void>;\n }\n | undefined;\n routes?: SlackRouteOptions | undefined;\n}\n\nexport function createSlackIntegrationProvider(\n options: CreateSlackIntegrationProviderOptions = {},\n) {\n const slack = options.slack ?? createSlackApiClient();\n const getInstallationByConnectionId =\n options.getSlackInstallationByConnectionId ?? getSlackInstallationByConnectionId;\n const adapters = options.agentTools\n ? {\n agent_tools: new SlackAgentToolsProvider({\n slack,\n tokenStore: options.agentTools.tokenStore,\n }),\n }\n : {};\n if (\n options.routes &&\n !hasSlackInstallationRoutesOptions(options.routes) &&\n !hasSlackWebhookRoutesOptions(options.routes)\n ) {\n throw new Error('Slack webhook routes require every core persistence dependency');\n }\n const routes: RouteGroup[] = [];\n const webhookProcessor =\n options.routes && hasSlackWebhookRoutesOptions(options.routes)\n ? createSlackWebhookProcessor(options.routes)\n : undefined;\n if (options.routes && hasSlackInstallationRoutesOptions(options.routes)) {\n const {\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n requireActiveWorkspaceMembership,\n } = options.routes;\n routes.push(\n createSlackIntegrationRoutes({\n slack,\n connectionCapabilities: adapters.agent_tools ? ['agent_tools'] : [],\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n ...(requireActiveWorkspaceMembership ? {requireActiveWorkspaceMembership} : {}),\n }),\n );\n }\n if (options.routes && hasSlackWebhookRoutesOptions(options.routes) && webhookProcessor) {\n routes.push(...createSlackWebhookRoutes({...options.routes, processor: webhookProcessor}));\n }\n\n return {\n provider: SLACK_PROVIDER,\n displayName: 'Slack',\n adapters,\n async connectionExternalUrl(connection: {id: string}): Promise<string | undefined> {\n const installation = await getInstallationByConnectionId(connection.id);\n if (!installation) return undefined;\n return `https://app.slack.com/client/${encodeURIComponent(installation.teamId)}`;\n },\n ...options.cleanup,\n routes,\n webhookProcessors: webhookProcessor\n ? [{routeIds: ['slack.event', 'slack.command'] as const, processor: webhookProcessor}]\n : undefined,\n };\n}\n\nfunction hasSlackInstallationRoutesOptions(\n routes: SlackRouteOptions,\n): routes is SlackInstallationRouteOptions {\n return (\n routes.tokenStore !== undefined &&\n routes.getExistingSlackConnection !== undefined &&\n routes.connectSlackInstallation !== undefined &&\n routes.disconnectSlackInstallation !== undefined\n );\n}\n\nfunction hasSlackWebhookRoutesOptions(\n routes: SlackRouteOptions,\n): routes is CreateSlackWebhookRoutesOptions {\n return (\n routes.coreDb !== undefined &&\n routes.claimWebhookDelivery !== undefined &&\n routes.publishIntegrationEventReceived !== undefined &&\n routes.recordDeliveryOnly !== undefined &&\n routes.getIntegrationConnectionById !== undefined\n );\n}\n"],"names":["SLACK_PROVIDER","createSlackApiClient","config","SlackAgentToolsProvider","createSlackWebhookProcessor","closeDb","db","getSlackInstallationByConnectionId","migrationsPath","createSlackIntegrationRoutes","createSlackWebhookRoutes","SLACK_TOOL_METHODS","slackAgentToolCatalog","slackAgentToolSelectionCatalog","disconnectSlackInstallation","SlackAccessTokenUnavailableError","SlackAuthorizationScopeMismatchError","SlackBotTokenMissingError","SlackConnectionAlreadyLinkedError","SlackConnectionNotFoundError","SlackEnterpriseInstallUnsupportedError","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackInstallStateError","SlackIntegrationProviderError","SlackOAuthCallbackError","handleSlackCallback","handleSlackOAuthCallbackError","assertSlackAuthorizationScopes","formatSlackBotScopes","SLACK_BOT_SCOPES","isSlackTimestampWithinReplayWindow","verifySlackSignature","signSlackInstallState","verifySlackInstallState","createSlackTokenStore","slackSecretsNamespace","handleSlackCommand","handleSlackEvent","isSelfAuthoredSlackEvent","deleteSlackInstallationByConnectionId","getSlackInstallationByTeamId","markSlackInstallationRevoked","upsertSlackInstallation","createSlackE2eRoutes","SLACK_WEBHOOK_BODY_LIMIT","SLASH_COMMAND_ACK","createSlackIntegrationProvider","options","slack","getInstallationByConnectionId","adapters","agentTools","agent_tools","tokenStore","routes","hasSlackInstallationRoutesOptions","hasSlackWebhookRoutesOptions","Error","webhookProcessor","undefined","getExistingSlackConnection","connectSlackInstallation","requireActiveWorkspaceMembership","push","connectionCapabilities","processor","provider","displayName","connectionExternalUrl","connection","installation","id","encodeURIComponent","teamId","cleanup","webhookProcessors","routeIds","coreDb","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","getIntegrationConnectionById"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/errors.ts"],"names":[],"mappings":"AAuBA,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CA4C5D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/errors.ts"],"names":[],"mappings":"AAyBA,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CA8D5D"}
@@ -1,4 +1,6 @@
1
1
  import { ConnectionSlugConflictError } from '@shipfox/api-integration-core-dto';
2
+ import { workspacesInterModuleContract } from '@shipfox/api-workspaces-dto/inter-module';
3
+ import { isInterModuleKnownError } from '@shipfox/inter-module';
2
4
  import { ClientError } from '@shipfox/node-fastify';
3
5
  import { SlackAuthorizationScopeMismatchError, SlackConnectionAlreadyLinkedError, SlackEnterpriseInstallUnsupportedError, SlackInstallationAlreadyLinkedError, SlackInstallStateActorMismatchError, SlackInstallStateError, SlackIntegrationProviderError, SlackOAuthCallbackError, SlackTokenRotationUnsupportedError } from '#core/errors.js';
4
6
  function providerStatus(reason) {
@@ -7,6 +9,26 @@ function providerStatus(reason) {
7
9
  return 422;
8
10
  }
9
11
  export function slackRouteErrorHandler(error) {
12
+ if (isInterModuleKnownError(workspacesInterModuleContract.methods.requireActiveMembership, error)) {
13
+ if (error.code === 'workspace-not-found') throw new ClientError('Workspace not found', 'not-found', {
14
+ status: 404,
15
+ details: {
16
+ workspace_id: error.details.workspaceId
17
+ }
18
+ });
19
+ if (error.code === 'membership-required') throw new ClientError('Workspace membership required', 'forbidden', {
20
+ status: 403,
21
+ details: {
22
+ workspace_id: error.details.workspaceId
23
+ }
24
+ });
25
+ throw new ClientError('Workspace is inactive', 'workspace-inactive', {
26
+ status: 403,
27
+ details: {
28
+ workspace_id: error.details.workspaceId
29
+ }
30
+ });
31
+ }
10
32
  if (error instanceof SlackInstallStateError) {
11
33
  throw new ClientError(error.message, 'invalid-slack-install-state', {
12
34
  status: 400
@@ -1 +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 SlackAuthorizationScopeMismatchError,\n SlackConnectionAlreadyLinkedError,\n SlackEnterpriseInstallUnsupportedError,\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackInstallStateError,\n SlackIntegrationProviderError,\n SlackOAuthCallbackError,\n SlackTokenRotationUnsupportedError,\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 slackRouteErrorHandler(error: unknown): never {\n if (error instanceof SlackInstallStateError) {\n throw new ClientError(error.message, 'invalid-slack-install-state', {status: 400});\n }\n if (error instanceof SlackInstallStateActorMismatchError) {\n throw new ClientError(error.message, 'slack-install-state-actor-mismatch', {status: 403});\n }\n if (error instanceof SlackInstallationAlreadyLinkedError) {\n throw new ClientError(error.message, 'slack-installation-already-linked', {status: 409});\n }\n if (error instanceof SlackConnectionAlreadyLinkedError) {\n throw new ClientError(error.message, 'slack-connection-already-linked', {status: 409});\n }\n if (error instanceof SlackAuthorizationScopeMismatchError) {\n throw new ClientError(error.message, 'slack-authorization-scope-mismatch', {\n status: 422,\n details: {missing_scopes: error.missingScopes},\n });\n }\n if (error instanceof SlackEnterpriseInstallUnsupportedError) {\n throw new ClientError(error.message, 'slack-enterprise-install-unsupported', {status: 422});\n }\n if (error instanceof SlackTokenRotationUnsupportedError) {\n throw new ClientError(error.message, 'slack-token-rotation-unsupported', {status: 422});\n }\n if (error instanceof SlackOAuthCallbackError) {\n throw new ClientError(error.message, 'slack-oauth-callback-error', {\n status: 422,\n details: {\n error: error.providerError,\n ...(error.providerDescription ? {error_description: error.providerDescription} : {}),\n },\n });\n }\n if (error instanceof ConnectionSlugConflictError) {\n throw new ClientError(error.message, 'slug-conflict', {status: 409});\n }\n if (error instanceof SlackIntegrationProviderError) {\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","SlackAuthorizationScopeMismatchError","SlackConnectionAlreadyLinkedError","SlackEnterpriseInstallUnsupportedError","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackInstallStateError","SlackIntegrationProviderError","SlackOAuthCallbackError","SlackTokenRotationUnsupportedError","providerStatus","reason","slackRouteErrorHandler","error","message","status","details","missing_scopes","missingScopes","providerError","providerDescription","error_description","retry_after_seconds","retryAfterSeconds"],"mappings":"AAAA,SACEA,2BAA2B,QAEtB,oCAAoC;AAC3C,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SACEC,oCAAoC,EACpCC,iCAAiC,EACjCC,sCAAsC,EACtCC,mCAAmC,EACnCC,mCAAmC,EACnCC,sBAAsB,EACtBC,6BAA6B,EAC7BC,uBAAuB,EACvBC,kCAAkC,QAC7B,kBAAkB;AAEzB,SAASC,eAAeC,MAAsC;IAC5D,IAAIA,WAAW,gBAAgB,OAAO;IACtC,IAAIA,WAAW,aAAaA,WAAW,wBAAwB,OAAO;IACtE,OAAO;AACT;AAEA,OAAO,SAASC,uBAAuBC,KAAc;IACnD,IAAIA,iBAAiBP,wBAAwB;QAC3C,MAAM,IAAIN,YAAYa,MAAMC,OAAO,EAAE,+BAA+B;YAACC,QAAQ;QAAG;IAClF;IACA,IAAIF,iBAAiBR,qCAAqC;QACxD,MAAM,IAAIL,YAAYa,MAAMC,OAAO,EAAE,sCAAsC;YAACC,QAAQ;QAAG;IACzF;IACA,IAAIF,iBAAiBT,qCAAqC;QACxD,MAAM,IAAIJ,YAAYa,MAAMC,OAAO,EAAE,qCAAqC;YAACC,QAAQ;QAAG;IACxF;IACA,IAAIF,iBAAiBX,mCAAmC;QACtD,MAAM,IAAIF,YAAYa,MAAMC,OAAO,EAAE,mCAAmC;YAACC,QAAQ;QAAG;IACtF;IACA,IAAIF,iBAAiBZ,sCAAsC;QACzD,MAAM,IAAID,YAAYa,MAAMC,OAAO,EAAE,sCAAsC;YACzEC,QAAQ;YACRC,SAAS;gBAACC,gBAAgBJ,MAAMK,aAAa;YAAA;QAC/C;IACF;IACA,IAAIL,iBAAiBV,wCAAwC;QAC3D,MAAM,IAAIH,YAAYa,MAAMC,OAAO,EAAE,wCAAwC;YAACC,QAAQ;QAAG;IAC3F;IACA,IAAIF,iBAAiBJ,oCAAoC;QACvD,MAAM,IAAIT,YAAYa,MAAMC,OAAO,EAAE,oCAAoC;YAACC,QAAQ;QAAG;IACvF;IACA,IAAIF,iBAAiBL,yBAAyB;QAC5C,MAAM,IAAIR,YAAYa,MAAMC,OAAO,EAAE,8BAA8B;YACjEC,QAAQ;YACRC,SAAS;gBACPH,OAAOA,MAAMM,aAAa;gBAC1B,GAAIN,MAAMO,mBAAmB,GAAG;oBAACC,mBAAmBR,MAAMO,mBAAmB;gBAAA,IAAI,CAAC,CAAC;YACrF;QACF;IACF;IACA,IAAIP,iBAAiBd,6BAA6B;QAChD,MAAM,IAAIC,YAAYa,MAAMC,OAAO,EAAE,iBAAiB;YAACC,QAAQ;QAAG;IACpE;IACA,IAAIF,iBAAiBN,+BAA+B;QAClD,MAAM,IAAIP,YAAYa,MAAMC,OAAO,EAAED,MAAMF,MAAM,EAAE;YACjDK,SAAS;gBAACM,qBAAqBT,MAAMU,iBAAiB;YAAA;YACtDR,QAAQL,eAAeG,MAAMF,MAAM;QACrC;IACF;IACA,MAAME;AACR"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/errors.ts"],"sourcesContent":["import {\n ConnectionSlugConflictError,\n type IntegrationProviderErrorReason,\n} from '@shipfox/api-integration-core-dto';\nimport {workspacesInterModuleContract} from '@shipfox/api-workspaces-dto/inter-module';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {ClientError} from '@shipfox/node-fastify';\nimport {\n SlackAuthorizationScopeMismatchError,\n SlackConnectionAlreadyLinkedError,\n SlackEnterpriseInstallUnsupportedError,\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackInstallStateError,\n SlackIntegrationProviderError,\n SlackOAuthCallbackError,\n SlackTokenRotationUnsupportedError,\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 slackRouteErrorHandler(error: unknown): never {\n if (\n isInterModuleKnownError(workspacesInterModuleContract.methods.requireActiveMembership, error)\n ) {\n if (error.code === 'workspace-not-found')\n throw new ClientError('Workspace not found', 'not-found', {\n status: 404,\n details: {workspace_id: error.details.workspaceId},\n });\n if (error.code === 'membership-required')\n throw new ClientError('Workspace membership required', 'forbidden', {\n status: 403,\n details: {workspace_id: error.details.workspaceId},\n });\n throw new ClientError('Workspace is inactive', 'workspace-inactive', {\n status: 403,\n details: {workspace_id: error.details.workspaceId},\n });\n }\n if (error instanceof SlackInstallStateError) {\n throw new ClientError(error.message, 'invalid-slack-install-state', {status: 400});\n }\n if (error instanceof SlackInstallStateActorMismatchError) {\n throw new ClientError(error.message, 'slack-install-state-actor-mismatch', {status: 403});\n }\n if (error instanceof SlackInstallationAlreadyLinkedError) {\n throw new ClientError(error.message, 'slack-installation-already-linked', {status: 409});\n }\n if (error instanceof SlackConnectionAlreadyLinkedError) {\n throw new ClientError(error.message, 'slack-connection-already-linked', {status: 409});\n }\n if (error instanceof SlackAuthorizationScopeMismatchError) {\n throw new ClientError(error.message, 'slack-authorization-scope-mismatch', {\n status: 422,\n details: {missing_scopes: error.missingScopes},\n });\n }\n if (error instanceof SlackEnterpriseInstallUnsupportedError) {\n throw new ClientError(error.message, 'slack-enterprise-install-unsupported', {status: 422});\n }\n if (error instanceof SlackTokenRotationUnsupportedError) {\n throw new ClientError(error.message, 'slack-token-rotation-unsupported', {status: 422});\n }\n if (error instanceof SlackOAuthCallbackError) {\n throw new ClientError(error.message, 'slack-oauth-callback-error', {\n status: 422,\n details: {\n error: error.providerError,\n ...(error.providerDescription ? {error_description: error.providerDescription} : {}),\n },\n });\n }\n if (error instanceof ConnectionSlugConflictError) {\n throw new ClientError(error.message, 'slug-conflict', {status: 409});\n }\n if (error instanceof SlackIntegrationProviderError) {\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","workspacesInterModuleContract","isInterModuleKnownError","ClientError","SlackAuthorizationScopeMismatchError","SlackConnectionAlreadyLinkedError","SlackEnterpriseInstallUnsupportedError","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackInstallStateError","SlackIntegrationProviderError","SlackOAuthCallbackError","SlackTokenRotationUnsupportedError","providerStatus","reason","slackRouteErrorHandler","error","methods","requireActiveMembership","code","status","details","workspace_id","workspaceId","message","missing_scopes","missingScopes","providerError","providerDescription","error_description","retry_after_seconds","retryAfterSeconds"],"mappings":"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"}
@@ -14,6 +14,11 @@ export interface CreateSlackIntegrationRoutesOptions {
14
14
  connectionId: string;
15
15
  }) => Promise<void>;
16
16
  connectionCapabilities: IntegrationCapability[];
17
+ requireActiveWorkspaceMembership?: (input: {
18
+ workspaceId: string;
19
+ userId: string;
20
+ memberships: ReadonlyArray<import('@shipfox/api-auth-context').UserContextMembership>;
21
+ }) => Promise<unknown>;
17
22
  }
18
- export declare function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSlackConnection, connectSlackInstallation, disconnectSlackInstallation, connectionCapabilities, }: CreateSlackIntegrationRoutesOptions): RouteGroup;
23
+ export declare function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSlackConnection, connectSlackInstallation, disconnectSlackInstallation, connectionCapabilities, requireActiveWorkspaceMembership, }: CreateSlackIntegrationRoutesOptions): RouteGroup;
19
24
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AASpG,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAEnD,OAAO,EACL,KAAK,6BAA6B,EAGnC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAIrD,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACjD,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,wBAAwB,EAAE,CACxB,KAAK,EAAE,6BAA6B,KACjC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,2BAA2B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;CACjD;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,KAAK,EACL,UAAU,EACV,0BAA0B,EAC1B,wBAAwB,EACxB,2BAA2B,EAC3B,sBAAsB,GACvB,EAAE,mCAAmC,GAAG,UAAU,CAgElD"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAQpG,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAEnD,OAAO,EACL,KAAK,6BAA6B,EAGnC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAIrD,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACjD,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,wBAAwB,EAAE,CACxB,KAAK,EAAE,6BAA6B,KACjC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,2BAA2B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;IAChD,gCAAgC,CAAC,EAAE,CAAC,KAAK,EAAE;QACzC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,OAAO,2BAA2B,EAAE,qBAAqB,CAAC,CAAC;KACvF,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,KAAK,EACL,UAAU,EACV,0BAA0B,EAC1B,wBAAwB,EACxB,2BAA2B,EAC3B,sBAAsB,EACtB,gCAAgC,GACjC,EAAE,mCAAmC,GAAG,UAAU,CAkElD"}
@@ -1,6 +1,5 @@
1
1
  import { AUTH_USER, requireUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { createSlackInstallBodySchema, createSlackInstallResponseSchema, slackCallbackQuerySchema, slackCallbackResponseSchema } from '@shipfox/api-integration-slack-dto';
3
- import { requireWorkspaceMembership } from '@shipfox/api-workspaces';
4
3
  import { defineRoute } from '@shipfox/node-fastify';
5
4
  import { config } from '#config.js';
6
5
  import { handleSlackCallback, handleSlackOAuthCallbackError } from '#core/install.js';
@@ -8,7 +7,7 @@ import { formatSlackBotScopes } from '#core/scopes.js';
8
7
  import { signSlackInstallState } from '#core/state.js';
9
8
  import { toIntegrationConnectionDto } from '#presentation/dto/integrations.js';
10
9
  import { slackRouteErrorHandler } from './errors.js';
11
- export function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSlackConnection, connectSlackInstallation, disconnectSlackInstallation, connectionCapabilities }) {
10
+ export function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSlackConnection, connectSlackInstallation, disconnectSlackInstallation, connectionCapabilities, requireActiveWorkspaceMembership }) {
12
11
  const installRoute = defineRoute({
13
12
  method: 'POST',
14
13
  path: '/install',
@@ -63,7 +62,7 @@ export function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSla
63
62
  errorDescription: query.error_description,
64
63
  sessionUserId: actor.userId,
65
64
  sessionMemberships: actor.memberships,
66
- requireWorkspaceMembership
65
+ requireWorkspaceMembership: requireActiveWorkspaceMembership ?? unavailableWorkspaceMembershipCheck
67
66
  });
68
67
  }
69
68
  const connection = await handleSlackCallback({
@@ -73,7 +72,7 @@ export function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSla
73
72
  state: query.state,
74
73
  sessionUserId: actor.userId,
75
74
  sessionMemberships: actor.memberships,
76
- requireWorkspaceMembership,
75
+ requireWorkspaceMembership: requireActiveWorkspaceMembership ?? unavailableWorkspaceMembershipCheck,
77
76
  getExistingSlackConnection,
78
77
  connectSlackInstallation,
79
78
  disconnectSlackInstallation
@@ -91,6 +90,9 @@ export function createSlackIntegrationRoutes({ slack, tokenStore, getExistingSla
91
90
  ]
92
91
  };
93
92
  }
93
+ function unavailableWorkspaceMembershipCheck(_input) {
94
+ return Promise.reject(new Error('Workspaces inter-module client is not configured'));
95
+ }
94
96
  function isSlackOAuthErrorCallback(query) {
95
97
  return 'error' in query && typeof query.error === 'string';
96
98
  }