@inkeep/agents-work-apps 0.0.0-dev-20260204182014 → 0.0.0-dev-20260204210021

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 (68) hide show
  1. package/dist/db/index.d.ts +1 -2
  2. package/dist/db/index.js +1 -2
  3. package/dist/db/runDbClient.d.ts +2 -2
  4. package/dist/env.d.ts +2 -24
  5. package/dist/env.js +1 -12
  6. package/dist/github/routes/setup.d.ts +2 -2
  7. package/dist/github/routes/tokenExchange.d.ts +2 -2
  8. package/package.json +2 -10
  9. package/dist/db/manageDbClient.d.ts +0 -7
  10. package/dist/db/manageDbClient.js +0 -16
  11. package/dist/slack/index.d.ts +0 -19
  12. package/dist/slack/index.js +0 -29
  13. package/dist/slack/middleware/permissions.d.ts +0 -16
  14. package/dist/slack/middleware/permissions.js +0 -49
  15. package/dist/slack/routes/events.d.ts +0 -10
  16. package/dist/slack/routes/events.js +0 -319
  17. package/dist/slack/routes/index.d.ts +0 -11
  18. package/dist/slack/routes/index.js +0 -64
  19. package/dist/slack/routes/internal.d.ts +0 -10
  20. package/dist/slack/routes/internal.js +0 -107
  21. package/dist/slack/routes/oauth.d.ts +0 -12
  22. package/dist/slack/routes/oauth.js +0 -218
  23. package/dist/slack/routes/resources.d.ts +0 -10
  24. package/dist/slack/routes/resources.js +0 -163
  25. package/dist/slack/routes/users.d.ts +0 -15
  26. package/dist/slack/routes/users.js +0 -430
  27. package/dist/slack/routes/workspaces.d.ts +0 -10
  28. package/dist/slack/routes/workspaces.js +0 -828
  29. package/dist/slack/routes.d.ts +0 -7
  30. package/dist/slack/routes.js +0 -12
  31. package/dist/slack/services/agent-resolution.d.ts +0 -49
  32. package/dist/slack/services/agent-resolution.js +0 -135
  33. package/dist/slack/services/api-client.d.ts +0 -161
  34. package/dist/slack/services/api-client.js +0 -248
  35. package/dist/slack/services/auth/index.d.ts +0 -61
  36. package/dist/slack/services/auth/index.js +0 -164
  37. package/dist/slack/services/blocks/index.d.ts +0 -60
  38. package/dist/slack/services/blocks/index.js +0 -143
  39. package/dist/slack/services/client.d.ts +0 -78
  40. package/dist/slack/services/client.js +0 -152
  41. package/dist/slack/services/commands/index.d.ts +0 -15
  42. package/dist/slack/services/commands/index.js +0 -556
  43. package/dist/slack/services/events/app-mention.d.ts +0 -41
  44. package/dist/slack/services/events/app-mention.js +0 -212
  45. package/dist/slack/services/events/block-actions.d.ts +0 -47
  46. package/dist/slack/services/events/block-actions.js +0 -287
  47. package/dist/slack/services/events/index.d.ts +0 -6
  48. package/dist/slack/services/events/index.js +0 -7
  49. package/dist/slack/services/events/modal-submission.d.ts +0 -12
  50. package/dist/slack/services/events/modal-submission.js +0 -279
  51. package/dist/slack/services/events/streaming.d.ts +0 -27
  52. package/dist/slack/services/events/streaming.js +0 -285
  53. package/dist/slack/services/events/utils.d.ts +0 -129
  54. package/dist/slack/services/events/utils.js +0 -315
  55. package/dist/slack/services/index.d.ts +0 -18
  56. package/dist/slack/services/index.js +0 -18
  57. package/dist/slack/services/modals.d.ts +0 -67
  58. package/dist/slack/services/modals.js +0 -203
  59. package/dist/slack/services/nango.d.ts +0 -82
  60. package/dist/slack/services/nango.js +0 -326
  61. package/dist/slack/services/security.d.ts +0 -35
  62. package/dist/slack/services/security.js +0 -65
  63. package/dist/slack/services/types.d.ts +0 -26
  64. package/dist/slack/services/types.js +0 -1
  65. package/dist/slack/services/workspace-tokens.d.ts +0 -37
  66. package/dist/slack/services/workspace-tokens.js +0 -39
  67. package/dist/slack/types.d.ts +0 -10
  68. package/dist/slack/types.js +0 -1
