@inkeep/agents-work-apps 0.0.0-dev-20260211191741 → 0.0.0-dev-20260211220939
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/env.d.ts +24 -2
- package/dist/env.js +13 -2
- package/dist/github/index.d.ts +3 -3
- package/dist/github/mcp/index.d.ts +2 -2
- package/dist/github/routes/setup.d.ts +2 -2
- package/dist/github/routes/tokenExchange.d.ts +2 -2
- package/dist/github/routes/webhooks.d.ts +2 -2
- package/dist/slack/i18n/index.d.ts +2 -0
- package/dist/slack/i18n/index.js +3 -0
- package/dist/slack/i18n/strings.d.ts +73 -0
- package/dist/slack/i18n/strings.js +67 -0
- package/dist/slack/index.d.ts +18 -0
- package/dist/slack/index.js +28 -0
- package/dist/slack/middleware/permissions.d.ts +31 -0
- package/dist/slack/middleware/permissions.js +159 -0
- package/dist/slack/routes/events.d.ts +10 -0
- package/dist/slack/routes/events.js +390 -0
- package/dist/slack/routes/index.d.ts +10 -0
- package/dist/slack/routes/index.js +47 -0
- package/dist/slack/routes/oauth.d.ts +20 -0
- package/dist/slack/routes/oauth.js +325 -0
- package/dist/slack/routes/users.d.ts +10 -0
- package/dist/slack/routes/users.js +358 -0
- package/dist/slack/routes/workspaces.d.ts +10 -0
- package/dist/slack/routes/workspaces.js +875 -0
- package/dist/slack/services/agent-resolution.d.ts +41 -0
- package/dist/slack/services/agent-resolution.js +99 -0
- package/dist/slack/services/blocks/index.d.ts +73 -0
- package/dist/slack/services/blocks/index.js +103 -0
- package/dist/slack/services/client.d.ts +105 -0
- package/dist/slack/services/client.js +220 -0
- package/dist/slack/services/commands/index.d.ts +19 -0
- package/dist/slack/services/commands/index.js +538 -0
- package/dist/slack/services/events/app-mention.d.ts +40 -0
- package/dist/slack/services/events/app-mention.js +234 -0
- package/dist/slack/services/events/block-actions.d.ts +40 -0
- package/dist/slack/services/events/block-actions.js +221 -0
- package/dist/slack/services/events/index.d.ts +6 -0
- package/dist/slack/services/events/index.js +7 -0
- package/dist/slack/services/events/modal-submission.d.ts +30 -0
- package/dist/slack/services/events/modal-submission.js +346 -0
- package/dist/slack/services/events/streaming.d.ts +26 -0
- package/dist/slack/services/events/streaming.js +228 -0
- package/dist/slack/services/events/utils.d.ts +146 -0
- package/dist/slack/services/events/utils.js +369 -0
- package/dist/slack/services/index.d.ts +16 -0
- package/dist/slack/services/index.js +16 -0
- package/dist/slack/services/modals.d.ts +86 -0
- package/dist/slack/services/modals.js +355 -0
- package/dist/slack/services/nango.d.ts +85 -0
- package/dist/slack/services/nango.js +462 -0
- package/dist/slack/services/security.d.ts +35 -0
- package/dist/slack/services/security.js +65 -0
- package/dist/slack/services/types.d.ts +26 -0
- package/dist/slack/services/types.js +1 -0
- package/dist/slack/services/workspace-tokens.d.ts +25 -0
- package/dist/slack/services/workspace-tokens.js +27 -0
- package/dist/slack/types.d.ts +10 -0
- package/dist/slack/types.js +1 -0
- package/package.json +10 -2
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { env } from "../../env.js";
|
|
2
|
+
import { getLogger } from "../../logger.js";
|
|
3
|
+
import runDbClient_default from "../../db/runDbClient.js";
|
|
4
|
+
import { findWorkspaceConnectionByTeamId, getSlackIntegrationId, getSlackNango, updateConnectionMetadata } from "../services/nango.js";
|
|
5
|
+
import { getSlackClient, getSlackUserInfo } from "../services/client.js";
|
|
6
|
+
import { sendResponseUrlMessage } from "../services/events/utils.js";
|
|
7
|
+
import { handleCommand } from "../services/commands/index.js";
|
|
8
|
+
import { handleAppMention } from "../services/events/app-mention.js";
|
|
9
|
+
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "../services/events/block-actions.js";
|
|
10
|
+
import { handleFollowUpSubmission, handleModalSubmission } from "../services/events/modal-submission.js";
|
|
11
|
+
import "../services/events/index.js";
|
|
12
|
+
import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "../services/security.js";
|
|
13
|
+
import "../services/index.js";
|
|
14
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
15
|
+
import { deleteAllWorkAppSlackChannelAgentConfigsByTeam, deleteAllWorkAppSlackUserMappingsByTeam, deleteWorkAppSlackWorkspaceByNangoConnectionId } from "@inkeep/agents-core";
|
|
16
|
+
|
|
17
|
+
//#region src/slack/routes/events.ts
|
|
18
|
+
/**
|
|
19
|
+
* Slack Events Routes
|
|
20
|
+
*
|
|
21
|
+
* Endpoints for handling Slack events, commands, and webhooks:
|
|
22
|
+
* - POST /commands - Handle /inkeep slash commands
|
|
23
|
+
* - POST /events - Handle Slack events & interactivity
|
|
24
|
+
* - POST /nango-webhook - Handle Nango auth webhooks
|
|
25
|
+
*/
|
|
26
|
+
const logger = getLogger("slack-events");
|
|
27
|
+
const app = new OpenAPIHono();
|
|
28
|
+
app.post("/commands", async (c) => {
|
|
29
|
+
const body = await c.req.text();
|
|
30
|
+
const timestamp = c.req.header("x-slack-request-timestamp") || "";
|
|
31
|
+
const signature = c.req.header("x-slack-signature") || "";
|
|
32
|
+
if (!env.SLACK_SIGNING_SECRET) {
|
|
33
|
+
logger.error({}, "SLACK_SIGNING_SECRET not configured - rejecting request");
|
|
34
|
+
return c.json({
|
|
35
|
+
response_type: "ephemeral",
|
|
36
|
+
text: "Server configuration error"
|
|
37
|
+
}, 500);
|
|
38
|
+
}
|
|
39
|
+
if (!verifySlackRequest(env.SLACK_SIGNING_SECRET, body, timestamp, signature)) {
|
|
40
|
+
logger.error({}, "Invalid Slack request signature");
|
|
41
|
+
return c.json({
|
|
42
|
+
response_type: "ephemeral",
|
|
43
|
+
text: "Invalid request signature"
|
|
44
|
+
}, 401);
|
|
45
|
+
}
|
|
46
|
+
const params = parseSlackCommandBody(body);
|
|
47
|
+
const response = await handleCommand({
|
|
48
|
+
command: params.command || "",
|
|
49
|
+
text: params.text || "",
|
|
50
|
+
userId: params.user_id || "",
|
|
51
|
+
userName: params.user_name || "",
|
|
52
|
+
teamId: params.team_id || "",
|
|
53
|
+
teamDomain: params.team_domain || "",
|
|
54
|
+
enterpriseId: params.enterprise_id,
|
|
55
|
+
channelId: params.channel_id || "",
|
|
56
|
+
channelName: params.channel_name || "",
|
|
57
|
+
responseUrl: params.response_url || "",
|
|
58
|
+
triggerId: params.trigger_id || ""
|
|
59
|
+
});
|
|
60
|
+
if (Object.keys(response).length === 0) return c.body(null, 200);
|
|
61
|
+
return c.json(response);
|
|
62
|
+
});
|
|
63
|
+
app.post("/events", async (c) => {
|
|
64
|
+
const contentType = c.req.header("content-type") || "";
|
|
65
|
+
const body = await c.req.text();
|
|
66
|
+
const timestamp = c.req.header("x-slack-request-timestamp") || "";
|
|
67
|
+
const signature = c.req.header("x-slack-signature") || "";
|
|
68
|
+
let eventBody;
|
|
69
|
+
try {
|
|
70
|
+
eventBody = parseSlackEventBody(body, contentType);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.error({
|
|
73
|
+
error,
|
|
74
|
+
contentType,
|
|
75
|
+
bodyPreview: body.slice(0, 200)
|
|
76
|
+
}, "Failed to parse Slack event body");
|
|
77
|
+
return c.json({ error: "Invalid payload" }, 400);
|
|
78
|
+
}
|
|
79
|
+
logger.debug({ eventType: eventBody.type }, "Slack event received");
|
|
80
|
+
const eventType = eventBody.type;
|
|
81
|
+
if (eventType === "url_verification") {
|
|
82
|
+
logger.info({}, "Responding to Slack URL verification challenge");
|
|
83
|
+
return c.text(String(eventBody.challenge));
|
|
84
|
+
}
|
|
85
|
+
if (!env.SLACK_SIGNING_SECRET) {
|
|
86
|
+
logger.error({}, "SLACK_SIGNING_SECRET not configured - rejecting request");
|
|
87
|
+
return c.json({ error: "Server configuration error" }, 500);
|
|
88
|
+
}
|
|
89
|
+
if (!verifySlackRequest(env.SLACK_SIGNING_SECRET, body, timestamp, signature)) {
|
|
90
|
+
logger.error({}, "Invalid Slack request signature");
|
|
91
|
+
return c.json({ error: "Invalid request signature" }, 401);
|
|
92
|
+
}
|
|
93
|
+
if (eventType === "event_callback") {
|
|
94
|
+
const teamId = eventBody.team_id;
|
|
95
|
+
const event = eventBody.event;
|
|
96
|
+
if (event?.bot_id || event?.subtype === "bot_message") {
|
|
97
|
+
logger.debug({ botId: event.bot_id }, "Ignoring bot message");
|
|
98
|
+
return c.json({ ok: true });
|
|
99
|
+
}
|
|
100
|
+
logger.debug({
|
|
101
|
+
eventType: event?.type,
|
|
102
|
+
teamId
|
|
103
|
+
}, "Slack event callback");
|
|
104
|
+
if (event?.type === "app_mention" && event.channel && event.user && teamId) {
|
|
105
|
+
const question = (event.text || "").replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
106
|
+
logger.info({
|
|
107
|
+
userId: event.user,
|
|
108
|
+
channel: event.channel,
|
|
109
|
+
teamId
|
|
110
|
+
}, "Bot was mentioned");
|
|
111
|
+
handleAppMention({
|
|
112
|
+
slackUserId: event.user,
|
|
113
|
+
channel: event.channel,
|
|
114
|
+
text: question,
|
|
115
|
+
threadTs: event.thread_ts || event.ts || "",
|
|
116
|
+
messageTs: event.ts || "",
|
|
117
|
+
teamId
|
|
118
|
+
}).catch((err) => {
|
|
119
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
120
|
+
const errorStack = err instanceof Error ? err.stack : void 0;
|
|
121
|
+
logger.error({
|
|
122
|
+
errorMessage,
|
|
123
|
+
errorStack
|
|
124
|
+
}, "Failed to handle app mention (outer catch)");
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (eventType === "block_actions" || eventType === "interactive_message") {
|
|
129
|
+
logger.debug({ eventType }, "Slack interactive event received");
|
|
130
|
+
const actions = eventBody.actions;
|
|
131
|
+
const teamId = eventBody.team?.id;
|
|
132
|
+
const responseUrl = eventBody.response_url;
|
|
133
|
+
const triggerId = eventBody.trigger_id;
|
|
134
|
+
if (actions && teamId) for (const action of actions) {
|
|
135
|
+
if (action.action_id === "open_agent_selector_modal" && action.value && triggerId) handleOpenAgentSelectorModal({
|
|
136
|
+
triggerId,
|
|
137
|
+
actionValue: action.value,
|
|
138
|
+
teamId,
|
|
139
|
+
responseUrl: responseUrl || ""
|
|
140
|
+
}).catch(async (err) => {
|
|
141
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
142
|
+
logger.error({
|
|
143
|
+
errorMessage,
|
|
144
|
+
actionId: action.action_id
|
|
145
|
+
}, "Failed to open agent selector modal");
|
|
146
|
+
if (responseUrl) await sendResponseUrlMessage(responseUrl, {
|
|
147
|
+
text: "Sorry, something went wrong while opening the agent selector. Please try again.",
|
|
148
|
+
response_type: "ephemeral"
|
|
149
|
+
}).catch(() => {});
|
|
150
|
+
});
|
|
151
|
+
if (action.action_id === "modal_project_select") {
|
|
152
|
+
const selectedProjectId = action.selected_option?.value;
|
|
153
|
+
const view = eventBody.view;
|
|
154
|
+
if (selectedProjectId && view?.id) (async () => {
|
|
155
|
+
try {
|
|
156
|
+
const metadata = JSON.parse(view.private_metadata || "{}");
|
|
157
|
+
const tenantId = metadata.tenantId || "default";
|
|
158
|
+
const workspace = await findWorkspaceConnectionByTeamId(teamId);
|
|
159
|
+
if (!workspace?.botToken) return;
|
|
160
|
+
const slackClient = getSlackClient(workspace.botToken);
|
|
161
|
+
const { fetchProjectsForTenant, fetchAgentsForProject } = await import("../services/events/utils.js");
|
|
162
|
+
const { buildAgentSelectorModal, buildMessageShortcutModal } = await import("../services/modals.js");
|
|
163
|
+
const projectList = await fetchProjectsForTenant(tenantId);
|
|
164
|
+
const agentList = await fetchAgentsForProject(tenantId, selectedProjectId);
|
|
165
|
+
const agentOptions = agentList.map((a) => ({
|
|
166
|
+
id: a.id,
|
|
167
|
+
name: a.name,
|
|
168
|
+
projectId: a.projectId,
|
|
169
|
+
projectName: a.projectName || a.projectId
|
|
170
|
+
}));
|
|
171
|
+
const modal = metadata.messageContext ? buildMessageShortcutModal({
|
|
172
|
+
projects: projectList,
|
|
173
|
+
agents: agentOptions,
|
|
174
|
+
metadata,
|
|
175
|
+
selectedProjectId,
|
|
176
|
+
messageContext: metadata.messageContext
|
|
177
|
+
}) : buildAgentSelectorModal({
|
|
178
|
+
projects: projectList,
|
|
179
|
+
agents: agentOptions,
|
|
180
|
+
metadata,
|
|
181
|
+
selectedProjectId
|
|
182
|
+
});
|
|
183
|
+
await slackClient.views.update({
|
|
184
|
+
view_id: view.id,
|
|
185
|
+
view: modal
|
|
186
|
+
});
|
|
187
|
+
logger.debug({
|
|
188
|
+
selectedProjectId,
|
|
189
|
+
agentCount: agentList.length
|
|
190
|
+
}, "Updated modal with agents for selected project");
|
|
191
|
+
} catch (err) {
|
|
192
|
+
logger.error({
|
|
193
|
+
err,
|
|
194
|
+
selectedProjectId
|
|
195
|
+
}, "Failed to update modal on project change");
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
}
|
|
199
|
+
if (action.action_id === "open_follow_up_modal" && action.value && triggerId) handleOpenFollowUpModal({
|
|
200
|
+
triggerId,
|
|
201
|
+
actionValue: action.value,
|
|
202
|
+
teamId,
|
|
203
|
+
responseUrl: responseUrl || void 0
|
|
204
|
+
}).catch((err) => {
|
|
205
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
206
|
+
logger.error({
|
|
207
|
+
errorMessage,
|
|
208
|
+
actionId: action.action_id
|
|
209
|
+
}, "Failed to open follow-up modal");
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (eventType === "message_action") {
|
|
214
|
+
const callbackId = eventBody.callback_id;
|
|
215
|
+
if (callbackId === "ask_agent_shortcut") {
|
|
216
|
+
const triggerId = eventBody.trigger_id;
|
|
217
|
+
const teamId = eventBody.team?.id;
|
|
218
|
+
const channelId = eventBody.channel?.id;
|
|
219
|
+
const userId = eventBody.user?.id;
|
|
220
|
+
const message = eventBody.message;
|
|
221
|
+
const responseUrl = eventBody.response_url;
|
|
222
|
+
if (triggerId && teamId && channelId && userId && message?.ts) handleMessageShortcut({
|
|
223
|
+
triggerId,
|
|
224
|
+
teamId,
|
|
225
|
+
channelId,
|
|
226
|
+
userId,
|
|
227
|
+
messageTs: message.ts,
|
|
228
|
+
messageText: message.text || "",
|
|
229
|
+
threadTs: message.thread_ts,
|
|
230
|
+
responseUrl
|
|
231
|
+
}).catch((err) => {
|
|
232
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
233
|
+
logger.error({
|
|
234
|
+
errorMessage,
|
|
235
|
+
callbackId
|
|
236
|
+
}, "Failed to handle message shortcut");
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (eventType === "view_submission") {
|
|
241
|
+
const callbackId = eventBody.view?.callback_id;
|
|
242
|
+
if (callbackId === "agent_selector_modal") {
|
|
243
|
+
const view = eventBody.view;
|
|
244
|
+
const agentSelect = view.state?.values?.agent_select_block?.agent_select;
|
|
245
|
+
if (!agentSelect?.selected_option?.value || agentSelect.selected_option.value === "none") return c.json({
|
|
246
|
+
response_action: "errors",
|
|
247
|
+
errors: { agent_select_block: "Please select an agent. If none are available, add agents to this project in the dashboard." }
|
|
248
|
+
});
|
|
249
|
+
handleModalSubmission(view).catch((err) => {
|
|
250
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
251
|
+
logger.error({
|
|
252
|
+
errorMessage,
|
|
253
|
+
callbackId
|
|
254
|
+
}, "Failed to handle modal submission");
|
|
255
|
+
});
|
|
256
|
+
return new Response(null, { status: 200 });
|
|
257
|
+
}
|
|
258
|
+
if (callbackId === "follow_up_modal") {
|
|
259
|
+
const view = eventBody.view;
|
|
260
|
+
handleFollowUpSubmission(view).catch((err) => {
|
|
261
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
262
|
+
logger.error({
|
|
263
|
+
errorMessage,
|
|
264
|
+
callbackId
|
|
265
|
+
}, "Failed to handle follow-up submission");
|
|
266
|
+
});
|
|
267
|
+
return new Response(null, { status: 200 });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return c.json({ ok: true });
|
|
271
|
+
});
|
|
272
|
+
app.post("/nango-webhook", async (c) => {
|
|
273
|
+
const body = await c.req.text();
|
|
274
|
+
if (env.NANGO_SECRET_KEY) {
|
|
275
|
+
const signature = c.req.header("x-nango-signature");
|
|
276
|
+
if (!signature) {
|
|
277
|
+
logger.warn({}, "Missing Nango webhook signature");
|
|
278
|
+
return c.json({ error: "Missing signature" }, 401);
|
|
279
|
+
}
|
|
280
|
+
const crypto = await import("node:crypto");
|
|
281
|
+
const expectedSignature = crypto.createHmac("sha256", env.NANGO_SECRET_KEY).update(body).digest("hex");
|
|
282
|
+
if (signature.length !== expectedSignature.length || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
|
|
283
|
+
logger.warn({ signature }, "Invalid Nango webhook signature");
|
|
284
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
let payload;
|
|
288
|
+
try {
|
|
289
|
+
payload = JSON.parse(body);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
logger.error({
|
|
292
|
+
error,
|
|
293
|
+
bodyPreview: body.slice(0, 200)
|
|
294
|
+
}, "Failed to parse Nango webhook JSON");
|
|
295
|
+
return c.json({ error: "Invalid JSON" }, 400);
|
|
296
|
+
}
|
|
297
|
+
logger.debug({ payload }, "Nango webhook received");
|
|
298
|
+
if (payload.type === "connection_deleted" && payload.connectionId && payload.providerConfigKey) {
|
|
299
|
+
const { connectionId, providerConfigKey } = payload;
|
|
300
|
+
if (providerConfigKey === getSlackIntegrationId()) try {
|
|
301
|
+
const teamMatch = connectionId.match(/T:([A-Z0-9]+)/);
|
|
302
|
+
if (teamMatch) {
|
|
303
|
+
const teamId = teamMatch[1];
|
|
304
|
+
const tenantId = (await findWorkspaceConnectionByTeamId(teamId))?.tenantId || "default";
|
|
305
|
+
if (await deleteWorkAppSlackWorkspaceByNangoConnectionId(runDbClient_default)(connectionId)) logger.info({ connectionId }, "Deleted workspace from database via Nango webhook");
|
|
306
|
+
const deletedMappings = await deleteAllWorkAppSlackUserMappingsByTeam(runDbClient_default)(tenantId, teamId);
|
|
307
|
+
if (deletedMappings > 0) logger.info({
|
|
308
|
+
teamId,
|
|
309
|
+
deletedMappings
|
|
310
|
+
}, "Deleted user mappings via Nango webhook");
|
|
311
|
+
const deletedChannelConfigs = await deleteAllWorkAppSlackChannelAgentConfigsByTeam(runDbClient_default)(tenantId, teamId);
|
|
312
|
+
if (deletedChannelConfigs > 0) logger.info({
|
|
313
|
+
teamId,
|
|
314
|
+
deletedChannelConfigs
|
|
315
|
+
}, "Deleted channel configs via Nango webhook");
|
|
316
|
+
logger.info({
|
|
317
|
+
connectionId,
|
|
318
|
+
teamId
|
|
319
|
+
}, "Processed Nango connection deletion webhook");
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
logger.error({
|
|
323
|
+
error,
|
|
324
|
+
connectionId
|
|
325
|
+
}, "Failed to process Nango deletion webhook");
|
|
326
|
+
return c.json({ error: "Deletion processing failed" }, 500);
|
|
327
|
+
}
|
|
328
|
+
return c.json({ received: true });
|
|
329
|
+
}
|
|
330
|
+
if (payload.type === "auth" && payload.success && payload.endUser && payload.connectionId) {
|
|
331
|
+
const { endUser, connectionId } = payload;
|
|
332
|
+
const integrationId = getSlackIntegrationId();
|
|
333
|
+
try {
|
|
334
|
+
const rawResponse = (await getSlackNango().getConnection(integrationId, connectionId)).credentials?.raw;
|
|
335
|
+
logger.debug({ teamId: rawResponse?.team?.id }, "Retrieved Nango connection info");
|
|
336
|
+
if (rawResponse?.ok && rawResponse.access_token) {
|
|
337
|
+
const slackUserId = rawResponse.authed_user?.id || "";
|
|
338
|
+
const slackTeamId = rawResponse.team?.id || "";
|
|
339
|
+
const accessToken = rawResponse.access_token;
|
|
340
|
+
let slackUsername = "";
|
|
341
|
+
let slackDisplayName = "";
|
|
342
|
+
let slackEmail = "";
|
|
343
|
+
let isSlackAdmin = false;
|
|
344
|
+
let isSlackOwner = false;
|
|
345
|
+
if (slackUserId && accessToken) {
|
|
346
|
+
const userInfo = await getSlackUserInfo(getSlackClient(accessToken), slackUserId);
|
|
347
|
+
if (userInfo) {
|
|
348
|
+
slackUsername = userInfo.name || "";
|
|
349
|
+
slackDisplayName = userInfo.displayName || userInfo.realName || "";
|
|
350
|
+
slackEmail = userInfo.email || "";
|
|
351
|
+
isSlackAdmin = userInfo.isAdmin || false;
|
|
352
|
+
isSlackOwner = userInfo.isOwner || false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const tenantId = payload.organization?.id || "default";
|
|
356
|
+
await updateConnectionMetadata(connectionId, {
|
|
357
|
+
linked_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
358
|
+
app_user_id: endUser.endUserId,
|
|
359
|
+
app_user_email: endUser.endUserEmail || "",
|
|
360
|
+
tenant_id: tenantId,
|
|
361
|
+
slack_user_id: slackUserId,
|
|
362
|
+
slack_team_id: slackTeamId,
|
|
363
|
+
slack_team_name: rawResponse.team?.name || "",
|
|
364
|
+
slack_username: slackUsername,
|
|
365
|
+
slack_display_name: slackDisplayName,
|
|
366
|
+
slack_email: slackEmail,
|
|
367
|
+
is_slack_admin: String(isSlackAdmin),
|
|
368
|
+
is_slack_owner: String(isSlackOwner),
|
|
369
|
+
enterprise_id: rawResponse.enterprise?.id || "",
|
|
370
|
+
enterprise_name: rawResponse.enterprise?.name || ""
|
|
371
|
+
});
|
|
372
|
+
logger.info({
|
|
373
|
+
appUserId: endUser.endUserId,
|
|
374
|
+
slackUserId,
|
|
375
|
+
slackEmail
|
|
376
|
+
}, "User linked to Slack with enriched metadata");
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
logger.error({
|
|
380
|
+
error,
|
|
381
|
+
connectionId
|
|
382
|
+
}, "Failed to process Nango webhook");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return c.json({ received: true });
|
|
386
|
+
});
|
|
387
|
+
var events_default = app;
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
export { events_default as default };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { WorkAppsVariables } from "../types.js";
|
|
2
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
3
|
+
|
|
4
|
+
//#region src/slack/routes/index.d.ts
|
|
5
|
+
|
|
6
|
+
declare const app: OpenAPIHono<{
|
|
7
|
+
Variables: WorkAppsVariables;
|
|
8
|
+
}, {}, "/">;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { app as default };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import events_default from "./events.js";
|
|
2
|
+
import oauth_default from "./oauth.js";
|
|
3
|
+
import users_default from "./users.js";
|
|
4
|
+
import workspaces_default from "./workspaces.js";
|
|
5
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
6
|
+
|
|
7
|
+
//#region src/slack/routes/index.ts
|
|
8
|
+
/**
|
|
9
|
+
* Slack Work App Routes - Main Router
|
|
10
|
+
*
|
|
11
|
+
* Modular RESTful API structure:
|
|
12
|
+
*
|
|
13
|
+
* OAuth & Installation (oauth.ts):
|
|
14
|
+
* GET /install - Redirect to Slack OAuth
|
|
15
|
+
* GET /oauth_redirect - OAuth callback
|
|
16
|
+
*
|
|
17
|
+
* Workspaces (workspaces.ts):
|
|
18
|
+
* GET /workspaces - List all workspaces
|
|
19
|
+
* GET /workspaces/:teamId - Get workspace details
|
|
20
|
+
* GET /workspaces/:teamId/settings - Get workspace settings
|
|
21
|
+
* PUT /workspaces/:teamId/settings - Update workspace settings [ADMIN]
|
|
22
|
+
* DELETE /workspaces/:teamId - Uninstall workspace [ADMIN]
|
|
23
|
+
* GET /workspaces/:teamId/channels - List channels
|
|
24
|
+
* GET/PUT/DELETE /workspaces/:teamId/channels/:channelId/settings - Channel config
|
|
25
|
+
* GET /workspaces/:teamId/users - List linked users
|
|
26
|
+
*
|
|
27
|
+
* Users (users.ts):
|
|
28
|
+
* GET /users/link-status - Check link status
|
|
29
|
+
* POST /users/link/verify-token - Verify JWT link token (primary linking method)
|
|
30
|
+
* POST /users/connect - Create Nango session
|
|
31
|
+
* POST /users/disconnect - Disconnect/unlink user
|
|
32
|
+
* GET /users/status - Get user connection status
|
|
33
|
+
*
|
|
34
|
+
* Events & Commands (events.ts):
|
|
35
|
+
* POST /commands - Handle slash commands
|
|
36
|
+
* POST /events - Handle Slack events
|
|
37
|
+
* POST /nango-webhook - Handle Nango webhooks
|
|
38
|
+
*/
|
|
39
|
+
const app = new OpenAPIHono();
|
|
40
|
+
app.route("/workspaces", workspaces_default);
|
|
41
|
+
app.route("/users", users_default);
|
|
42
|
+
app.route("/", oauth_default);
|
|
43
|
+
app.route("/", events_default);
|
|
44
|
+
var routes_default = app;
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { routes_default as default };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { WorkAppsVariables } from "../types.js";
|
|
2
|
+
import { getBotTokenForTeam, setBotTokenForTeam } from "../services/workspace-tokens.js";
|
|
3
|
+
import "../services/index.js";
|
|
4
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
5
|
+
|
|
6
|
+
//#region src/slack/routes/oauth.d.ts
|
|
7
|
+
|
|
8
|
+
interface OAuthState {
|
|
9
|
+
nonce: string;
|
|
10
|
+
tenantId?: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
declare function getStateSigningSecret(): string;
|
|
14
|
+
declare function createOAuthState(tenantId?: string): string;
|
|
15
|
+
declare function parseOAuthState(stateStr: string): OAuthState | null;
|
|
16
|
+
declare const app: OpenAPIHono<{
|
|
17
|
+
Variables: WorkAppsVariables;
|
|
18
|
+
}, {}, "/">;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { createOAuthState, app as default, getBotTokenForTeam, getStateSigningSecret, parseOAuthState, setBotTokenForTeam };
|