@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.
- package/dist/db/index.d.ts +1 -2
- package/dist/db/index.js +1 -2
- package/dist/db/runDbClient.d.ts +2 -2
- package/dist/env.d.ts +2 -24
- package/dist/env.js +1 -12
- package/dist/github/routes/setup.d.ts +2 -2
- package/dist/github/routes/tokenExchange.d.ts +2 -2
- package/package.json +2 -10
- package/dist/db/manageDbClient.d.ts +0 -7
- package/dist/db/manageDbClient.js +0 -16
- package/dist/slack/index.d.ts +0 -19
- package/dist/slack/index.js +0 -29
- package/dist/slack/middleware/permissions.d.ts +0 -16
- package/dist/slack/middleware/permissions.js +0 -49
- package/dist/slack/routes/events.d.ts +0 -10
- package/dist/slack/routes/events.js +0 -319
- package/dist/slack/routes/index.d.ts +0 -11
- package/dist/slack/routes/index.js +0 -64
- package/dist/slack/routes/internal.d.ts +0 -10
- package/dist/slack/routes/internal.js +0 -107
- package/dist/slack/routes/oauth.d.ts +0 -12
- package/dist/slack/routes/oauth.js +0 -218
- package/dist/slack/routes/resources.d.ts +0 -10
- package/dist/slack/routes/resources.js +0 -163
- package/dist/slack/routes/users.d.ts +0 -15
- package/dist/slack/routes/users.js +0 -430
- package/dist/slack/routes/workspaces.d.ts +0 -10
- package/dist/slack/routes/workspaces.js +0 -828
- package/dist/slack/routes.d.ts +0 -7
- package/dist/slack/routes.js +0 -12
- package/dist/slack/services/agent-resolution.d.ts +0 -49
- package/dist/slack/services/agent-resolution.js +0 -135
- package/dist/slack/services/api-client.d.ts +0 -161
- package/dist/slack/services/api-client.js +0 -248
- package/dist/slack/services/auth/index.d.ts +0 -61
- package/dist/slack/services/auth/index.js +0 -164
- package/dist/slack/services/blocks/index.d.ts +0 -60
- package/dist/slack/services/blocks/index.js +0 -143
- package/dist/slack/services/client.d.ts +0 -78
- package/dist/slack/services/client.js +0 -152
- package/dist/slack/services/commands/index.d.ts +0 -15
- package/dist/slack/services/commands/index.js +0 -556
- package/dist/slack/services/events/app-mention.d.ts +0 -41
- package/dist/slack/services/events/app-mention.js +0 -212
- package/dist/slack/services/events/block-actions.d.ts +0 -47
- package/dist/slack/services/events/block-actions.js +0 -287
- package/dist/slack/services/events/index.d.ts +0 -6
- package/dist/slack/services/events/index.js +0 -7
- package/dist/slack/services/events/modal-submission.d.ts +0 -12
- package/dist/slack/services/events/modal-submission.js +0 -279
- package/dist/slack/services/events/streaming.d.ts +0 -27
- package/dist/slack/services/events/streaming.js +0 -285
- package/dist/slack/services/events/utils.d.ts +0 -129
- package/dist/slack/services/events/utils.js +0 -315
- package/dist/slack/services/index.d.ts +0 -18
- package/dist/slack/services/index.js +0 -18
- package/dist/slack/services/modals.d.ts +0 -67
- package/dist/slack/services/modals.js +0 -203
- package/dist/slack/services/nango.d.ts +0 -82
- package/dist/slack/services/nango.js +0 -326
- package/dist/slack/services/security.d.ts +0 -35
- package/dist/slack/services/security.js +0 -65
- package/dist/slack/services/types.d.ts +0 -26
- package/dist/slack/services/types.js +0 -1
- package/dist/slack/services/workspace-tokens.d.ts +0 -37
- package/dist/slack/services/workspace-tokens.js +0 -39
- package/dist/slack/types.d.ts +0 -10
- 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 };
|