@@ -1,203 +0,0 @@
1
- //#region src/slack/services/modals.ts
2
- /**
3
- * Build the agent selector modal for thread context.
4
- *
5
- * Shows:
6
- * - Project dropdown
7
- * - Agent dropdown (updates based on project selection)
8
- * - Include thread context checkbox (if in thread)
9
- * - Question/instructions input
10
- * - Private response checkbox
11
- *
12
- * @param params - Modal configuration parameters
13
- * @returns Slack ModalView object ready for views.open()
14
- */
15
- function buildAgentSelectorModal(params) {
16
- const { projects, agents, metadata, selectedProjectId } = params;
17
- const isInThread = metadata.isInThread;
18
- const projectOptions = projects.map((project) => ({
19
- text: {
20
- type: "plain_text",
21
- text: project.name,
22
- emoji: true
23
- },
24
- value: project.id
25
- }));
26
- const agentOptions = agents.length > 0 ? agents.map((agent) => ({
27
- text: {
28
- type: "plain_text",
29
- text: agent.name || agent.id,
30
- emoji: true
31
- },
32
- value: JSON.stringify({
33
- agentId: agent.id,
34
- projectId: agent.projectId
35
- })
36
- })) : [{
37
- text: {
38
- type: "plain_text",
39
- text: "No agents available",
40
- emoji: true
41
- },
42
- value: "none"
43
- }];
44
- const selectedProjectOption = selectedProjectId ? projectOptions.find((p) => p.value === selectedProjectId) : projectOptions[0];
45
- const blocks = [{
46
- type: "input",
47
- block_id: "project_select_block",
48
- element: {
49
- type: "static_select",
50
- action_id: "modal_project_select",
51
- placeholder: {
52
- type: "plain_text",
53
- text: "Select a project..."
54
- },
55
- options: projectOptions,
56
- ...selectedProjectOption ? { initial_option: selectedProjectOption } : {}
57
- },
58
- label: {
59
- type: "plain_text",
60
- text: "Project",
61
- emoji: true
62
- }
63
- }, {
64
- type: "input",
65
- block_id: "agent_select_block",
66
- element: {
67
- type: "static_select",
68
- action_id: "agent_select",
69
- placeholder: {
70
- type: "plain_text",
71
- text: "Select an agent..."
72
- },
73
- options: agentOptions,
74
- ...agents.length > 0 ? { initial_option: agentOptions[0] } : {}
75
- },
76
- label: {
77
- type: "plain_text",
78
- text: "Agent",
79
- emoji: true
80
- }
81
- }];
82
- if (isInThread) blocks.push({
83
- type: "input",
84
- block_id: "context_block",
85
- element: {
86
- type: "checkboxes",
87
- action_id: "include_context_checkbox",
88
- options: [{
89
- text: {
90
- type: "plain_text",
91
- text: "Include thread context",
92
- emoji: true
93
- },
94
- value: "include_context"
95
- }],
96
- initial_options: [{
97
- text: {
98
- type: "plain_text",
99
- text: "Include thread context",
100
- emoji: true
101
- },
102
- value: "include_context"
103
- }]
104
- },
105
- label: {
106
- type: "plain_text",
107
- text: "Context",
108
- emoji: true
109
- },
110
- optional: true
111
- });
112
- blocks.push({
113
- type: "input",
114
- block_id: "question_block",
115
- element: {
116
- type: "plain_text_input",
117
- action_id: "question_input",
118
- multiline: true,
119
- placeholder: {
120
- type: "plain_text",
121
- text: isInThread ? "Additional instructions (optional)..." : "What would you like to ask?"
122
- }
123
- },
124
- label: {
125
- type: "plain_text",
126
- text: isInThread ? "Additional Instructions" : "Your Question",
127
- emoji: true
128
- },
129
- optional: isInThread
130
- });
131
- blocks.push({
132
- type: "input",
133
- block_id: "visibility_block",
134
- element: {
135
- type: "checkboxes",
136
- action_id: "visibility_checkbox",
137
- options: [{
138
- text: {
139
- type: "plain_text",
140
- text: "Private response (only visible to you)",
141
- emoji: true
142
- },
143
- value: "ephemeral"
144
- }]
145
- },
146
- label: {
147
- type: "plain_text",
148
- text: "Visibility",
149
- emoji: true
150
- },
151
- optional: true
152
- });
153
- return {
154
- type: "modal",
155
- callback_id: "agent_selector_modal",
156
- private_metadata: JSON.stringify(metadata),
157
- title: {
158
- type: "plain_text",
159
- text: isInThread ? "Ask About Thread" : "Ask an Agent",
160
- emoji: true
161
- },
162
- submit: {
163
- type: "plain_text",
164
- text: "Ask Agent",
165
- emoji: true
166
- },
167
- close: {
168
- type: "plain_text",
169
- text: "Cancel",
170
- emoji: true
171
- },
172
- blocks
173
- };
174
- }
175
- /**
176
- * Parse the modal submission payload from Slack.
177
- * Extracts agent selection, question, and visibility settings.
178
- *
179
- * @param view - The view object from view_submission event
180
- * @returns Parsed submission data, or null if parsing fails
181
- */
182
- function parseModalSubmission(view) {
183
- try {
184
- const metadata = JSON.parse(view.private_metadata || "{}");
185
- const values = view.state?.values || {};
186
- const agentSelectValue = values.agent_select_block?.agent_select;
187
- const questionValue = values.question_block?.question_input;
188
- const visibilityValue = values.visibility_block?.visibility_checkbox;
189
- const agentData = JSON.parse(agentSelectValue?.selected_option?.value || "{}");
190
- return {
191
- agentId: agentData.agentId || "",
192
- projectId: agentData.projectId || "",
193
- question: questionValue?.value || "",
194
- isEphemeral: visibilityValue?.selected_options?.some((o) => o.value === "ephemeral") || false,
195
- metadata
196
- };
197
- } catch {
198
- return null;
199
- }
200
- }
201
-
202
- //#endregion
203
- export { buildAgentSelectorModal, parseModalSubmission };
@@ -1,82 +0,0 @@
1
- import { Nango } from "@nangohq/node";
2
-
3
- //#region src/slack/services/nango.d.ts
4
-
5
- declare function getSlackNango(): Nango;
6
- declare function getSlackIntegrationId(): string;
7
- declare function createConnectSession(params: {
8
- userId: string;
9
- userEmail?: string;
10
- userName?: string;
11
- tenantId: string;
12
- }): Promise<{
13
- sessionToken: string;
14
- } | null>;
15
- declare function getConnectionAccessToken(connectionId: string): Promise<string | null>;
16
- interface DefaultAgentConfig {
17
- agentId: string;
18
- agentName?: string;
19
- projectId: string;
20
- projectName?: string;
21
- }
22
- interface SlackWorkspaceConnection {
23
- connectionId: string;
24
- teamId: string;
25
- teamName?: string;
26
- botToken: string;
27
- tenantId: string;
28
- defaultAgent?: DefaultAgentConfig;
29
- }
30
- /**
31
- * Find a workspace connection by Slack team ID.
32
- * Used for @mentions where any user can trigger the bot.
33
- * Returns the bot token for the workspace.
34
- */
35
- declare function findWorkspaceConnectionByTeamId(teamId: string): Promise<SlackWorkspaceConnection | null>;
36
- declare function updateConnectionMetadata(connectionId: string, metadata: Record<string, string>): Promise<boolean>;
37
- declare function setWorkspaceDefaultAgent(teamId: string, defaultAgent: DefaultAgentConfig | null): Promise<boolean>;
38
- declare function getWorkspaceDefaultAgentFromNango(teamId: string): Promise<DefaultAgentConfig | null>;
39
- /**
40
- * Compute a stable, deterministic connection ID for a Slack workspace.
41
- * Format: "T:<team_id>" or "E:<enterprise_id>:T:<team_id>" for Enterprise Grid
42
- */
43
- declare function computeWorkspaceConnectionId(params: {
44
- teamId: string;
45
- enterpriseId?: string;
46
- }): string;
47
- interface WorkspaceInstallData {
48
- teamId: string;
49
- teamName?: string;
50
- teamDomain?: string;
51
- enterpriseId?: string;
52
- enterpriseName?: string;
53
- botUserId?: string;
54
- botToken: string;
55
- botScopes?: string;
56
- installerUserId?: string;
57
- installerUserName?: string;
58
- isEnterpriseInstall?: boolean;
59
- appId?: string;
60
- tenantId?: string;
61
- workspaceUrl?: string;
62
- workspaceIconUrl?: string;
63
- installationSource?: string;
64
- }
65
- /**
66
- * Store a workspace installation in Nango.
67
- * Uses upsert semantics - will update if the connection already exists.
68
- */
69
- declare function storeWorkspaceInstallation(data: WorkspaceInstallData): Promise<{
70
- connectionId: string;
71
- success: boolean;
72
- }>;
73
- /**
74
- * List all workspace installations from Nango.
75
- */
76
- declare function listWorkspaceInstallations(): Promise<SlackWorkspaceConnection[]>;
77
- /**
78
- * Delete a workspace installation from Nango.
79
- */
80
- declare function deleteWorkspaceInstallation(connectionId: string): Promise<boolean>;
81
- //#endregion
82
- export { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata };
@@ -1,326 +0,0 @@
1
- import { env } from "../../env.js";
2
- import { getLogger } from "../../logger.js";
3
- import { Nango } from "@nangohq/node";
4
-
5
- //#region src/slack/services/nango.ts
6
- /**
7
- * Nango Service for Slack OAuth Token Management
8
- *
9
- * ARCHITECTURE NOTE: PostgreSQL is the authoritative source of truth for:
10
- * - User linking data (work_app_slack_user_mappings table)
11
- * - User settings/preferences (work_app_slack_user_settings table)
12
- * - Workspace metadata (work_app_slack_workspaces table)
13
- *
14
- * Nango is used ONLY for:
15
- * - OAuth token storage and refresh (bot tokens for workspaces)
16
- * - OAuth flow management (createConnectSession)
17
- *
18
- * For user data, use the PostgreSQL data access layer:
19
- * @see packages/agents-core/src/data-access/runtime/workAppSlack.ts
20
- */
21
- const logger = getLogger("slack-nango");
22
- function getSlackNango() {
23
- const secretKey = env.NANGO_SLACK_SECRET_KEY || env.NANGO_SECRET_KEY;
24
- if (!secretKey) throw new Error("NANGO_SLACK_SECRET_KEY or NANGO_SECRET_KEY is required for Slack integration");
25
- return new Nango({ secretKey });
26
- }
27
- function getSlackIntegrationId() {
28
- return env.NANGO_SLACK_INTEGRATION_ID || "slack-agent";
29
- }
30
- async function createConnectSession(params) {
31
- try {
32
- const nango = getSlackNango();
33
- const integrationId = getSlackIntegrationId();
34
- const session = await nango.createConnectSession({
35
- end_user: {
36
- id: params.userId,
37
- email: params.userEmail,
38
- display_name: params.userName
39
- },
40
- organization: {
41
- id: params.tenantId,
42
- display_name: params.tenantId
43
- },
44
- allowed_integrations: [integrationId]
45
- });
46
- logger.info({
47
- userId: params.userId,
48
- userEmail: params.userEmail,
49
- integrationId
50
- }, "Created Nango connect session");
51
- return { sessionToken: session.data.token };
52
- } catch (error) {
53
- logger.error({ error }, "Failed to create Nango connect session");
54
- return null;
55
- }
56
- }
57
- async function getConnectionAccessToken(connectionId) {
58
- try {
59
- const nango = getSlackNango();
60
- const integrationId = getSlackIntegrationId();
61
- return (await nango.getConnection(integrationId, connectionId)).credentials?.access_token || null;
62
- } catch (error) {
63
- logger.error({
64
- error,
65
- connectionId
66
- }, "Failed to get connection access token");
67
- return null;
68
- }
69
- }
70
- /**
71
- * Find a workspace connection by Slack team ID.
72
- * Used for @mentions where any user can trigger the bot.
73
- * Returns the bot token for the workspace.
74
- */
75
- async function findWorkspaceConnectionByTeamId(teamId) {
76
- try {
77
- const nango = getSlackNango();
78
- const integrationId = getSlackIntegrationId();
79
- const connections = await nango.listConnections();
80
- for (const conn of connections.connections) if (conn.provider_config_key === integrationId) try {
81
- const fullConn = await nango.getConnection(integrationId, conn.connection_id);
82
- const connectionConfig = fullConn.connection_config;
83
- const metadata = fullConn.metadata;
84
- const credentials = fullConn;
85
- if ((connectionConfig?.["team.id"] || metadata?.slack_team_id) === teamId && credentials.credentials?.access_token) {
86
- let defaultAgent;
87
- if (metadata?.default_agent) try {
88
- defaultAgent = JSON.parse(metadata.default_agent);
89
- } catch {}
90
- return {
91
- connectionId: conn.connection_id,
92
- teamId,
93
- teamName: metadata?.slack_team_name,
94
- botToken: credentials.credentials.access_token,
95
- tenantId: metadata?.tenant_id || "default",
96
- defaultAgent
97
- };
98
- }
99
- } catch {}
100
- return null;
101
- } catch (error) {
102
- logger.error({
103
- error,
104
- teamId
105
- }, "Failed to find workspace connection by team ID");
106
- return null;
107
- }
108
- }
109
- async function updateConnectionMetadata(connectionId, metadata) {
110
- try {
111
- const nango = getSlackNango();
112
- const integrationId = getSlackIntegrationId();
113
- await nango.updateMetadata(integrationId, connectionId, metadata);
114
- return true;
115
- } catch (error) {
116
- logger.error({
117
- error,
118
- connectionId
119
- }, "Failed to update connection metadata");
120
- return false;
121
- }
122
- }
123
- async function setWorkspaceDefaultAgent(teamId, defaultAgent) {
124
- try {
125
- const workspace = await findWorkspaceConnectionByTeamId(teamId);
126
- if (!workspace) {
127
- logger.warn({ teamId }, "No workspace connection found to set default agent");
128
- return false;
129
- }
130
- return updateConnectionMetadata(workspace.connectionId, { default_agent: defaultAgent ? JSON.stringify(defaultAgent) : "" });
131
- } catch (error) {
132
- logger.error({
133
- error,
134
- teamId
135
- }, "Failed to set workspace default agent");
136
- return false;
137
- }
138
- }
139
- async function getWorkspaceDefaultAgentFromNango(teamId) {
140
- try {
141
- return (await findWorkspaceConnectionByTeamId(teamId))?.defaultAgent || null;
142
- } catch (error) {
143
- logger.error({
144
- error,
145
- teamId
146
- }, "Failed to get workspace default agent");
147
- return null;
148
- }
149
- }
150
- /**
151
- * Compute a stable, deterministic connection ID for a Slack workspace.
152
- * Format: "T:<team_id>" or "E:<enterprise_id>:T:<team_id>" for Enterprise Grid
153
- */
154
- function computeWorkspaceConnectionId(params) {
155
- const { teamId, enterpriseId } = params;
156
- if (enterpriseId) return `E:${enterpriseId}:T:${teamId}`;
157
- return `T:${teamId}`;
158
- }
159
- /**
160
- * Store a workspace installation in Nango.
161
- * Uses upsert semantics - will update if the connection already exists.
162
- */
163
- async function storeWorkspaceInstallation(data) {
164
- const connectionId = computeWorkspaceConnectionId({
165
- teamId: data.teamId,
166
- enterpriseId: data.enterpriseId
167
- });
168
- try {
169
- const integrationId = getSlackIntegrationId();
170
- const secretKey = env.NANGO_SLACK_SECRET_KEY || env.NANGO_SECRET_KEY;
171
- if (!secretKey) {
172
- logger.error({}, "No Nango secret key available");
173
- return {
174
- connectionId,
175
- success: false
176
- };
177
- }
178
- const nangoApiUrl = env.NANGO_SERVER_URL || "https://api.nango.dev";
179
- logger.info({
180
- integrationId,
181
- connectionId,
182
- teamId: data.teamId,
183
- teamName: data.teamName
184
- }, "Importing connection to Nango");
185
- const displayName = data.enterpriseName ? `${data.teamName || data.teamId} (${data.enterpriseName})` : data.teamName || data.teamId;
186
- const workspaceUrl = data.workspaceUrl || (data.teamDomain ? `https://${data.teamDomain}.slack.com` : "");
187
- const now = (/* @__PURE__ */ new Date()).toISOString();
188
- const requestBody = {
189
- provider_config_key: integrationId,
190
- connection_id: connectionId,
191
- credentials: {
192
- type: "OAUTH2",
193
- access_token: data.botToken
194
- },
195
- metadata: {
196
- display_name: displayName,
197
- connection_type: "workspace",
198
- slack_team_id: data.teamId,
199
- slack_team_name: data.teamName || "",
200
- slack_team_domain: data.teamDomain || "",
201
- slack_workspace_url: workspaceUrl,
202
- slack_workspace_icon_url: data.workspaceIconUrl || "",
203
- slack_enterprise_id: data.enterpriseId || "",
204
- slack_enterprise_name: data.enterpriseName || "",
205
- is_enterprise_install: String(data.isEnterpriseInstall || false),
206
- slack_bot_user_id: data.botUserId || "",
207
- slack_bot_scopes: data.botScopes || "",
208
- slack_app_id: data.appId || "",
209
- installed_by_slack_user_id: data.installerUserId || "",
210
- installed_by_slack_user_name: data.installerUserName || "",
211
- installed_at: now,
212
- last_updated_at: now,
213
- installation_source: data.installationSource || "dashboard",
214
- inkeep_tenant_id: data.tenantId || "default",
215
- status: "active"
216
- },
217
- connection_config: { "team.id": data.teamId }
218
- };
219
- const response = await fetch(`${nangoApiUrl}/connections`, {
220
- method: "POST",
221
- headers: {
222
- Authorization: `Bearer ${secretKey}`,
223
- "Content-Type": "application/json"
224
- },
225
- body: JSON.stringify(requestBody)
226
- });
227
- const responseText = await response.text();
228
- if (!response.ok) {
229
- logger.error({
230
- status: response.status,
231
- errorBody: responseText,
232
- connectionId
233
- }, "Failed to import connection to Nango");
234
- return {
235
- connectionId,
236
- success: false
237
- };
238
- }
239
- logger.info({
240
- connectionId,
241
- teamId: data.teamId,
242
- teamName: data.teamName
243
- }, "Stored workspace installation in Nango");
244
- return {
245
- connectionId,
246
- success: true
247
- };
248
- } catch (error) {
249
- logger.error({
250
- error,
251
- connectionId,
252
- teamId: data.teamId
253
- }, "Failed to store workspace installation");
254
- return {
255
- connectionId,
256
- success: false
257
- };
258
- }
259
- }
260
- /**
261
- * List all workspace installations from Nango.
262
- */
263
- async function listWorkspaceInstallations() {
264
- try {
265
- const nango = getSlackNango();
266
- const integrationId = getSlackIntegrationId();
267
- const connections = await nango.listConnections();
268
- const workspaces = [];
269
- for (const conn of connections.connections) if (conn.provider_config_key === integrationId) try {
270
- const fullConn = await nango.getConnection(integrationId, conn.connection_id);
271
- const metadata = fullConn.metadata;
272
- const credentials = fullConn;
273
- if (metadata?.connection_type === "workspace" && credentials.credentials?.access_token) {
274
- let defaultAgent;
275
- if (metadata?.default_agent) try {
276
- defaultAgent = JSON.parse(metadata.default_agent);
277
- } catch {}
278
- workspaces.push({
279
- connectionId: conn.connection_id,
280
- teamId: metadata.slack_team_id || "",
281
- teamName: metadata.slack_team_name,
282
- botToken: credentials.credentials.access_token,
283
- tenantId: metadata.tenant_id || "default",
284
- defaultAgent
285
- });
286
- }
287
- } catch {}
288
- return workspaces;
289
- } catch (error) {
290
- logger.error({ error }, "Failed to list workspace installations");
291
- return [];
292
- }
293
- }
294
- /**
295
- * Delete a workspace installation from Nango.
296
- */
297
- async function deleteWorkspaceInstallation(connectionId) {
298
- try {
299
- const nango = getSlackNango();
300
- const integrationId = getSlackIntegrationId();
301
- logger.info({
302
- connectionId,
303
- integrationId
304
- }, "Attempting to delete workspace installation");
305
- await nango.deleteConnection(integrationId, connectionId);
306
- logger.info({ connectionId }, "Deleted workspace installation from Nango");
307
- return true;
308
- } catch (error) {
309
- const errorObj = error;
310
- const errorMessage = errorObj?.message || String(error);
311
- const statusCode = errorObj?.status;
312
- if (statusCode === 404 || errorMessage.includes("404") || errorMessage.includes("not found")) {
313
- logger.warn({ connectionId }, "Connection not found in Nango, treating as already deleted");
314
- return true;
315
- }
316
- logger.error({
317
- error: errorMessage,
318
- statusCode,
319
- connectionId
320
- }, "Failed to delete workspace installation");
321
- return false;
322
- }
323
- }
324
-
325
- //#endregion
326
- export { computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata };
@@ -1,35 +0,0 @@
1
- //#region src/slack/services/security.d.ts
2
- /**
3
- * Slack Security Utilities
4
- *
5
- * Provides security functions for verifying Slack requests and parsing payloads.
6
- * All incoming Slack requests are verified using HMAC-SHA256 signatures.
7
- */
8
- /**
9
- * Verify that a request originated from Slack using HMAC-SHA256 signature.
10
- *
11
- * @param signingSecret - The Slack signing secret from app settings
12
- * @param requestBody - The raw request body string
13
- * @param timestamp - The X-Slack-Request-Timestamp header value
14
- * @param signature - The X-Slack-Signature header value
15
- * @returns true if the signature is valid, false otherwise
16
- */
17
- declare function verifySlackRequest(signingSecret: string, requestBody: string, timestamp: string, signature: string): boolean;
18
- /**
19
- * Parse a URL-encoded Slack command body into key-value pairs.
20
- *
21
- * @param body - The URL-encoded request body from a slash command
22
- * @returns Parsed parameters as a string record
23
- */
24
- declare function parseSlackCommandBody(body: string): Record<string, string>;
25
- /**
26
- * Parse a Slack event body based on content type.
27
- * Handles both JSON and URL-encoded payloads (for interactive components).
28
- *
29
- * @param body - The raw request body
30
- * @param contentType - The Content-Type header value
31
- * @returns Parsed event payload
32
- */
33
- declare function parseSlackEventBody(body: string, contentType: string): Record<string, unknown>;
34
- //#endregion
35
- export { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest };
@@ -1,65 +0,0 @@
1
- import { getLogger } from "../../logger.js";
2
- import crypto from "node:crypto";
3
-
4
- //#region src/slack/services/security.ts
5
- /**
6
- * Slack Security Utilities
7
- *
8
- * Provides security functions for verifying Slack requests and parsing payloads.
9
- * All incoming Slack requests are verified using HMAC-SHA256 signatures.
10
- */
11
- const logger = getLogger("slack-security");
12
- /**
13
- * Verify that a request originated from Slack using HMAC-SHA256 signature.
14
- *
15
- * @param signingSecret - The Slack signing secret from app settings
16
- * @param requestBody - The raw request body string
17
- * @param timestamp - The X-Slack-Request-Timestamp header value
18
- * @param signature - The X-Slack-Signature header value
19
- * @returns true if the signature is valid, false otherwise
20
- */
21
- function verifySlackRequest(signingSecret, requestBody, timestamp, signature) {
22
- try {
23
- const fiveMinutesAgo = Math.floor(Date.now() / 1e3) - 300;
24
- if (Number.parseInt(timestamp, 10) < fiveMinutesAgo) {
25
- logger.warn({}, "Slack request timestamp too old");
26
- return false;
27
- }
28
- const sigBaseString = `v0:${timestamp}:${requestBody}`;
29
- const mySignature = `v0=${crypto.createHmac("sha256", signingSecret).update(sigBaseString).digest("hex")}`;
30
- return crypto.timingSafeEqual(Buffer.from(mySignature), Buffer.from(signature));
31
- } catch (error) {
32
- logger.error({ error }, "Error verifying Slack request");
33
- return false;
34
- }
35
- }
36
- /**
37
- * Parse a URL-encoded Slack command body into key-value pairs.
38
- *
39
- * @param body - The URL-encoded request body from a slash command
40
- * @returns Parsed parameters as a string record
41
- */
42
- function parseSlackCommandBody(body) {
43
- const params = new URLSearchParams(body);
44
- return Object.fromEntries(params.entries());
45
- }
46
- /**
47
- * Parse a Slack event body based on content type.
48
- * Handles both JSON and URL-encoded payloads (for interactive components).
49
- *
50
- * @param body - The raw request body
51
- * @param contentType - The Content-Type header value
52
- * @returns Parsed event payload
53
- */
54
- function parseSlackEventBody(body, contentType) {
55
- if (contentType.includes("application/x-www-form-urlencoded")) {
56
- const params = new URLSearchParams(body);
57
- const payload = params.get("payload");
58
- if (payload) return JSON.parse(payload);
59
- return Object.fromEntries(params.entries());
60
- }
61
- return JSON.parse(body);
62
- }
63
-
64
- //#endregion
65
- export { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest };