@shipfox/api-integration-slack 4.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 +22 -0
- package/LICENSE +21 -0
- package/dist/api/client.d.ts +28 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +163 -0
- package/dist/api/client.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -0
- package/dist/core/agent-tools-provider.d.ts +75 -0
- package/dist/core/agent-tools-provider.d.ts.map +1 -0
- package/dist/core/agent-tools-provider.js +111 -0
- package/dist/core/agent-tools-provider.js.map +1 -0
- package/dist/core/agent-tools.d.ts +65 -0
- package/dist/core/agent-tools.d.ts.map +1 -0
- package/dist/core/agent-tools.js +177 -0
- package/dist/core/agent-tools.js.map +1 -0
- package/dist/core/disconnect.d.ts +18 -0
- package/dist/core/disconnect.d.ts.map +1 -0
- package/dist/core/disconnect.js +23 -0
- package/dist/core/disconnect.js.map +1 -0
- package/dist/core/errors.d.ts +44 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/install.d.ts +48 -0
- package/dist/core/install.d.ts.map +1 -0
- package/dist/core/install.js +88 -0
- package/dist/core/install.js.map +1 -0
- package/dist/core/scopes.d.ts +4 -0
- package/dist/core/scopes.d.ts.map +1 -0
- package/dist/core/scopes.js +24 -0
- package/dist/core/scopes.js.map +1 -0
- package/dist/core/signature.d.ts +10 -0
- package/dist/core/signature.d.ts.map +1 -0
- package/dist/core/signature.js +18 -0
- package/dist/core/signature.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/tokens.d.ts +35 -0
- package/dist/core/tokens.d.ts.map +1 -0
- package/dist/core/tokens.js +51 -0
- package/dist/core/tokens.js.map +1 -0
- package/dist/core/webhook.d.ts +25 -0
- package/dist/core/webhook.d.ts.map +1 -0
- package/dist/core/webhook.js +160 -0
- package/dist/core/webhook.js.map +1 -0
- package/dist/db/db.d.ts +403 -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/installations.d.ts +40 -0
- package/dist/db/installations.d.ts.map +1 -0
- package/dist/db/installations.js +73 -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 +203 -0
- package/dist/db/schema/installations.d.ts.map +1 -0
- package/dist/db/schema/installations.js +42 -0
- package/dist/db/schema/installations.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation/dto/integrations.d.ts +2 -0
- package/dist/presentation/dto/integrations.d.ts.map +1 -0
- package/dist/presentation/dto/integrations.js +3 -0
- package/dist/presentation/dto/integrations.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-connection.d.ts +16 -0
- package/dist/presentation/e2eRoutes/create-connection.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-connection.js +65 -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 +75 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/install.d.ts +19 -0
- package/dist/presentation/routes/install.d.ts.map +1 -0
- package/dist/presentation/routes/install.js +98 -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 +166 -0
- package/dist/presentation/routes/webhooks.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_calm_fantastic_four.sql +16 -0
- package/drizzle/meta/0000_snapshot.json +132 -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 +239 -0
- package/src/api/client.ts +243 -0
- package/src/config.test.ts +32 -0
- package/src/config.ts +20 -0
- package/src/connection-external-url.test.ts +25 -0
- package/src/core/agent-tools-provider.test.ts +256 -0
- package/src/core/agent-tools-provider.ts +147 -0
- package/src/core/agent-tools.test.ts +90 -0
- package/src/core/agent-tools.ts +189 -0
- package/src/core/disconnect.ts +26 -0
- package/src/core/errors.ts +92 -0
- package/src/core/install.test.ts +155 -0
- package/src/core/install.ts +137 -0
- package/src/core/scopes.test.ts +22 -0
- package/src/core/scopes.ts +25 -0
- package/src/core/signature.test.ts +45 -0
- package/src/core/signature.ts +41 -0
- package/src/core/state.test.ts +48 -0
- package/src/core/state.ts +82 -0
- package/src/core/tokens.test.ts +163 -0
- package/src/core/tokens.ts +98 -0
- package/src/core/webhook.test.ts +269 -0
- package/src/core/webhook.ts +223 -0
- package/src/db/db.ts +16 -0
- package/src/db/installations.test.ts +93 -0
- package/src/db/installations.ts +141 -0
- package/src/db/migrations.ts +4 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/installations.ts +44 -0
- package/src/index.test.ts +25 -0
- package/src/index.ts +196 -0
- package/src/presentation/dto/integrations.ts +1 -0
- package/src/presentation/e2eRoutes/create-connection.ts +82 -0
- package/src/presentation/e2eRoutes/index.test.ts +96 -0
- package/src/presentation/e2eRoutes/index.ts +11 -0
- package/src/presentation/routes/errors.ts +68 -0
- package/src/presentation/routes/install.test.ts +229 -0
- package/src/presentation/routes/install.ts +117 -0
- package/src/presentation/routes/webhooks.test.ts +266 -0
- package/src/presentation/routes/webhooks.ts +170 -0
- package/test/api-secrets.d.ts +26 -0
- package/test/env.ts +13 -0
- package/test/factories/index.ts +1 -0
- package/test/globalSetup.ts +23 -0
- package/test/index.ts +1 -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,177 @@
|
|
|
1
|
+
const channelSchema = stringSchema('Slack channel ID');
|
|
2
|
+
const cursorSchema = stringSchema('Next page cursor');
|
|
3
|
+
const limitSchema = numberSchema('Maximum number of results to return');
|
|
4
|
+
export const slackAgentToolCatalog = [
|
|
5
|
+
tool({
|
|
6
|
+
id: 'conversations_history',
|
|
7
|
+
description: 'List messages in a Slack channel or direct message.',
|
|
8
|
+
sensitivity: 'read',
|
|
9
|
+
sensitive: false,
|
|
10
|
+
requiredScope: 'read',
|
|
11
|
+
inputSchema: objectSchema({
|
|
12
|
+
channel: channelSchema,
|
|
13
|
+
cursor: cursorSchema,
|
|
14
|
+
limit: limitSchema,
|
|
15
|
+
oldest: stringSchema('Only messages after this Slack timestamp'),
|
|
16
|
+
latest: stringSchema('Only messages before this Slack timestamp'),
|
|
17
|
+
inclusive: booleanSchema('Include messages at the oldest or latest timestamp')
|
|
18
|
+
}, [
|
|
19
|
+
'channel'
|
|
20
|
+
])
|
|
21
|
+
}),
|
|
22
|
+
tool({
|
|
23
|
+
id: 'conversations_replies',
|
|
24
|
+
description: 'List replies in a Slack message thread.',
|
|
25
|
+
sensitivity: 'read',
|
|
26
|
+
sensitive: false,
|
|
27
|
+
requiredScope: 'read',
|
|
28
|
+
inputSchema: objectSchema({
|
|
29
|
+
channel: channelSchema,
|
|
30
|
+
ts: stringSchema('Slack timestamp of the parent message'),
|
|
31
|
+
cursor: cursorSchema,
|
|
32
|
+
limit: limitSchema
|
|
33
|
+
}, [
|
|
34
|
+
'channel',
|
|
35
|
+
'ts'
|
|
36
|
+
])
|
|
37
|
+
}),
|
|
38
|
+
tool({
|
|
39
|
+
id: 'conversations_list',
|
|
40
|
+
description: 'List Slack channels visible to the bot.',
|
|
41
|
+
sensitivity: 'read',
|
|
42
|
+
sensitive: false,
|
|
43
|
+
requiredScope: 'read',
|
|
44
|
+
inputSchema: objectSchema({
|
|
45
|
+
cursor: cursorSchema,
|
|
46
|
+
limit: limitSchema,
|
|
47
|
+
types: stringSchema('Comma-separated channel types, such as public_channel,private_channel'),
|
|
48
|
+
exclude_archived: booleanSchema('Exclude archived channels')
|
|
49
|
+
})
|
|
50
|
+
}),
|
|
51
|
+
tool({
|
|
52
|
+
id: 'users_info',
|
|
53
|
+
description: 'Look up a Slack user and profile by user ID.',
|
|
54
|
+
sensitivity: 'read',
|
|
55
|
+
sensitive: false,
|
|
56
|
+
requiredScope: 'read',
|
|
57
|
+
inputSchema: objectSchema({
|
|
58
|
+
user: stringSchema('Slack user ID')
|
|
59
|
+
}, [
|
|
60
|
+
'user'
|
|
61
|
+
])
|
|
62
|
+
}),
|
|
63
|
+
tool({
|
|
64
|
+
id: 'chat_postMessage',
|
|
65
|
+
description: 'Post a Slack message to a channel or thread. Provide text, blocks, or both.',
|
|
66
|
+
sensitivity: 'write',
|
|
67
|
+
sensitive: false,
|
|
68
|
+
requiredScope: 'write',
|
|
69
|
+
inputSchema: objectSchema({
|
|
70
|
+
channel: channelSchema,
|
|
71
|
+
text: stringSchema('Message text'),
|
|
72
|
+
thread_ts: stringSchema('Slack timestamp of the parent message when replying in a thread'),
|
|
73
|
+
blocks: arraySchema({
|
|
74
|
+
type: 'object'
|
|
75
|
+
})
|
|
76
|
+
}, [
|
|
77
|
+
'channel'
|
|
78
|
+
])
|
|
79
|
+
}),
|
|
80
|
+
tool({
|
|
81
|
+
id: 'chat_update',
|
|
82
|
+
description: 'Update a Slack message with text, blocks, or both.',
|
|
83
|
+
sensitivity: 'write',
|
|
84
|
+
sensitive: false,
|
|
85
|
+
requiredScope: 'write',
|
|
86
|
+
inputSchema: objectSchema({
|
|
87
|
+
channel: channelSchema,
|
|
88
|
+
ts: stringSchema('Slack timestamp of the message to update'),
|
|
89
|
+
text: stringSchema('Updated message text'),
|
|
90
|
+
blocks: arraySchema({
|
|
91
|
+
type: 'object'
|
|
92
|
+
})
|
|
93
|
+
}, [
|
|
94
|
+
'channel',
|
|
95
|
+
'ts'
|
|
96
|
+
])
|
|
97
|
+
}),
|
|
98
|
+
tool({
|
|
99
|
+
id: 'reactions_add',
|
|
100
|
+
description: 'Add an emoji reaction to a Slack message.',
|
|
101
|
+
sensitivity: 'write',
|
|
102
|
+
sensitive: false,
|
|
103
|
+
requiredScope: 'write',
|
|
104
|
+
inputSchema: objectSchema({
|
|
105
|
+
channel: channelSchema,
|
|
106
|
+
timestamp: stringSchema('Slack timestamp of the message'),
|
|
107
|
+
name: stringSchema('Emoji name without surrounding colons')
|
|
108
|
+
}, [
|
|
109
|
+
'channel',
|
|
110
|
+
'timestamp',
|
|
111
|
+
'name'
|
|
112
|
+
])
|
|
113
|
+
})
|
|
114
|
+
];
|
|
115
|
+
export const SLACK_TOOL_METHODS = {
|
|
116
|
+
conversations_history: 'conversations.history',
|
|
117
|
+
conversations_replies: 'conversations.replies',
|
|
118
|
+
conversations_list: 'conversations.list',
|
|
119
|
+
users_info: 'users.info',
|
|
120
|
+
chat_postMessage: 'chat.postMessage',
|
|
121
|
+
chat_update: 'chat.update',
|
|
122
|
+
reactions_add: 'reactions.add'
|
|
123
|
+
};
|
|
124
|
+
export const slackAgentToolSelectionCatalog = buildSlackAgentToolSelectionCatalog(slackAgentToolCatalog);
|
|
125
|
+
function buildSlackAgentToolSelectionCatalog(catalog) {
|
|
126
|
+
return {
|
|
127
|
+
selectors: catalog.map((entry)=>({
|
|
128
|
+
token: entry.id,
|
|
129
|
+
kind: 'standalone',
|
|
130
|
+
sensitivity: entry.sensitivity,
|
|
131
|
+
sensitive: entry.sensitive
|
|
132
|
+
}))
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function tool(input) {
|
|
136
|
+
return input;
|
|
137
|
+
}
|
|
138
|
+
function objectSchema(properties, required = []) {
|
|
139
|
+
return {
|
|
140
|
+
type: 'object',
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
properties,
|
|
143
|
+
...required.length > 0 ? {
|
|
144
|
+
required
|
|
145
|
+
} : {}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function stringSchema(description) {
|
|
149
|
+
return {
|
|
150
|
+
type: 'string',
|
|
151
|
+
...description ? {
|
|
152
|
+
description
|
|
153
|
+
} : {}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function numberSchema(description) {
|
|
157
|
+
return {
|
|
158
|
+
type: 'number',
|
|
159
|
+
...description ? {
|
|
160
|
+
description
|
|
161
|
+
} : {}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function booleanSchema(description) {
|
|
165
|
+
return {
|
|
166
|
+
type: 'boolean',
|
|
167
|
+
description
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function arraySchema(items) {
|
|
171
|
+
return {
|
|
172
|
+
type: 'array',
|
|
173
|
+
items
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
//# sourceMappingURL=agent-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/agent-tools.ts"],"sourcesContent":["import type {\n AgentToolCatalogEntry,\n AgentToolJsonSchema,\n AgentToolSelectionCatalog,\n AgentToolSelector,\n} from '@shipfox/api-integration-core-dto';\n\nexport type SlackAgentToolRequiredScope = 'read' | 'write';\nexport type SlackAgentToolCatalogEntry = AgentToolCatalogEntry<SlackAgentToolRequiredScope>;\n\ninterface SlackAgentToolCatalogInput {\n id: string;\n description: string;\n sensitivity: 'read' | 'write';\n sensitive: boolean;\n requiredScope: SlackAgentToolRequiredScope;\n inputSchema: AgentToolJsonSchema;\n}\n\nconst channelSchema = stringSchema('Slack channel ID');\nconst cursorSchema = stringSchema('Next page cursor');\nconst limitSchema = numberSchema('Maximum number of results to return');\n\nexport const slackAgentToolCatalog = [\n tool({\n id: 'conversations_history',\n description: 'List messages in a Slack channel or direct message.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n cursor: cursorSchema,\n limit: limitSchema,\n oldest: stringSchema('Only messages after this Slack timestamp'),\n latest: stringSchema('Only messages before this Slack timestamp'),\n inclusive: booleanSchema('Include messages at the oldest or latest timestamp'),\n },\n ['channel'],\n ),\n }),\n tool({\n id: 'conversations_replies',\n description: 'List replies in a Slack message thread.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n ts: stringSchema('Slack timestamp of the parent message'),\n cursor: cursorSchema,\n limit: limitSchema,\n },\n ['channel', 'ts'],\n ),\n }),\n tool({\n id: 'conversations_list',\n description: 'List Slack channels visible to the bot.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema({\n cursor: cursorSchema,\n limit: limitSchema,\n types: stringSchema('Comma-separated channel types, such as public_channel,private_channel'),\n exclude_archived: booleanSchema('Exclude archived channels'),\n }),\n }),\n tool({\n id: 'users_info',\n description: 'Look up a Slack user and profile by user ID.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema({user: stringSchema('Slack user ID')}, ['user']),\n }),\n tool({\n id: 'chat_postMessage',\n description: 'Post a Slack message to a channel or thread. Provide text, blocks, or both.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n text: stringSchema('Message text'),\n thread_ts: stringSchema('Slack timestamp of the parent message when replying in a thread'),\n blocks: arraySchema({type: 'object'}),\n },\n ['channel'],\n ),\n }),\n tool({\n id: 'chat_update',\n description: 'Update a Slack message with text, blocks, or both.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n ts: stringSchema('Slack timestamp of the message to update'),\n text: stringSchema('Updated message text'),\n blocks: arraySchema({type: 'object'}),\n },\n ['channel', 'ts'],\n ),\n }),\n tool({\n id: 'reactions_add',\n description: 'Add an emoji reaction to a Slack message.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n timestamp: stringSchema('Slack timestamp of the message'),\n name: stringSchema('Emoji name without surrounding colons'),\n },\n ['channel', 'timestamp', 'name'],\n ),\n }),\n] as const satisfies readonly SlackAgentToolCatalogEntry[];\n\nexport type SlackAgentToolId = (typeof slackAgentToolCatalog)[number]['id'];\n\nexport const SLACK_TOOL_METHODS = {\n conversations_history: 'conversations.history',\n conversations_replies: 'conversations.replies',\n conversations_list: 'conversations.list',\n users_info: 'users.info',\n chat_postMessage: 'chat.postMessage',\n chat_update: 'chat.update',\n reactions_add: 'reactions.add',\n} as const satisfies Record<SlackAgentToolId, string>;\n\nexport const slackAgentToolSelectionCatalog =\n buildSlackAgentToolSelectionCatalog(slackAgentToolCatalog);\n\nfunction buildSlackAgentToolSelectionCatalog(\n catalog: readonly SlackAgentToolCatalogEntry[],\n): AgentToolSelectionCatalog {\n return {\n selectors: catalog.map(\n (entry): AgentToolSelector => ({\n token: entry.id,\n kind: 'standalone',\n sensitivity: entry.sensitivity,\n sensitive: entry.sensitive,\n }),\n ),\n };\n}\n\nfunction tool<const Entry extends SlackAgentToolCatalogInput>(input: Entry): Entry {\n return input;\n}\n\nfunction objectSchema(\n properties: Record<string, AgentToolJsonSchema>,\n required: string[] = [],\n): AgentToolJsonSchema {\n return {\n type: 'object',\n additionalProperties: false,\n properties,\n ...(required.length > 0 ? {required} : {}),\n };\n}\n\nfunction stringSchema(description?: string): AgentToolJsonSchema {\n return {type: 'string', ...(description ? {description} : {})};\n}\n\nfunction numberSchema(description?: string): AgentToolJsonSchema {\n return {type: 'number', ...(description ? {description} : {})};\n}\n\nfunction booleanSchema(description: string): AgentToolJsonSchema {\n return {type: 'boolean', description};\n}\n\nfunction arraySchema(items: AgentToolJsonSchema): AgentToolJsonSchema {\n return {type: 'array', items};\n}\n"],"names":["channelSchema","stringSchema","cursorSchema","limitSchema","numberSchema","slackAgentToolCatalog","tool","id","description","sensitivity","sensitive","requiredScope","inputSchema","objectSchema","channel","cursor","limit","oldest","latest","inclusive","booleanSchema","ts","types","exclude_archived","user","text","thread_ts","blocks","arraySchema","type","timestamp","name","SLACK_TOOL_METHODS","conversations_history","conversations_replies","conversations_list","users_info","chat_postMessage","chat_update","reactions_add","slackAgentToolSelectionCatalog","buildSlackAgentToolSelectionCatalog","catalog","selectors","map","entry","token","kind","input","properties","required","additionalProperties","length","items"],"mappings":"AAmBA,MAAMA,gBAAgBC,aAAa;AACnC,MAAMC,eAAeD,aAAa;AAClC,MAAME,cAAcC,aAAa;AAEjC,OAAO,MAAMC,wBAAwB;IACnCC,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTe,QAAQb;YACRc,OAAOb;YACPc,QAAQhB,aAAa;YACrBiB,QAAQjB,aAAa;YACrBkB,WAAWC,cAAc;QAC3B,GACA;YAAC;SAAU;IAEf;IACAd,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTqB,IAAIpB,aAAa;YACjBc,QAAQb;YACRc,OAAOb;QACT,GACA;YAAC;YAAW;SAAK;IAErB;IACAG,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aAAa;YACxBE,QAAQb;YACRc,OAAOb;YACPmB,OAAOrB,aAAa;YACpBsB,kBAAkBH,cAAc;QAClC;IACF;IACAd,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aAAa;YAACW,MAAMvB,aAAa;QAAgB,GAAG;YAAC;SAAO;IAC3E;IACAK,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTyB,MAAMxB,aAAa;YACnByB,WAAWzB,aAAa;YACxB0B,QAAQC,YAAY;gBAACC,MAAM;YAAQ;QACrC,GACA;YAAC;SAAU;IAEf;IACAvB,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTqB,IAAIpB,aAAa;YACjBwB,MAAMxB,aAAa;YACnB0B,QAAQC,YAAY;gBAACC,MAAM;YAAQ;QACrC,GACA;YAAC;YAAW;SAAK;IAErB;IACAvB,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACT8B,WAAW7B,aAAa;YACxB8B,MAAM9B,aAAa;QACrB,GACA;YAAC;YAAW;YAAa;SAAO;IAEpC;CACD,CAA0D;AAI3D,OAAO,MAAM+B,qBAAqB;IAChCC,uBAAuB;IACvBC,uBAAuB;IACvBC,oBAAoB;IACpBC,YAAY;IACZC,kBAAkB;IAClBC,aAAa;IACbC,eAAe;AACjB,EAAsD;AAEtD,OAAO,MAAMC,iCACXC,oCAAoCpC,uBAAuB;AAE7D,SAASoC,oCACPC,OAA8C;IAE9C,OAAO;QACLC,WAAWD,QAAQE,GAAG,CACpB,CAACC,QAA8B,CAAA;gBAC7BC,OAAOD,MAAMtC,EAAE;gBACfwC,MAAM;gBACNtC,aAAaoC,MAAMpC,WAAW;gBAC9BC,WAAWmC,MAAMnC,SAAS;YAC5B,CAAA;IAEJ;AACF;AAEA,SAASJ,KAAqD0C,KAAY;IACxE,OAAOA;AACT;AAEA,SAASnC,aACPoC,UAA+C,EAC/CC,WAAqB,EAAE;IAEvB,OAAO;QACLrB,MAAM;QACNsB,sBAAsB;QACtBF;QACA,GAAIC,SAASE,MAAM,GAAG,IAAI;YAACF;QAAQ,IAAI,CAAC,CAAC;IAC3C;AACF;AAEA,SAASjD,aAAaO,WAAoB;IACxC,OAAO;QAACqB,MAAM;QAAU,GAAIrB,cAAc;YAACA;QAAW,IAAI,CAAC,CAAC;IAAC;AAC/D;AAEA,SAASJ,aAAaI,WAAoB;IACxC,OAAO;QAACqB,MAAM;QAAU,GAAIrB,cAAc;YAACA;QAAW,IAAI,CAAC,CAAC;IAAC;AAC/D;AAEA,SAASY,cAAcZ,WAAmB;IACxC,OAAO;QAACqB,MAAM;QAAWrB;IAAW;AACtC;AAEA,SAASoB,YAAYyB,KAA0B;IAC7C,OAAO;QAACxB,MAAM;QAASwB;IAAK;AAC9B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface DisconnectSlackInstallationParams<Tx = unknown> {
|
|
2
|
+
connectionId: string;
|
|
3
|
+
getConnection(connectionId: string): Promise<{
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
} | undefined>;
|
|
6
|
+
deleteSecrets(params: {
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
namespace: string;
|
|
9
|
+
}): Promise<number>;
|
|
10
|
+
transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;
|
|
11
|
+
deleteConnection(params: {
|
|
12
|
+
connectionId: string;
|
|
13
|
+
}, options: {
|
|
14
|
+
tx: Tx;
|
|
15
|
+
}): Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
export declare function disconnectSlackInstallation<Tx = unknown>(params: DisconnectSlackInstallationParams<Tx>): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=disconnect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disconnect.d.ts","sourceRoot":"","sources":["../../src/core/disconnect.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iCAAiC,CAAC,EAAE,GAAG,OAAO;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GAAG,SAAS,CAAC,CAAC;IAChF,aAAa,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjF,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACvD,gBAAgB,CAAC,MAAM,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,EAAE,OAAO,EAAE;QAAC,EAAE,EAAE,EAAE,CAAA;KAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AAED,wBAAsB,2BAA2B,CAAC,EAAE,GAAG,OAAO,EAC5D,MAAM,EAAE,iCAAiC,CAAC,EAAE,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { deleteSlackInstallationByConnectionId } from '#db/installations.js';
|
|
2
|
+
import { slackSecretsNamespace } from './tokens.js';
|
|
3
|
+
export async function disconnectSlackInstallation(params) {
|
|
4
|
+
const connection = await params.getConnection(params.connectionId);
|
|
5
|
+
if (connection) {
|
|
6
|
+
await params.deleteSecrets({
|
|
7
|
+
workspaceId: connection.workspaceId,
|
|
8
|
+
namespace: slackSecretsNamespace(params.connectionId)
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
await params.transaction(async (tx)=>{
|
|
12
|
+
await deleteSlackInstallationByConnectionId(params.connectionId, {
|
|
13
|
+
tx
|
|
14
|
+
});
|
|
15
|
+
await params.deleteConnection({
|
|
16
|
+
connectionId: params.connectionId
|
|
17
|
+
}, {
|
|
18
|
+
tx
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=disconnect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/disconnect.ts"],"sourcesContent":["import {deleteSlackInstallationByConnectionId} from '#db/installations.js';\nimport {slackSecretsNamespace} from './tokens.js';\n\nexport interface DisconnectSlackInstallationParams<Tx = unknown> {\n connectionId: string;\n getConnection(connectionId: string): Promise<{workspaceId: string} | undefined>;\n deleteSecrets(params: {workspaceId: string; namespace: string}): Promise<number>;\n transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;\n deleteConnection(params: {connectionId: string}, options: {tx: Tx}): Promise<boolean>;\n}\n\nexport async function disconnectSlackInstallation<Tx = unknown>(\n params: DisconnectSlackInstallationParams<Tx>,\n): Promise<void> {\n const connection = await params.getConnection(params.connectionId);\n if (connection) {\n await params.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: slackSecretsNamespace(params.connectionId),\n });\n }\n await params.transaction(async (tx) => {\n await deleteSlackInstallationByConnectionId(params.connectionId, {tx});\n await params.deleteConnection({connectionId: params.connectionId}, {tx});\n });\n}\n"],"names":["deleteSlackInstallationByConnectionId","slackSecretsNamespace","disconnectSlackInstallation","params","connection","getConnection","connectionId","deleteSecrets","workspaceId","namespace","transaction","tx","deleteConnection"],"mappings":"AAAA,SAAQA,qCAAqC,QAAO,uBAAuB;AAC3E,SAAQC,qBAAqB,QAAO,cAAc;AAUlD,OAAO,eAAeC,4BACpBC,MAA6C;IAE7C,MAAMC,aAAa,MAAMD,OAAOE,aAAa,CAACF,OAAOG,YAAY;IACjE,IAAIF,YAAY;QACd,MAAMD,OAAOI,aAAa,CAAC;YACzBC,aAAaJ,WAAWI,WAAW;YACnCC,WAAWR,sBAAsBE,OAAOG,YAAY;QACtD;IACF;IACA,MAAMH,OAAOO,WAAW,CAAC,OAAOC;QAC9B,MAAMX,sCAAsCG,OAAOG,YAAY,EAAE;YAACK;QAAE;QACpE,MAAMR,OAAOS,gBAAgB,CAAC;YAACN,cAAcH,OAAOG,YAAY;QAAA,GAAG;YAACK;QAAE;IACxE;AACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { IntegrationProviderError } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
export declare class SlackIntegrationProviderError extends IntegrationProviderError {
|
|
3
|
+
}
|
|
4
|
+
export declare class SlackInstallStateError extends Error {
|
|
5
|
+
constructor(message?: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class SlackInstallStateActorMismatchError extends Error {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
export declare class SlackOAuthCallbackError extends Error {
|
|
11
|
+
readonly providerError: string;
|
|
12
|
+
readonly providerDescription: string | undefined;
|
|
13
|
+
constructor(providerError: string, providerDescription: string | undefined);
|
|
14
|
+
}
|
|
15
|
+
export declare class SlackAuthorizationScopeMismatchError extends Error {
|
|
16
|
+
readonly missingScopes: string[];
|
|
17
|
+
constructor(missingScopes: string[]);
|
|
18
|
+
}
|
|
19
|
+
export declare class SlackEnterpriseInstallUnsupportedError extends Error {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
22
|
+
export declare class SlackTokenRotationUnsupportedError extends Error {
|
|
23
|
+
constructor();
|
|
24
|
+
}
|
|
25
|
+
export declare class SlackInstallationAlreadyLinkedError extends Error {
|
|
26
|
+
constructor(teamId: string);
|
|
27
|
+
}
|
|
28
|
+
export declare class SlackConnectionAlreadyLinkedError extends Error {
|
|
29
|
+
constructor(connectionId: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class SlackConnectionNotFoundError extends Error {
|
|
32
|
+
constructor(connectionId: string);
|
|
33
|
+
}
|
|
34
|
+
export declare class SlackBotTokenMissingError extends Error {
|
|
35
|
+
constructor(connectionId: string);
|
|
36
|
+
}
|
|
37
|
+
export type SlackAccessTokenUnavailableReason = 'installation-not-found' | 'not-installed' | 'expired';
|
|
38
|
+
export declare class SlackAccessTokenUnavailableError extends Error {
|
|
39
|
+
readonly connectionId: string;
|
|
40
|
+
readonly reason: SlackAccessTokenUnavailableReason;
|
|
41
|
+
readonly expiresAt?: Date | undefined;
|
|
42
|
+
constructor(connectionId: string, reason: SlackAccessTokenUnavailableReason, expiresAt?: Date | undefined);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,mCAAmC,CAAC;AAE3E,qBAAa,6BAA8B,SAAQ,wBAAwB;CAAG;AAE9E,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,SAAgC;CAIpD;AAED,qBAAa,mCAAoC,SAAQ,KAAK;;CAK7D;AAED,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,aAAa,EAAE,MAAM;aACrB,mBAAmB,EAAE,MAAM,GAAG,SAAS;gBADvC,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,MAAM,GAAG,SAAS;CAK1D;AAED,qBAAa,oCAAqC,SAAQ,KAAK;aACjC,aAAa,EAAE,MAAM,EAAE;gBAAvB,aAAa,EAAE,MAAM,EAAE;CAIpD;AAED,qBAAa,sCAAuC,SAAQ,KAAK;;CAKhE;AAED,qBAAa,kCAAmC,SAAQ,KAAK;;CAK5D;AAED,qBAAa,mCAAoC,SAAQ,KAAK;gBAChD,MAAM,EAAE,MAAM;CAI3B;AAED,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,YAAY,EAAE,MAAM;CAIjC;AAED,qBAAa,4BAA6B,SAAQ,KAAK;gBACzC,YAAY,EAAE,MAAM;CAIjC;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,YAAY,EAAE,MAAM;CAIjC;AAED,MAAM,MAAM,iCAAiC,GACzC,wBAAwB,GACxB,eAAe,GACf,SAAS,CAAC;AAEd,qBAAa,gCAAiC,SAAQ,KAAK;aAEvC,YAAY,EAAE,MAAM;aACpB,MAAM,EAAE,iCAAiC;aACzC,SAAS,CAAC,EAAE,IAAI;gBAFhB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,iCAAiC,EACzC,SAAS,CAAC,EAAE,IAAI,YAAA;CAKnC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { IntegrationProviderError } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
export class SlackIntegrationProviderError extends IntegrationProviderError {
|
|
3
|
+
}
|
|
4
|
+
export class SlackInstallStateError extends Error {
|
|
5
|
+
constructor(message = 'Invalid Slack install state'){
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'SlackInstallStateError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class SlackInstallStateActorMismatchError extends Error {
|
|
11
|
+
constructor(){
|
|
12
|
+
super('Slack install state was created by a different user');
|
|
13
|
+
this.name = 'SlackInstallStateActorMismatchError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class SlackOAuthCallbackError extends Error {
|
|
17
|
+
constructor(providerError, providerDescription){
|
|
18
|
+
super(providerDescription ?? `Slack OAuth callback failed: ${providerError}`), this.providerError = providerError, this.providerDescription = providerDescription;
|
|
19
|
+
this.name = 'SlackOAuthCallbackError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class SlackAuthorizationScopeMismatchError extends Error {
|
|
23
|
+
constructor(missingScopes){
|
|
24
|
+
super(`Slack authorization is missing required scopes: ${missingScopes.join(', ')}`), this.missingScopes = missingScopes;
|
|
25
|
+
this.name = 'SlackAuthorizationScopeMismatchError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class SlackEnterpriseInstallUnsupportedError extends Error {
|
|
29
|
+
constructor(){
|
|
30
|
+
super('Slack Enterprise Grid installations are not supported');
|
|
31
|
+
this.name = 'SlackEnterpriseInstallUnsupportedError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class SlackTokenRotationUnsupportedError extends Error {
|
|
35
|
+
constructor(){
|
|
36
|
+
super('Slack token rotation is not supported');
|
|
37
|
+
this.name = 'SlackTokenRotationUnsupportedError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class SlackInstallationAlreadyLinkedError extends Error {
|
|
41
|
+
constructor(teamId){
|
|
42
|
+
super(`Slack team is already linked to another Shipfox workspace: ${teamId}`);
|
|
43
|
+
this.name = 'SlackInstallationAlreadyLinkedError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class SlackConnectionAlreadyLinkedError extends Error {
|
|
47
|
+
constructor(connectionId){
|
|
48
|
+
super(`Integration connection is already linked to another Slack team: ${connectionId}`);
|
|
49
|
+
this.name = 'SlackConnectionAlreadyLinkedError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class SlackConnectionNotFoundError extends Error {
|
|
53
|
+
constructor(connectionId){
|
|
54
|
+
super(`Slack integration connection was not found: ${connectionId}`);
|
|
55
|
+
this.name = 'SlackConnectionNotFoundError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class SlackBotTokenMissingError extends Error {
|
|
59
|
+
constructor(connectionId){
|
|
60
|
+
super(`Slack bot token is missing for connection: ${connectionId}`);
|
|
61
|
+
this.name = 'SlackBotTokenMissingError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class SlackAccessTokenUnavailableError extends Error {
|
|
65
|
+
constructor(connectionId, reason, expiresAt){
|
|
66
|
+
super(`Slack access token is unavailable for connection: ${connectionId} (${reason})`), this.connectionId = connectionId, this.reason = reason, this.expiresAt = expiresAt;
|
|
67
|
+
this.name = 'SlackAccessTokenUnavailableError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["import {IntegrationProviderError} from '@shipfox/api-integration-core-dto';\n\nexport class SlackIntegrationProviderError extends IntegrationProviderError {}\n\nexport class SlackInstallStateError extends Error {\n constructor(message = 'Invalid Slack install state') {\n super(message);\n this.name = 'SlackInstallStateError';\n }\n}\n\nexport class SlackInstallStateActorMismatchError extends Error {\n constructor() {\n super('Slack install state was created by a different user');\n this.name = 'SlackInstallStateActorMismatchError';\n }\n}\n\nexport class SlackOAuthCallbackError extends Error {\n constructor(\n public readonly providerError: string,\n public readonly providerDescription: string | undefined,\n ) {\n super(providerDescription ?? `Slack OAuth callback failed: ${providerError}`);\n this.name = 'SlackOAuthCallbackError';\n }\n}\n\nexport class SlackAuthorizationScopeMismatchError extends Error {\n constructor(public readonly missingScopes: string[]) {\n super(`Slack authorization is missing required scopes: ${missingScopes.join(', ')}`);\n this.name = 'SlackAuthorizationScopeMismatchError';\n }\n}\n\nexport class SlackEnterpriseInstallUnsupportedError extends Error {\n constructor() {\n super('Slack Enterprise Grid installations are not supported');\n this.name = 'SlackEnterpriseInstallUnsupportedError';\n }\n}\n\nexport class SlackTokenRotationUnsupportedError extends Error {\n constructor() {\n super('Slack token rotation is not supported');\n this.name = 'SlackTokenRotationUnsupportedError';\n }\n}\n\nexport class SlackInstallationAlreadyLinkedError extends Error {\n constructor(teamId: string) {\n super(`Slack team is already linked to another Shipfox workspace: ${teamId}`);\n this.name = 'SlackInstallationAlreadyLinkedError';\n }\n}\n\nexport class SlackConnectionAlreadyLinkedError extends Error {\n constructor(connectionId: string) {\n super(`Integration connection is already linked to another Slack team: ${connectionId}`);\n this.name = 'SlackConnectionAlreadyLinkedError';\n }\n}\n\nexport class SlackConnectionNotFoundError extends Error {\n constructor(connectionId: string) {\n super(`Slack integration connection was not found: ${connectionId}`);\n this.name = 'SlackConnectionNotFoundError';\n }\n}\n\nexport class SlackBotTokenMissingError extends Error {\n constructor(connectionId: string) {\n super(`Slack bot token is missing for connection: ${connectionId}`);\n this.name = 'SlackBotTokenMissingError';\n }\n}\n\nexport type SlackAccessTokenUnavailableReason =\n | 'installation-not-found'\n | 'not-installed'\n | 'expired';\n\nexport class SlackAccessTokenUnavailableError extends Error {\n constructor(\n public readonly connectionId: string,\n public readonly reason: SlackAccessTokenUnavailableReason,\n public readonly expiresAt?: Date,\n ) {\n super(`Slack access token is unavailable for connection: ${connectionId} (${reason})`);\n this.name = 'SlackAccessTokenUnavailableError';\n }\n}\n"],"names":["IntegrationProviderError","SlackIntegrationProviderError","SlackInstallStateError","Error","message","name","SlackInstallStateActorMismatchError","SlackOAuthCallbackError","providerError","providerDescription","SlackAuthorizationScopeMismatchError","missingScopes","join","SlackEnterpriseInstallUnsupportedError","SlackTokenRotationUnsupportedError","SlackInstallationAlreadyLinkedError","teamId","SlackConnectionAlreadyLinkedError","connectionId","SlackConnectionNotFoundError","SlackBotTokenMissingError","SlackAccessTokenUnavailableError","reason","expiresAt"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,oCAAoC;AAE3E,OAAO,MAAMC,sCAAsCD;AAA0B;AAE7E,OAAO,MAAME,+BAA+BC;IAC1C,YAAYC,UAAU,6BAA6B,CAAE;QACnD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,4CAA4CH;IACvD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAME,gCAAgCJ;IAC3C,YACE,AAAgBK,aAAqB,EACrC,AAAgBC,mBAAuC,CACvD;QACA,KAAK,CAACA,uBAAuB,CAAC,6BAA6B,EAAED,eAAe,QAH5DA,gBAAAA,oBACAC,sBAAAA;QAGhB,IAAI,CAACJ,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMK,6CAA6CP;IACxD,YAAY,AAAgBQ,aAAuB,CAAE;QACnD,KAAK,CAAC,CAAC,gDAAgD,EAAEA,cAAcC,IAAI,CAAC,OAAO,QADzDD,gBAAAA;QAE1B,IAAI,CAACN,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMQ,+CAA+CV;IAC1D,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMS,2CAA2CX;IACtD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMU,4CAA4CZ;IACvD,YAAYa,MAAc,CAAE;QAC1B,KAAK,CAAC,CAAC,2DAA2D,EAAEA,QAAQ;QAC5E,IAAI,CAACX,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMY,0CAA0Cd;IACrD,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,gEAAgE,EAAEA,cAAc;QACvF,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMc,qCAAqChB;IAChD,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,4CAA4C,EAAEA,cAAc;QACnE,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMe,kCAAkCjB;IAC7C,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,2CAA2C,EAAEA,cAAc;QAClE,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAOA,OAAO,MAAMgB,yCAAyClB;IACpD,YACE,AAAgBe,YAAoB,EACpC,AAAgBI,MAAyC,EACzD,AAAgBC,SAAgB,CAChC;QACA,KAAK,CAAC,CAAC,kDAAkD,EAAEL,aAAa,EAAE,EAAEI,OAAO,CAAC,CAAC,QAJrEJ,eAAAA,mBACAI,SAAAA,aACAC,YAAAA;QAGhB,IAAI,CAAClB,IAAI,GAAG;IACd;AACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { UserContextMembership } from '@shipfox/api-auth-context';
|
|
2
|
+
import type { IntegrationConnection } from '@shipfox/api-integration-core-dto';
|
|
3
|
+
import type { SlackApiClient } from '#api/client.js';
|
|
4
|
+
import type { SlackTokenStore } from '#core/tokens.js';
|
|
5
|
+
export interface ConnectSlackInstallationInput {
|
|
6
|
+
workspaceId: string;
|
|
7
|
+
teamId: string;
|
|
8
|
+
teamName: string;
|
|
9
|
+
appId: string;
|
|
10
|
+
botUserId: string;
|
|
11
|
+
scopes: string[];
|
|
12
|
+
tokenExpiresAt: Date | null;
|
|
13
|
+
displayName: string;
|
|
14
|
+
}
|
|
15
|
+
export interface HandleSlackCallbackParams {
|
|
16
|
+
slack: SlackApiClient;
|
|
17
|
+
tokenStore: Pick<SlackTokenStore, 'storeTokens'>;
|
|
18
|
+
code: string;
|
|
19
|
+
state: string;
|
|
20
|
+
sessionUserId: string;
|
|
21
|
+
sessionMemberships: ReadonlyArray<UserContextMembership>;
|
|
22
|
+
requireWorkspaceMembership: (params: {
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
userId: string;
|
|
25
|
+
memberships: ReadonlyArray<UserContextMembership>;
|
|
26
|
+
}) => Promise<unknown>;
|
|
27
|
+
getExistingSlackConnection: (input: {
|
|
28
|
+
teamId: string;
|
|
29
|
+
}) => Promise<IntegrationConnection<'slack'> | undefined>;
|
|
30
|
+
connectSlackInstallation: (input: ConnectSlackInstallationInput) => Promise<IntegrationConnection<'slack'>>;
|
|
31
|
+
disconnectSlackInstallation: (input: {
|
|
32
|
+
connectionId: string;
|
|
33
|
+
}) => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function handleSlackCallback(params: HandleSlackCallbackParams): Promise<IntegrationConnection<'slack'>>;
|
|
36
|
+
export declare function handleSlackOAuthCallbackError(params: {
|
|
37
|
+
state: string;
|
|
38
|
+
error: string;
|
|
39
|
+
errorDescription?: string | undefined;
|
|
40
|
+
sessionUserId: string;
|
|
41
|
+
sessionMemberships: ReadonlyArray<UserContextMembership>;
|
|
42
|
+
requireWorkspaceMembership: (input: {
|
|
43
|
+
workspaceId: string;
|
|
44
|
+
userId: string;
|
|
45
|
+
memberships: ReadonlyArray<UserContextMembership>;
|
|
46
|
+
}) => Promise<unknown>;
|
|
47
|
+
}): Promise<never>;
|
|
48
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/core/install.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAE7E,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AASrD,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACzD,0BAA0B,EAAE,CAAC,MAAM,EAAE;QACnC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,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;CAC/E;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CA6CzC;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACzD,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB,GAAG,OAAO,CAAC,KAAK,CAAC,CASjB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
|
+
import { SlackInstallationAlreadyLinkedError, SlackInstallStateActorMismatchError, SlackOAuthCallbackError } from './errors.js';
|
|
3
|
+
import { assertSlackAuthorizationScopes } from './scopes.js';
|
|
4
|
+
import { verifySlackInstallState } from './state.js';
|
|
5
|
+
export async function handleSlackCallback(params) {
|
|
6
|
+
const claims = verifySlackInstallState(params.state);
|
|
7
|
+
if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();
|
|
8
|
+
await params.requireWorkspaceMembership({
|
|
9
|
+
workspaceId: claims.workspaceId,
|
|
10
|
+
userId: claims.userId,
|
|
11
|
+
memberships: params.sessionMemberships
|
|
12
|
+
});
|
|
13
|
+
const authorization = await params.slack.exchangeAuthorizationCode({
|
|
14
|
+
code: params.code
|
|
15
|
+
});
|
|
16
|
+
let connectedConnectionId;
|
|
17
|
+
let shouldDisconnectConnection = false;
|
|
18
|
+
let shouldRevokeToken = false;
|
|
19
|
+
try {
|
|
20
|
+
const existing = await params.getExistingSlackConnection({
|
|
21
|
+
teamId: authorization.teamId
|
|
22
|
+
});
|
|
23
|
+
if (existing && existing.workspaceId !== claims.workspaceId) {
|
|
24
|
+
throw new SlackInstallationAlreadyLinkedError(authorization.teamId);
|
|
25
|
+
}
|
|
26
|
+
shouldRevokeToken = !existing;
|
|
27
|
+
assertSlackAuthorizationScopes(authorization.scopes);
|
|
28
|
+
const connection = await params.connectSlackInstallation({
|
|
29
|
+
workspaceId: claims.workspaceId,
|
|
30
|
+
teamId: authorization.teamId,
|
|
31
|
+
teamName: authorization.teamName,
|
|
32
|
+
appId: authorization.appId,
|
|
33
|
+
botUserId: authorization.botUserId,
|
|
34
|
+
scopes: authorization.scopes,
|
|
35
|
+
tokenExpiresAt: null,
|
|
36
|
+
displayName: `Slack ${authorization.teamName}`
|
|
37
|
+
});
|
|
38
|
+
connectedConnectionId = connection.id;
|
|
39
|
+
shouldDisconnectConnection = !existing;
|
|
40
|
+
await params.tokenStore.storeTokens({
|
|
41
|
+
connectionId: connection.id,
|
|
42
|
+
botToken: authorization.accessToken,
|
|
43
|
+
editedBy: claims.userId
|
|
44
|
+
});
|
|
45
|
+
return connection;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (shouldRevokeToken) await bestEffortRevokeToken(params.slack, authorization.accessToken);
|
|
48
|
+
if (connectedConnectionId && shouldDisconnectConnection) {
|
|
49
|
+
await bestEffortDisconnectSlackInstallation(params, connectedConnectionId);
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function handleSlackOAuthCallbackError(params) {
|
|
55
|
+
const claims = verifySlackInstallState(params.state);
|
|
56
|
+
if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();
|
|
57
|
+
await params.requireWorkspaceMembership({
|
|
58
|
+
workspaceId: claims.workspaceId,
|
|
59
|
+
userId: claims.userId,
|
|
60
|
+
memberships: params.sessionMemberships
|
|
61
|
+
});
|
|
62
|
+
throw new SlackOAuthCallbackError(params.error, params.errorDescription);
|
|
63
|
+
}
|
|
64
|
+
async function bestEffortRevokeToken(slack, token) {
|
|
65
|
+
try {
|
|
66
|
+
await slack.revokeToken({
|
|
67
|
+
token
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger().warn({
|
|
71
|
+
err: error
|
|
72
|
+
}, 'Slack OAuth token revocation failed');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function bestEffortDisconnectSlackInstallation(params, connectionId) {
|
|
76
|
+
try {
|
|
77
|
+
await params.disconnectSlackInstallation({
|
|
78
|
+
connectionId
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger().warn({
|
|
82
|
+
err: error,
|
|
83
|
+
connectionId
|
|
84
|
+
}, 'Slack connect compensation failed after token storage rejection');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/install.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport type {IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {SlackApiClient} from '#api/client.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\nimport {\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackOAuthCallbackError,\n} from './errors.js';\nimport {assertSlackAuthorizationScopes} from './scopes.js';\nimport {verifySlackInstallState} from './state.js';\n\nexport interface ConnectSlackInstallationInput {\n workspaceId: string;\n teamId: string;\n teamName: string;\n appId: string;\n botUserId: string;\n scopes: string[];\n tokenExpiresAt: Date | null;\n displayName: string;\n}\n\nexport interface HandleSlackCallbackParams {\n slack: SlackApiClient;\n tokenStore: Pick<SlackTokenStore, 'storeTokens'>;\n code: string;\n state: string;\n sessionUserId: string;\n sessionMemberships: ReadonlyArray<UserContextMembership>;\n requireWorkspaceMembership: (params: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>;\n getExistingSlackConnection: (input: {\n teamId: string;\n }) => Promise<IntegrationConnection<'slack'> | undefined>;\n connectSlackInstallation: (\n input: ConnectSlackInstallationInput,\n ) => Promise<IntegrationConnection<'slack'>>;\n disconnectSlackInstallation: (input: {connectionId: string}) => Promise<void>;\n}\n\nexport async function handleSlackCallback(\n params: HandleSlackCallbackParams,\n): Promise<IntegrationConnection<'slack'>> {\n const claims = verifySlackInstallState(params.state);\n if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();\n await params.requireWorkspaceMembership({\n workspaceId: claims.workspaceId,\n userId: claims.userId,\n memberships: params.sessionMemberships,\n });\n\n const authorization = await params.slack.exchangeAuthorizationCode({code: params.code});\n let connectedConnectionId: string | undefined;\n let shouldDisconnectConnection = false;\n let shouldRevokeToken = false;\n try {\n const existing = await params.getExistingSlackConnection({teamId: authorization.teamId});\n if (existing && existing.workspaceId !== claims.workspaceId) {\n throw new SlackInstallationAlreadyLinkedError(authorization.teamId);\n }\n shouldRevokeToken = !existing;\n assertSlackAuthorizationScopes(authorization.scopes);\n const connection = await params.connectSlackInstallation({\n workspaceId: claims.workspaceId,\n teamId: authorization.teamId,\n teamName: authorization.teamName,\n appId: authorization.appId,\n botUserId: authorization.botUserId,\n scopes: authorization.scopes,\n tokenExpiresAt: null,\n displayName: `Slack ${authorization.teamName}`,\n });\n connectedConnectionId = connection.id;\n shouldDisconnectConnection = !existing;\n await params.tokenStore.storeTokens({\n connectionId: connection.id,\n botToken: authorization.accessToken,\n editedBy: claims.userId,\n });\n return connection;\n } catch (error) {\n if (shouldRevokeToken) await bestEffortRevokeToken(params.slack, authorization.accessToken);\n if (connectedConnectionId && shouldDisconnectConnection) {\n await bestEffortDisconnectSlackInstallation(params, connectedConnectionId);\n }\n throw error;\n }\n}\n\nexport async function handleSlackOAuthCallbackError(params: {\n state: string;\n error: string;\n errorDescription?: string | undefined;\n sessionUserId: string;\n sessionMemberships: ReadonlyArray<UserContextMembership>;\n requireWorkspaceMembership: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>;\n}): Promise<never> {\n const claims = verifySlackInstallState(params.state);\n if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();\n await params.requireWorkspaceMembership({\n workspaceId: claims.workspaceId,\n userId: claims.userId,\n memberships: params.sessionMemberships,\n });\n throw new SlackOAuthCallbackError(params.error, params.errorDescription);\n}\n\nasync function bestEffortRevokeToken(slack: SlackApiClient, token: string): Promise<void> {\n try {\n await slack.revokeToken({token});\n } catch (error) {\n logger().warn({err: error}, 'Slack OAuth token revocation failed');\n }\n}\n\nasync function bestEffortDisconnectSlackInstallation(\n params: HandleSlackCallbackParams,\n connectionId: string,\n): Promise<void> {\n try {\n await params.disconnectSlackInstallation({connectionId});\n } catch (error) {\n logger().warn(\n {err: error, connectionId},\n 'Slack connect compensation failed after token storage rejection',\n );\n }\n}\n"],"names":["logger","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackOAuthCallbackError","assertSlackAuthorizationScopes","verifySlackInstallState","handleSlackCallback","params","claims","state","userId","sessionUserId","requireWorkspaceMembership","workspaceId","memberships","sessionMemberships","authorization","slack","exchangeAuthorizationCode","code","connectedConnectionId","shouldDisconnectConnection","shouldRevokeToken","existing","getExistingSlackConnection","teamId","scopes","connection","connectSlackInstallation","teamName","appId","botUserId","tokenExpiresAt","displayName","id","tokenStore","storeTokens","connectionId","botToken","accessToken","editedBy","error","bestEffortRevokeToken","bestEffortDisconnectSlackInstallation","handleSlackOAuthCallbackError","errorDescription","token","revokeToken","warn","err","disconnectSlackInstallation"],"mappings":"AAEA,SAAQA,MAAM,QAAO,8BAA8B;AAGnD,SACEC,mCAAmC,EACnCC,mCAAmC,EACnCC,uBAAuB,QAClB,cAAc;AACrB,SAAQC,8BAA8B,QAAO,cAAc;AAC3D,SAAQC,uBAAuB,QAAO,aAAa;AAkCnD,OAAO,eAAeC,oBACpBC,MAAiC;IAEjC,MAAMC,SAASH,wBAAwBE,OAAOE,KAAK;IACnD,IAAID,OAAOE,MAAM,KAAKH,OAAOI,aAAa,EAAE,MAAM,IAAIT;IACtD,MAAMK,OAAOK,0BAA0B,CAAC;QACtCC,aAAaL,OAAOK,WAAW;QAC/BH,QAAQF,OAAOE,MAAM;QACrBI,aAAaP,OAAOQ,kBAAkB;IACxC;IAEA,MAAMC,gBAAgB,MAAMT,OAAOU,KAAK,CAACC,yBAAyB,CAAC;QAACC,MAAMZ,OAAOY,IAAI;IAAA;IACrF,IAAIC;IACJ,IAAIC,6BAA6B;IACjC,IAAIC,oBAAoB;IACxB,IAAI;QACF,MAAMC,WAAW,MAAMhB,OAAOiB,0BAA0B,CAAC;YAACC,QAAQT,cAAcS,MAAM;QAAA;QACtF,IAAIF,YAAYA,SAASV,WAAW,KAAKL,OAAOK,WAAW,EAAE;YAC3D,MAAM,IAAIZ,oCAAoCe,cAAcS,MAAM;QACpE;QACAH,oBAAoB,CAACC;QACrBnB,+BAA+BY,cAAcU,MAAM;QACnD,MAAMC,aAAa,MAAMpB,OAAOqB,wBAAwB,CAAC;YACvDf,aAAaL,OAAOK,WAAW;YAC/BY,QAAQT,cAAcS,MAAM;YAC5BI,UAAUb,cAAca,QAAQ;YAChCC,OAAOd,cAAcc,KAAK;YAC1BC,WAAWf,cAAce,SAAS;YAClCL,QAAQV,cAAcU,MAAM;YAC5BM,gBAAgB;YAChBC,aAAa,CAAC,MAAM,EAAEjB,cAAca,QAAQ,EAAE;QAChD;QACAT,wBAAwBO,WAAWO,EAAE;QACrCb,6BAA6B,CAACE;QAC9B,MAAMhB,OAAO4B,UAAU,CAACC,WAAW,CAAC;YAClCC,cAAcV,WAAWO,EAAE;YAC3BI,UAAUtB,cAAcuB,WAAW;YACnCC,UAAUhC,OAAOE,MAAM;QACzB;QACA,OAAOiB;IACT,EAAE,OAAOc,OAAO;QACd,IAAInB,mBAAmB,MAAMoB,sBAAsBnC,OAAOU,KAAK,EAAED,cAAcuB,WAAW;QAC1F,IAAInB,yBAAyBC,4BAA4B;YACvD,MAAMsB,sCAAsCpC,QAAQa;QACtD;QACA,MAAMqB;IACR;AACF;AAEA,OAAO,eAAeG,8BAA8BrC,MAWnD;IACC,MAAMC,SAASH,wBAAwBE,OAAOE,KAAK;IACnD,IAAID,OAAOE,MAAM,KAAKH,OAAOI,aAAa,EAAE,MAAM,IAAIT;IACtD,MAAMK,OAAOK,0BAA0B,CAAC;QACtCC,aAAaL,OAAOK,WAAW;QAC/BH,QAAQF,OAAOE,MAAM;QACrBI,aAAaP,OAAOQ,kBAAkB;IACxC;IACA,MAAM,IAAIZ,wBAAwBI,OAAOkC,KAAK,EAAElC,OAAOsC,gBAAgB;AACzE;AAEA,eAAeH,sBAAsBzB,KAAqB,EAAE6B,KAAa;IACvE,IAAI;QACF,MAAM7B,MAAM8B,WAAW,CAAC;YAACD;QAAK;IAChC,EAAE,OAAOL,OAAO;QACdzC,SAASgD,IAAI,CAAC;YAACC,KAAKR;QAAK,GAAG;IAC9B;AACF;AAEA,eAAeE,sCACbpC,MAAiC,EACjC8B,YAAoB;IAEpB,IAAI;QACF,MAAM9B,OAAO2C,2BAA2B,CAAC;YAACb;QAAY;IACxD,EAAE,OAAOI,OAAO;QACdzC,SAASgD,IAAI,CACX;YAACC,KAAKR;YAAOJ;QAAY,GACzB;IAEJ;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/core/scopes.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,mBAY3B,CAAC;AAEH,wBAAgB,oBAAoB,CAAC,MAAM,oBAAmB,GAAG,MAAM,CAEtE;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAIrE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SlackAuthorizationScopeMismatchError } from './errors.js';
|
|
2
|
+
export const SLACK_BOT_SCOPES = Object.freeze([
|
|
3
|
+
'app_mentions:read',
|
|
4
|
+
'im:history',
|
|
5
|
+
'chat:write',
|
|
6
|
+
'channels:history',
|
|
7
|
+
'groups:history',
|
|
8
|
+
'channels:read',
|
|
9
|
+
'groups:read',
|
|
10
|
+
'users:read',
|
|
11
|
+
'reactions:read',
|
|
12
|
+
'reactions:write',
|
|
13
|
+
'commands'
|
|
14
|
+
]);
|
|
15
|
+
export function formatSlackBotScopes(scopes = SLACK_BOT_SCOPES) {
|
|
16
|
+
return scopes.join(',');
|
|
17
|
+
}
|
|
18
|
+
export function assertSlackAuthorizationScopes(scopes) {
|
|
19
|
+
const grantedScopes = new Set(scopes);
|
|
20
|
+
const missingScopes = SLACK_BOT_SCOPES.filter((scope)=>!grantedScopes.has(scope));
|
|
21
|
+
if (missingScopes.length > 0) throw new SlackAuthorizationScopeMismatchError(missingScopes);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=scopes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/scopes.ts"],"sourcesContent":["import {SlackAuthorizationScopeMismatchError} from './errors.js';\n\nexport const SLACK_BOT_SCOPES = Object.freeze([\n 'app_mentions:read',\n 'im:history',\n 'chat:write',\n 'channels:history',\n 'groups:history',\n 'channels:read',\n 'groups:read',\n 'users:read',\n 'reactions:read',\n 'reactions:write',\n 'commands',\n]);\n\nexport function formatSlackBotScopes(scopes = SLACK_BOT_SCOPES): string {\n return scopes.join(',');\n}\n\nexport function assertSlackAuthorizationScopes(scopes: string[]): void {\n const grantedScopes = new Set(scopes);\n const missingScopes = SLACK_BOT_SCOPES.filter((scope) => !grantedScopes.has(scope));\n if (missingScopes.length > 0) throw new SlackAuthorizationScopeMismatchError(missingScopes);\n}\n"],"names":["SlackAuthorizationScopeMismatchError","SLACK_BOT_SCOPES","Object","freeze","formatSlackBotScopes","scopes","join","assertSlackAuthorizationScopes","grantedScopes","Set","missingScopes","filter","scope","has","length"],"mappings":"AAAA,SAAQA,oCAAoC,QAAO,cAAc;AAEjE,OAAO,MAAMC,mBAAmBC,OAAOC,MAAM,CAAC;IAC5C;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,EAAE;AAEH,OAAO,SAASC,qBAAqBC,SAASJ,gBAAgB;IAC5D,OAAOI,OAAOC,IAAI,CAAC;AACrB;AAEA,OAAO,SAASC,+BAA+BF,MAAgB;IAC7D,MAAMG,gBAAgB,IAAIC,IAAIJ;IAC9B,MAAMK,gBAAgBT,iBAAiBU,MAAM,CAAC,CAACC,QAAU,CAACJ,cAAcK,GAAG,CAACD;IAC5E,IAAIF,cAAcI,MAAM,GAAG,GAAG,MAAM,IAAId,qCAAqCU;AAC/E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface VerifySlackSignatureParams {
|
|
2
|
+
signingSecret: string;
|
|
3
|
+
signature: string | undefined;
|
|
4
|
+
timestamp: string | undefined;
|
|
5
|
+
rawBody: string;
|
|
6
|
+
now?: number | undefined;
|
|
7
|
+
replayWindowMs?: number | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare function verifySlackSignature({ signingSecret, signature, timestamp, rawBody, now, replayWindowMs, }: VerifySlackSignatureParams): boolean;
|
|
10
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
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
|
+
}
|
|
7
|
+
const timestampSeconds = Number(timestamp);
|
|
8
|
+
if (!Number.isSafeInteger(timestampSeconds)) return false;
|
|
9
|
+
const timestampMilliseconds = timestampSeconds * 1000;
|
|
10
|
+
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')}`;
|
|
13
|
+
const expectedBuffer = Buffer.from(expected, 'utf8');
|
|
14
|
+
const receivedBuffer = Buffer.from(signature, 'utf8');
|
|
15
|
+
return expectedBuffer.length === receivedBuffer.length && timingSafeEqual(expectedBuffer, receivedBuffer);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +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"}
|