@inkeep/agents-work-apps 0.0.0-dev-20260212083055 → 0.0.0-dev-20260212092330
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 +22 -0
- package/dist/env.js +13 -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 +402 -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 +344 -0
- package/dist/slack/routes/users.d.ts +10 -0
- package/dist/slack/routes/users.js +365 -0
- package/dist/slack/routes/workspaces.d.ts +10 -0
- package/dist/slack/routes/workspaces.js +899 -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 +553 -0
- package/dist/slack/services/events/app-mention.d.ts +40 -0
- package/dist/slack/services/events/app-mention.js +246 -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 +349 -0
- package/dist/slack/services/events/streaming.d.ts +26 -0
- package/dist/slack/services/events/streaming.js +233 -0
- package/dist/slack/services/events/utils.d.ts +146 -0
- package/dist/slack/services/events/utils.js +370 -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 +476 -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
package/dist/env.d.ts
CHANGED
|
@@ -28,6 +28,17 @@ declare const envSchema: z.ZodObject<{
|
|
|
28
28
|
GITHUB_STATE_SIGNING_SECRET: z.ZodOptional<z.ZodString>;
|
|
29
29
|
GITHUB_APP_NAME: z.ZodOptional<z.ZodString>;
|
|
30
30
|
GITHUB_MCP_API_KEY: z.ZodOptional<z.ZodString>;
|
|
31
|
+
SLACK_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
32
|
+
SLACK_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
33
|
+
SLACK_SIGNING_SECRET: z.ZodOptional<z.ZodString>;
|
|
34
|
+
SLACK_BOT_TOKEN: z.ZodOptional<z.ZodString>;
|
|
35
|
+
SLACK_APP_URL: z.ZodOptional<z.ZodString>;
|
|
36
|
+
NANGO_SECRET_KEY: z.ZodOptional<z.ZodString>;
|
|
37
|
+
NANGO_SLACK_SECRET_KEY: z.ZodOptional<z.ZodString>;
|
|
38
|
+
NANGO_SLACK_INTEGRATION_ID: z.ZodOptional<z.ZodString>;
|
|
39
|
+
NANGO_SERVER_URL: z.ZodOptional<z.ZodString>;
|
|
40
|
+
INKEEP_AGENTS_JWT_SIGNING_SECRET: z.ZodOptional<z.ZodString>;
|
|
41
|
+
INKEEP_AGENTS_API_URL: z.ZodOptional<z.ZodString>;
|
|
31
42
|
}, z.core.$strip>;
|
|
32
43
|
declare const env: {
|
|
33
44
|
NODE_ENV: "development" | "production" | "test";
|
|
@@ -41,6 +52,17 @@ declare const env: {
|
|
|
41
52
|
GITHUB_STATE_SIGNING_SECRET?: string | undefined;
|
|
42
53
|
GITHUB_APP_NAME?: string | undefined;
|
|
43
54
|
GITHUB_MCP_API_KEY?: string | undefined;
|
|
55
|
+
SLACK_CLIENT_ID?: string | undefined;
|
|
56
|
+
SLACK_CLIENT_SECRET?: string | undefined;
|
|
57
|
+
SLACK_SIGNING_SECRET?: string | undefined;
|
|
58
|
+
SLACK_BOT_TOKEN?: string | undefined;
|
|
59
|
+
SLACK_APP_URL?: string | undefined;
|
|
60
|
+
NANGO_SECRET_KEY?: string | undefined;
|
|
61
|
+
NANGO_SLACK_SECRET_KEY?: string | undefined;
|
|
62
|
+
NANGO_SLACK_INTEGRATION_ID?: string | undefined;
|
|
63
|
+
NANGO_SERVER_URL?: string | undefined;
|
|
64
|
+
INKEEP_AGENTS_JWT_SIGNING_SECRET?: string | undefined;
|
|
65
|
+
INKEEP_AGENTS_API_URL?: string | undefined;
|
|
44
66
|
};
|
|
45
67
|
type Env = z.infer<typeof envSchema>;
|
|
46
68
|
//#endregion
|
package/dist/env.js
CHANGED
|
@@ -22,14 +22,25 @@ const envSchema = z.object({
|
|
|
22
22
|
"warn",
|
|
23
23
|
"error"
|
|
24
24
|
]).default("info").describe("Logging verbosity level"),
|
|
25
|
-
INKEEP_AGENTS_RUN_DATABASE_URL: z.string().describe("PostgreSQL connection URL for the runtime database
|
|
25
|
+
INKEEP_AGENTS_RUN_DATABASE_URL: z.string().describe("PostgreSQL connection URL for the runtime database"),
|
|
26
26
|
INKEEP_AGENTS_MANAGE_UI_URL: z.string().optional().describe("URL where the management UI is hosted"),
|
|
27
27
|
GITHUB_APP_ID: z.string().optional().describe("GitHub App ID for GitHub integration"),
|
|
28
28
|
GITHUB_APP_PRIVATE_KEY: z.string().optional().describe("GitHub App private key for authentication"),
|
|
29
29
|
GITHUB_WEBHOOK_SECRET: z.string().optional().describe("Secret for validating GitHub webhook payloads"),
|
|
30
30
|
GITHUB_STATE_SIGNING_SECRET: z.string().min(32, "GITHUB_STATE_SIGNING_SECRET must be at least 32 characters").optional().describe("Secret for signing GitHub OAuth state (minimum 32 characters)"),
|
|
31
31
|
GITHUB_APP_NAME: z.string().optional().describe("Name of the GitHub App"),
|
|
32
|
-
GITHUB_MCP_API_KEY: z.string().optional().describe("API key for the GitHub MCP")
|
|
32
|
+
GITHUB_MCP_API_KEY: z.string().optional().describe("API key for the GitHub MCP"),
|
|
33
|
+
SLACK_CLIENT_ID: z.string().optional().describe("Slack App Client ID"),
|
|
34
|
+
SLACK_CLIENT_SECRET: z.string().optional().describe("Slack App Client Secret"),
|
|
35
|
+
SLACK_SIGNING_SECRET: z.string().optional().describe("Slack App Signing Secret"),
|
|
36
|
+
SLACK_BOT_TOKEN: z.string().optional().describe("Slack Bot Token (for testing)"),
|
|
37
|
+
SLACK_APP_URL: z.string().optional().describe("Slack App Install URL"),
|
|
38
|
+
NANGO_SECRET_KEY: z.string().optional().describe("Nango Secret Key"),
|
|
39
|
+
NANGO_SLACK_SECRET_KEY: z.string().optional().describe("Nango Slack-specific Secret Key"),
|
|
40
|
+
NANGO_SLACK_INTEGRATION_ID: z.string().optional().describe("Nango Slack Integration ID"),
|
|
41
|
+
NANGO_SERVER_URL: z.string().optional().describe("Nango Server URL"),
|
|
42
|
+
INKEEP_AGENTS_JWT_SIGNING_SECRET: z.string().optional().describe("JWT signing secret shared with agents-api. Required in production (min 32 chars). Used for Slack user tokens and link tokens."),
|
|
43
|
+
INKEEP_AGENTS_API_URL: z.string().optional().describe("Inkeep Agents API URL")
|
|
33
44
|
});
|
|
34
45
|
const parseEnv = () => {
|
|
35
46
|
try {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/slack/i18n/strings.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Slack UI/UX Internationalization Strings
|
|
4
|
+
*
|
|
5
|
+
* Centralized strings for all Slack-facing UI text.
|
|
6
|
+
* Update this file to change text across the entire Slack integration.
|
|
7
|
+
*/
|
|
8
|
+
declare const SlackStrings: {
|
|
9
|
+
readonly buttons: {
|
|
10
|
+
readonly triggerAgent: "Trigger Agent";
|
|
11
|
+
readonly send: "Send";
|
|
12
|
+
readonly followUp: "Follow Up";
|
|
13
|
+
readonly cancel: "Cancel";
|
|
14
|
+
readonly openDashboard: "⚙️ Open Dashboard";
|
|
15
|
+
};
|
|
16
|
+
readonly modals: {
|
|
17
|
+
readonly triggerAgent: "Trigger Agent";
|
|
18
|
+
readonly triggerAgentThread: "Trigger Agent (Thread)";
|
|
19
|
+
readonly askAboutMessage: "Ask About Message";
|
|
20
|
+
readonly followUp: "Follow Up";
|
|
21
|
+
};
|
|
22
|
+
readonly labels: {
|
|
23
|
+
readonly project: "Project";
|
|
24
|
+
readonly agent: "Agent";
|
|
25
|
+
readonly prompt: "Prompt";
|
|
26
|
+
readonly additionalInstructions: "Additional Instructions";
|
|
27
|
+
readonly context: "Context";
|
|
28
|
+
};
|
|
29
|
+
readonly placeholders: {
|
|
30
|
+
readonly selectProject: "Select a project...";
|
|
31
|
+
readonly selectAgent: "Select an agent...";
|
|
32
|
+
readonly enterPrompt: "Enter your prompt or question...";
|
|
33
|
+
readonly additionalInstructionsOptional: "Additional instructions (optional)...";
|
|
34
|
+
readonly additionalInstructionsMessage: "Additional instructions or question about this message...";
|
|
35
|
+
};
|
|
36
|
+
readonly visibility: {
|
|
37
|
+
readonly includeThreadContext: "Include thread context";
|
|
38
|
+
};
|
|
39
|
+
readonly context: {
|
|
40
|
+
readonly poweredBy: (agentName: string) => string;
|
|
41
|
+
readonly privateResponse: "_Private response_";
|
|
42
|
+
};
|
|
43
|
+
readonly usage: {
|
|
44
|
+
readonly mentionEmpty: string;
|
|
45
|
+
};
|
|
46
|
+
readonly status: {
|
|
47
|
+
readonly thinking: (agentName: string) => string;
|
|
48
|
+
readonly noAgentsAvailable: "No agents available";
|
|
49
|
+
readonly noProjectsConfigured: "⚙️ No projects configured. Please set up projects in the dashboard.";
|
|
50
|
+
};
|
|
51
|
+
readonly errors: {
|
|
52
|
+
readonly generic: "Sorry, something went wrong. Please try again.";
|
|
53
|
+
readonly failedToOpenSelector: "❌ Failed to open agent selector. Please try again.";
|
|
54
|
+
};
|
|
55
|
+
readonly help: {
|
|
56
|
+
readonly title: "Inkeep — How to Use";
|
|
57
|
+
readonly publicSection: string;
|
|
58
|
+
readonly privateSection: string;
|
|
59
|
+
readonly otherCommands: string;
|
|
60
|
+
};
|
|
61
|
+
readonly agentList: {
|
|
62
|
+
readonly title: "🤖 Available Agents";
|
|
63
|
+
readonly usage: "Usage:";
|
|
64
|
+
readonly runUsage: "`/inkeep run \"agent name\" question` - Run a specific agent";
|
|
65
|
+
readonly andMore: (count: number) => string;
|
|
66
|
+
};
|
|
67
|
+
readonly messageContext: {
|
|
68
|
+
readonly label: "Message:";
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
type SlackStringsType = typeof SlackStrings;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { SlackStrings, SlackStringsType };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/slack/i18n/strings.ts
|
|
2
|
+
/**
|
|
3
|
+
* Slack UI/UX Internationalization Strings
|
|
4
|
+
*
|
|
5
|
+
* Centralized strings for all Slack-facing UI text.
|
|
6
|
+
* Update this file to change text across the entire Slack integration.
|
|
7
|
+
*/
|
|
8
|
+
const SlackStrings = {
|
|
9
|
+
buttons: {
|
|
10
|
+
triggerAgent: "Trigger Agent",
|
|
11
|
+
send: "Send",
|
|
12
|
+
followUp: "Follow Up",
|
|
13
|
+
cancel: "Cancel",
|
|
14
|
+
openDashboard: "⚙️ Open Dashboard"
|
|
15
|
+
},
|
|
16
|
+
modals: {
|
|
17
|
+
triggerAgent: "Trigger Agent",
|
|
18
|
+
triggerAgentThread: "Trigger Agent (Thread)",
|
|
19
|
+
askAboutMessage: "Ask About Message",
|
|
20
|
+
followUp: "Follow Up"
|
|
21
|
+
},
|
|
22
|
+
labels: {
|
|
23
|
+
project: "Project",
|
|
24
|
+
agent: "Agent",
|
|
25
|
+
prompt: "Prompt",
|
|
26
|
+
additionalInstructions: "Additional Instructions",
|
|
27
|
+
context: "Context"
|
|
28
|
+
},
|
|
29
|
+
placeholders: {
|
|
30
|
+
selectProject: "Select a project...",
|
|
31
|
+
selectAgent: "Select an agent...",
|
|
32
|
+
enterPrompt: "Enter your prompt or question...",
|
|
33
|
+
additionalInstructionsOptional: "Additional instructions (optional)...",
|
|
34
|
+
additionalInstructionsMessage: "Additional instructions or question about this message..."
|
|
35
|
+
},
|
|
36
|
+
visibility: { includeThreadContext: "Include thread context" },
|
|
37
|
+
context: {
|
|
38
|
+
poweredBy: (agentName) => `Powered by *${agentName}* via Inkeep`,
|
|
39
|
+
privateResponse: "_Private response_"
|
|
40
|
+
},
|
|
41
|
+
usage: { mentionEmpty: "*To use your Inkeep agent, include a message:*\n\n• `@Inkeep <message>` — Send a message to your agent (reply appears in a thread)\n• `@Inkeep <message>` in a thread — Includes the thread as context for your agent\n• `@Inkeep` in a thread — Triggers your agent using the full thread as context\n\n💡 Use `/inkeep help` for all available commands." },
|
|
42
|
+
status: {
|
|
43
|
+
thinking: (agentName) => `_${agentName} is thinking..._`,
|
|
44
|
+
noAgentsAvailable: "No agents available",
|
|
45
|
+
noProjectsConfigured: "⚙️ No projects configured. Please set up projects in the dashboard."
|
|
46
|
+
},
|
|
47
|
+
errors: {
|
|
48
|
+
generic: "Sorry, something went wrong. Please try again.",
|
|
49
|
+
failedToOpenSelector: "❌ Failed to open agent selector. Please try again."
|
|
50
|
+
},
|
|
51
|
+
help: {
|
|
52
|
+
title: "Inkeep — How to Use",
|
|
53
|
+
publicSection: "🔊 *Public* — everyone in the channel can see the response\n\n• `@Inkeep <message>` — Send a message to your agent\n• `@Inkeep <message>` in a thread — Includes thread as context\n• `@Inkeep` in a thread — Uses the full thread as context",
|
|
54
|
+
privateSection: "🔒 *Private* — only you can see the response\n\n• `/inkeep <message>` — Send a message to your agent\n• `/inkeep` — Open the agent picker to choose an agent and prompt",
|
|
55
|
+
otherCommands: "⚙️ *Other Commands*\n\n• `/inkeep run \"agent name\" <message>` — Use a specific agent\n• `/inkeep list` — List available agents\n• `/inkeep status` — Check your connection and agent config\n• `/inkeep link` / `/inkeep unlink` — Manage account connection\n• `/inkeep help` — Show this message"
|
|
56
|
+
},
|
|
57
|
+
agentList: {
|
|
58
|
+
title: "🤖 Available Agents",
|
|
59
|
+
usage: "Usage:",
|
|
60
|
+
runUsage: "`/inkeep run \"agent name\" question` - Run a specific agent",
|
|
61
|
+
andMore: (count) => `...and ${count} more`
|
|
62
|
+
},
|
|
63
|
+
messageContext: { label: "Message:" }
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { SlackStrings };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ManageAppVariables, WorkAppsVariables } from "./types.js";
|
|
2
|
+
import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./services/nango.js";
|
|
3
|
+
import { getChannelAgentConfig, getWorkspaceDefaultAgent } from "./services/events/utils.js";
|
|
4
|
+
import "./services/events/index.js";
|
|
5
|
+
import { getBotTokenForTeam, setBotTokenForTeam } from "./services/workspace-tokens.js";
|
|
6
|
+
import "./routes/oauth.js";
|
|
7
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
8
|
+
|
|
9
|
+
//#region src/slack/index.d.ts
|
|
10
|
+
|
|
11
|
+
declare function createSlackRoutes(): OpenAPIHono<{
|
|
12
|
+
Variables: WorkAppsVariables;
|
|
13
|
+
}, {}, "/">;
|
|
14
|
+
declare const slackRoutes: OpenAPIHono<{
|
|
15
|
+
Variables: WorkAppsVariables;
|
|
16
|
+
}, {}, "/">;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { DefaultAgentConfig, ManageAppVariables, SlackWorkspaceConnection, WorkAppsVariables, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, createSlackRoutes, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setBotTokenForTeam, setWorkspaceDefaultAgent, slackRoutes, storeWorkspaceInstallation, updateConnectionMetadata };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./services/nango.js";
|
|
2
|
+
import { getChannelAgentConfig, getWorkspaceDefaultAgent } from "./services/events/utils.js";
|
|
3
|
+
import { getBotTokenForTeam, setBotTokenForTeam } from "./services/workspace-tokens.js";
|
|
4
|
+
import "./services/events/index.js";
|
|
5
|
+
import "./routes/oauth.js";
|
|
6
|
+
import routes_default from "./routes/index.js";
|
|
7
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
8
|
+
|
|
9
|
+
//#region src/slack/index.ts
|
|
10
|
+
/**
|
|
11
|
+
* Slack Work App
|
|
12
|
+
*
|
|
13
|
+
* Provides Slack integration for Inkeep Agents including:
|
|
14
|
+
* - OAuth-based workspace installation
|
|
15
|
+
* - User account linking via JWT tokens
|
|
16
|
+
* - Slash commands for agent interaction
|
|
17
|
+
* - @mention support for workspace-wide agent access
|
|
18
|
+
* - Channel-specific agent configuration
|
|
19
|
+
*/
|
|
20
|
+
function createSlackRoutes() {
|
|
21
|
+
const app = new OpenAPIHono();
|
|
22
|
+
app.route("/", routes_default);
|
|
23
|
+
return app;
|
|
24
|
+
}
|
|
25
|
+
const slackRoutes = createSlackRoutes();
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, createSlackRoutes, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setBotTokenForTeam, setWorkspaceDefaultAgent, slackRoutes, storeWorkspaceInstallation, updateConnectionMetadata };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ManageAppVariables } from "../types.js";
|
|
2
|
+
import * as hono0 from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/slack/middleware/permissions.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Check if user has admin role (owner or admin)
|
|
7
|
+
*/
|
|
8
|
+
declare function isOrgAdmin(tenantRole: string | undefined): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Middleware that requires Inkeep org admin/owner role.
|
|
11
|
+
* Use for workspace-level settings that only Inkeep organization admins can modify.
|
|
12
|
+
*/
|
|
13
|
+
declare const requireWorkspaceAdmin: <Env extends {
|
|
14
|
+
Variables: ManageAppVariables;
|
|
15
|
+
} = {
|
|
16
|
+
Variables: ManageAppVariables;
|
|
17
|
+
}>() => hono0.MiddlewareHandler<Env, string, {}, Response>;
|
|
18
|
+
/**
|
|
19
|
+
* Middleware that requires either:
|
|
20
|
+
* 1. Org admin/owner role (can modify any channel), OR
|
|
21
|
+
* 2. Member role AND membership in the specific Slack channel
|
|
22
|
+
*
|
|
23
|
+
* Use for channel-level settings where members can configure their own channels.
|
|
24
|
+
*/
|
|
25
|
+
declare const requireChannelMemberOrAdmin: <Env extends {
|
|
26
|
+
Variables: ManageAppVariables;
|
|
27
|
+
} = {
|
|
28
|
+
Variables: ManageAppVariables;
|
|
29
|
+
}>() => hono0.MiddlewareHandler<Env, string, {}, Response>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { isOrgAdmin, requireChannelMemberOrAdmin, requireWorkspaceAdmin };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { getLogger } from "../../logger.js";
|
|
2
|
+
import runDbClient_default from "../../db/runDbClient.js";
|
|
3
|
+
import { findWorkspaceConnectionByTeamId } from "../services/nango.js";
|
|
4
|
+
import { checkUserIsChannelMember, getSlackClient } from "../services/client.js";
|
|
5
|
+
import { OrgRoles, createApiError, findWorkAppSlackUserMappingByInkeepUserId, getUserOrganizationsFromDb } from "@inkeep/agents-core";
|
|
6
|
+
import { createMiddleware } from "hono/factory";
|
|
7
|
+
|
|
8
|
+
//#region src/slack/middleware/permissions.ts
|
|
9
|
+
const logger = getLogger("slack-permissions");
|
|
10
|
+
/**
|
|
11
|
+
* Check if user has admin role (owner or admin)
|
|
12
|
+
*/
|
|
13
|
+
function isOrgAdmin(tenantRole) {
|
|
14
|
+
return tenantRole === OrgRoles.OWNER || tenantRole === OrgRoles.ADMIN;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve tenantId and tenantRole from a Slack teamId.
|
|
18
|
+
* Looks up the workspace connection to find the owning tenant,
|
|
19
|
+
* then checks the user's org membership to determine their role.
|
|
20
|
+
*
|
|
21
|
+
* This is needed because /work-apps/* routes don't go through requireTenantAccess
|
|
22
|
+
* middleware (which normally sets tenantRole on /manage/* routes).
|
|
23
|
+
*/
|
|
24
|
+
async function resolveWorkAppTenantContext(c, teamId, userId) {
|
|
25
|
+
const workspace = await findWorkspaceConnectionByTeamId(teamId);
|
|
26
|
+
if (!workspace?.tenantId) throw createApiError({
|
|
27
|
+
code: "not_found",
|
|
28
|
+
message: "Slack workspace not found or not associated with a tenant",
|
|
29
|
+
instance: c.req.path
|
|
30
|
+
});
|
|
31
|
+
const orgAccess = (await getUserOrganizationsFromDb(runDbClient_default)(userId)).find((org) => org.organizationId === workspace.tenantId);
|
|
32
|
+
if (!orgAccess) throw createApiError({
|
|
33
|
+
code: "forbidden",
|
|
34
|
+
message: "Access denied to this organization",
|
|
35
|
+
instance: c.req.path
|
|
36
|
+
});
|
|
37
|
+
c.set("tenantId", workspace.tenantId);
|
|
38
|
+
c.set("tenantRole", orgAccess.role);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Middleware that requires Inkeep org admin/owner role.
|
|
42
|
+
* Use for workspace-level settings that only Inkeep organization admins can modify.
|
|
43
|
+
*/
|
|
44
|
+
const requireWorkspaceAdmin = () => createMiddleware(async (c, next) => {
|
|
45
|
+
if (process.env.ENVIRONMENT === "test" && c.req.header("x-test-bypass-auth") === "true") {
|
|
46
|
+
await next();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const userId = c.get("userId");
|
|
50
|
+
if (!userId) throw createApiError({
|
|
51
|
+
code: "unauthorized",
|
|
52
|
+
message: "User context not found",
|
|
53
|
+
instance: c.req.path
|
|
54
|
+
});
|
|
55
|
+
if (userId === "system" || userId.startsWith("apikey:")) {
|
|
56
|
+
await next();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const teamId = c.req.param("teamId") || c.req.param("workspaceId");
|
|
60
|
+
if (teamId && !c.get("tenantId")) await resolveWorkAppTenantContext(c, teamId, userId);
|
|
61
|
+
const tenantId = c.get("tenantId");
|
|
62
|
+
const tenantRole = c.get("tenantRole");
|
|
63
|
+
if (!tenantId) throw createApiError({
|
|
64
|
+
code: "unauthorized",
|
|
65
|
+
message: "Organization context not found",
|
|
66
|
+
instance: c.req.path
|
|
67
|
+
});
|
|
68
|
+
if (!isOrgAdmin(tenantRole)) throw createApiError({
|
|
69
|
+
code: "forbidden",
|
|
70
|
+
message: "Only organization administrators can modify workspace settings",
|
|
71
|
+
instance: c.req.path,
|
|
72
|
+
extensions: {
|
|
73
|
+
requiredRole: "admin or owner",
|
|
74
|
+
currentRole: tenantRole
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
await next();
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Middleware that requires either:
|
|
81
|
+
* 1. Org admin/owner role (can modify any channel), OR
|
|
82
|
+
* 2. Member role AND membership in the specific Slack channel
|
|
83
|
+
*
|
|
84
|
+
* Use for channel-level settings where members can configure their own channels.
|
|
85
|
+
*/
|
|
86
|
+
const requireChannelMemberOrAdmin = () => createMiddleware(async (c, next) => {
|
|
87
|
+
if (process.env.ENVIRONMENT === "test" && c.req.header("x-test-bypass-auth") === "true") {
|
|
88
|
+
await next();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const userId = c.get("userId");
|
|
92
|
+
if (!userId) throw createApiError({
|
|
93
|
+
code: "unauthorized",
|
|
94
|
+
message: "User context not found",
|
|
95
|
+
instance: c.req.path
|
|
96
|
+
});
|
|
97
|
+
if (userId === "system" || userId.startsWith("apikey:")) {
|
|
98
|
+
await next();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const teamId = c.req.param("teamId");
|
|
102
|
+
if (teamId && !c.get("tenantId")) await resolveWorkAppTenantContext(c, teamId, userId);
|
|
103
|
+
const tenantId = c.get("tenantId");
|
|
104
|
+
const tenantRole = c.get("tenantRole");
|
|
105
|
+
if (!tenantId) throw createApiError({
|
|
106
|
+
code: "unauthorized",
|
|
107
|
+
message: "Organization context not found",
|
|
108
|
+
instance: c.req.path
|
|
109
|
+
});
|
|
110
|
+
if (isOrgAdmin(tenantRole)) {
|
|
111
|
+
await next();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const channelId = c.req.param("channelId");
|
|
115
|
+
if (!teamId || !channelId) throw createApiError({
|
|
116
|
+
code: "bad_request",
|
|
117
|
+
message: "Team ID and Channel ID are required",
|
|
118
|
+
instance: c.req.path
|
|
119
|
+
});
|
|
120
|
+
const userMapping = (await findWorkAppSlackUserMappingByInkeepUserId(runDbClient_default)(userId)).find((m) => m.tenantId === tenantId && m.slackTeamId === teamId);
|
|
121
|
+
if (!userMapping) throw createApiError({
|
|
122
|
+
code: "forbidden",
|
|
123
|
+
message: "You must link your Slack account to modify channel settings. Use /inkeep link in Slack.",
|
|
124
|
+
instance: c.req.path
|
|
125
|
+
});
|
|
126
|
+
const workspace = await findWorkspaceConnectionByTeamId(teamId);
|
|
127
|
+
if (!workspace?.botToken) throw createApiError({
|
|
128
|
+
code: "not_found",
|
|
129
|
+
message: "Slack workspace not found or not properly configured",
|
|
130
|
+
instance: c.req.path
|
|
131
|
+
});
|
|
132
|
+
if (!await checkUserIsChannelMember(getSlackClient(workspace.botToken), channelId, userMapping.slackUserId)) {
|
|
133
|
+
logger.info({
|
|
134
|
+
userId,
|
|
135
|
+
slackUserId: userMapping.slackUserId,
|
|
136
|
+
channelId,
|
|
137
|
+
teamId
|
|
138
|
+
}, "User is not a member of the channel");
|
|
139
|
+
throw createApiError({
|
|
140
|
+
code: "forbidden",
|
|
141
|
+
message: "You can only configure channels you are a member of",
|
|
142
|
+
instance: c.req.path,
|
|
143
|
+
extensions: {
|
|
144
|
+
channelId,
|
|
145
|
+
reason: "not_channel_member"
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
logger.debug({
|
|
150
|
+
userId,
|
|
151
|
+
slackUserId: userMapping.slackUserId,
|
|
152
|
+
channelId,
|
|
153
|
+
teamId
|
|
154
|
+
}, "User verified as channel member");
|
|
155
|
+
await next();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
export { isOrgAdmin, requireChannelMemberOrAdmin, requireWorkspaceAdmin };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { WorkAppsVariables } from "../types.js";
|
|
2
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
3
|
+
|
|
4
|
+
//#region src/slack/routes/events.d.ts
|
|
5
|
+
|
|
6
|
+
declare const app: OpenAPIHono<{
|
|
7
|
+
Variables: WorkAppsVariables;
|
|
8
|
+
}, {}, "/">;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { app as default };
|