diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-fns.ts b/backend/src/ee/services/access-approval-request/access-approval-request-fns.ts index ef9a0b61d..90b42aaf7 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-fns.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-fns.ts @@ -1,11 +1,6 @@ import { PackRule, unpackRules } from "@casl/ability/extra"; import { UnauthorizedError } from "@app/lib/errors"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { TProjectDALFactory } from "@app/services/project/project-dal"; -import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; -import { triggerSlackNotification } from "@app/services/slack/slack-fns"; -import { SlackTriggerFeature } from "@app/services/slack/slack-types"; import { TVerifyPermission } from "./access-approval-request-types"; @@ -56,67 +51,3 @@ export const verifyRequestedPermissions = ({ permissions }: TVerifyPermission) = accessTypes: requestedPermissions.filter(filterUnique) }; }; - -export const triggerAccessRequestSlackNotif = async ({ - projectId, - projectName, - requesterFullName, - isTemporary, - requesterEmail, - secretPath, - environment, - permissions, - approvalUrl, - projectDAL, - kmsService, - projectSlackConfigDAL -}: { - projectId: string; - projectName: string; - requesterFullName: string; - isTemporary: boolean; - requesterEmail: string; - secretPath: string; - environment: string; - permissions: string[]; - approvalUrl: string; - projectDAL: Pick; - kmsService: Pick; - projectSlackConfigDAL: Pick; -}) => { - const messageBody = `${requesterFullName} (${requesterEmail}) has requested ${ - isTemporary ? "temporary" : "permanent" - } access to ${secretPath} in the ${environment} environment of ${projectName}. - -The following permissions are requested: ${permissions.join(", ")} - -View the request and approve or deny it <${approvalUrl}|here>.`; - - const payloadBlocks = [ - { - type: "header", - text: { - type: "plain_text", - text: "New access approval request pending for review", - emoji: true - } - }, - { - type: "section", - text: { - type: "mrkdwn", - text: messageBody - } - } - ]; - - await triggerSlackNotification({ - projectId, - projectDAL, - kmsService, - payloadMessage: messageBody, - projectSlackConfigDAL, - payloadBlocks, - feature: SlackTriggerFeature.ACCESS_REQUEST - }); -}; diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 6ba231e83..2a7953ead 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -10,6 +10,8 @@ import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; +import { triggerSlackNotification } from "@app/services/slack/slack-fns"; +import { SlackTriggerFeature } from "@app/services/slack/slack-types"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TUserDALFactory } from "@app/services/user/user-dal"; @@ -20,7 +22,7 @@ import { TPermissionServiceFactory } from "../permission/permission-service"; import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types"; import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal"; -import { triggerAccessRequestSlackNotif, verifyRequestedPermissions } from "./access-approval-request-fns"; +import { verifyRequestedPermissions } from "./access-approval-request-fns"; import { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-request-reviewer-dal"; import { ApprovalStatus, @@ -178,19 +180,24 @@ export const accessApprovalRequestServiceFactory = ({ const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`; - await triggerAccessRequestSlackNotif({ + await triggerSlackNotification({ projectId: project.id, - projectName: project.name, - requesterFullName, - isTemporary, - requesterEmail: requestedByUser.email as string, - secretPath, - environment: envSlug, - permissions: accessTypes, - approvalUrl, + projectSlackConfigDAL, projectDAL, kmsService, - projectSlackConfigDAL + notification: { + type: SlackTriggerFeature.ACCESS_REQUEST, + payload: { + projectName: project.name, + requesterFullName, + isTemporary, + requesterEmail: requestedByUser.email as string, + secretPath, + environment: envSlug, + permissions: accessTypes, + approvalUrl + } + } }); await smtpService.sendMail({ diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts index 5b881db3e..05b7280b2 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts @@ -1,12 +1,7 @@ import { TSecretApprovalRequests } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectDALFactory } from "@app/services/project/project-dal"; -import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; -import { triggerSlackNotification } from "@app/services/slack/slack-fns"; -import { SlackTriggerFeature } from "@app/services/slack/slack-types"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; -import { TUserDALFactory } from "@app/services/user/user-dal"; import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal"; @@ -18,68 +13,6 @@ type TSendApprovalEmails = { secretApprovalRequest: TSecretApprovalRequests; }; -type TTriggerSecretApprovalSlackNotif = { - environment: string; - projectId: string; - projectDAL: Pick; - kmsService: Pick; - projectSlackConfigDAL: Pick; - secretApprovalRequest: TSecretApprovalRequests; - secretPath: string; - userDAL: Pick; -}; - -export const triggerSecretApprovalSlackNotif = async ({ - projectId, - projectDAL, - kmsService, - secretApprovalRequest, - projectSlackConfigDAL, - userDAL, - environment, - secretPath -}: TTriggerSecretApprovalSlackNotif) => { - const appCfg = getConfig(); - const project = await projectDAL.findProjectWithOrg(projectId); - const user = await userDAL.findById(secretApprovalRequest.committerUserId); - - const messageBody = `A secret approval request has been opened by ${user.email}. -*Environment*: ${environment} -*Secret path*: ${secretPath || "/"} - -View the complete details <${appCfg.SITE_URL}/project/${project.id}/approval?requestId=${ - secretApprovalRequest.id - }|here>.`; - - const payloadBlocks = [ - { - type: "header", - text: { - type: "plain_text", - text: "Secret approval request", - emoji: true - } - }, - { - type: "section", - text: { - type: "mrkdwn", - text: messageBody - } - } - ]; - - await triggerSlackNotification({ - projectId, - projectDAL, - kmsService, - payloadMessage: messageBody, - projectSlackConfigDAL, - payloadBlocks, - feature: SlackTriggerFeature.SECRET_APPROVAL - }); -}; - export const sendApprovalEmailsFn = async ({ secretApprovalPolicyDAL, projectDAL, diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index cff973420..1b606f617 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -48,6 +48,8 @@ import { import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; +import { triggerSlackNotification } from "@app/services/slack/slack-fns"; +import { SlackTriggerFeature } from "@app/services/slack/slack-types"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TUserDALFactory } from "@app/services/user/user-dal"; @@ -57,7 +59,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/pr import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal"; import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service"; import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal"; -import { sendApprovalEmailsFn, triggerSecretApprovalSlackNotif } from "./secret-approval-request-fns"; +import { sendApprovalEmailsFn } from "./secret-approval-request-fns"; import { TSecretApprovalRequestReviewerDALFactory } from "./secret-approval-request-reviewer-dal"; import { TSecretApprovalRequestSecretDALFactory } from "./secret-approval-request-secret-dal"; import { @@ -1073,15 +1075,22 @@ export const secretApprovalRequestServiceFactory = ({ }); const env = await projectEnvDAL.findOne({ id: policy.envId }); - await triggerSecretApprovalSlackNotif({ + const user = await userDAL.findById(secretApprovalRequest.committerUserId); + await triggerSlackNotification({ projectId, - secretPath: policy.secretPath as string, - environment: env.name, projectDAL, kmsService, - secretApprovalRequest, - userDAL, - projectSlackConfigDAL + projectSlackConfigDAL, + notification: { + type: SlackTriggerFeature.SECRET_APPROVAL, + payload: { + userEmail: user.email as string, + environment: env.name, + secretPath, + projectId, + requestId: secretApprovalRequest.id + } + } }); await sendApprovalEmailsFn({ @@ -1346,16 +1355,23 @@ export const secretApprovalRequestServiceFactory = ({ return { ...doc, commits: approvalCommits }; }); + const user = await userDAL.findById(secretApprovalRequest.committerUserId); const env = await projectEnvDAL.findOne({ id: policy.envId }); - await triggerSecretApprovalSlackNotif({ - secretPath: policy.secretPath as string, - environment: env.name, + await triggerSlackNotification({ projectId, projectDAL, kmsService, - secretApprovalRequest, - userDAL, - projectSlackConfigDAL + projectSlackConfigDAL, + notification: { + type: SlackTriggerFeature.SECRET_APPROVAL, + payload: { + userEmail: user.email as string, + environment: env.name, + secretPath, + projectId, + requestId: secretApprovalRequest.id + } + } }); await sendApprovalEmailsFn({ diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index d23597569..e476a53f9 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -130,29 +130,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { } }); - server.route({ - method: "GET", - url: "/integrations/slack/bot-creation-url", - config: { - rateLimit: readLimit - }, - schema: { - response: { - 200: z.string() - } - }, - onRequest: (req, res, done) => { - verifyAuth([AuthMode.JWT])(req, res, () => { - verifySuperAdmin(req, res, done); - }); - }, - handler: async () => { - const url = await server.services.superAdmin.getCustomSlackBotCreationUrl(); - - return url; - } - }); - server.route({ method: "GET", url: "/integrations/slack/config", diff --git a/backend/src/services/slack/slack-fns.ts b/backend/src/services/slack/slack-fns.ts index 6f62fa125..919f9de05 100644 --- a/backend/src/services/slack/slack-fns.ts +++ b/backend/src/services/slack/slack-fns.ts @@ -1,53 +1,14 @@ -import { Block, WebClient } from "@slack/web-api"; +import { WebClient } from "@slack/web-api"; import { getConfig } from "@app/lib/config/env"; +import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TKmsServiceFactory } from "../kms/kms-service"; import { KmsDataKey } from "../kms/kms-types"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectSlackConfigDALFactory } from "./project-slack-config-dal"; -import { SlackTriggerFeature } from "./slack-types"; - -export const getCustomSlackBotManifest = () => { - const appCfg = getConfig(); - - return { - display_information: { - name: "Infisical", - description: "Get real-time Infisical updates in Slack", - background_color: "#c2d62b", - long_description: `This Slack application is designed specifically for use with your self-hosted Infisical instance, allowing seamless integration between your Infisical projects and your Slack workspace. With this integration, your team can stay up-to-date with the latest events, changes, and notifications directly inside Slack. - - Notifications: Receive real-time updates and alerts about critical events in your Infisical projects. Whether it's a new project being created, updates to secrets, or changes to your team's configuration, you will be promptly notified within the designated Slack channels of your choice. - - Customization: Tailor the notifications to your team's specific needs by configuring which types of events trigger alerts and in which channels they are sent. - - Collaboration: Keep your entire team in the loop with notifications that help facilitate more efficient collaboration by ensuring that everyone is aware of important developments in your Infisical projects. - - By integrating Infisical with Slack, you can enhance your workflow by combining the power of secure secrets management with the communication capabilities of Slack.` - }, - features: { - app_home: { - home_tab_enabled: false, - messages_tab_enabled: false, - messages_tab_read_only_enabled: true - }, - bot_user: { - display_name: "Infisical", - always_online: true - } - }, - oauth_config: { - redirect_urls: [`${appCfg.SITE_URL}/api/v1/workflow-integrations/slack/oauth_redirect`], - scopes: { - bot: ["chat:write.public", "chat:write", "channels:read", "groups:read", "im:read", "mpim:read"] - } - }, - settings: { - org_deploy_enabled: false, - socket_mode_enabled: false, - token_rotation_enabled: false - } - }; -}; +import { SlackTriggerFeature, TSlackNotification } from "./slack-types"; export const fetchSlackChannels = async (botKey: string) => { const slackChannels: { @@ -80,23 +41,98 @@ export const fetchSlackChannels = async (botKey: string) => { return slackChannels; }; +const buildSlackPayload = (notification: TSlackNotification) => { + const appCfg = getConfig(); + + switch (notification.type) { + case SlackTriggerFeature.SECRET_APPROVAL: { + const { payload } = notification; + const messageBody = `A secret approval request has been opened by ${payload.userEmail}. +*Environment*: ${payload.environment} +*Secret path*: ${payload.secretPath || "/"} + +View the complete details <${appCfg.SITE_URL}/project/${payload.projectId}/approval?requestId=${ + payload.requestId + }|here>.`; + + const payloadBlocks = [ + { + type: "header", + text: { + type: "plain_text", + text: "Secret approval request", + emoji: true + } + }, + { + type: "section", + text: { + type: "mrkdwn", + text: messageBody + } + } + ]; + + return { + payloadMessage: messageBody, + payloadBlocks + }; + } + case SlackTriggerFeature.ACCESS_REQUEST: { + const { payload } = notification; + const messageBody = `${payload.requesterFullName} (${payload.requesterEmail}) has requested ${ + payload.isTemporary ? "temporary" : "permanent" + } access to ${payload.secretPath} in the ${payload.environment} environment of ${payload.projectName}. + +The following permissions are requested: ${payload.permissions.join(", ")} + +View the request and approve or deny it <${payload.approvalUrl}|here>.`; + + const payloadBlocks = [ + { + type: "header", + text: { + type: "plain_text", + text: "New access approval request pending for review", + emoji: true + } + }, + { + type: "section", + text: { + type: "mrkdwn", + text: messageBody + } + } + ]; + + return { + payloadMessage: messageBody, + payloadBlocks + }; + } + default: { + throw new BadRequestError({ + message: "Slack notification type not supported." + }); + } + } +}; + export const triggerSlackNotification = async ({ projectId, - payloadBlocks, - payloadMessage, + notification, projectSlackConfigDAL, projectDAL, - kmsService, - feature + kmsService }: { projectId: string; - payloadBlocks: Block[]; - payloadMessage: string; + notification: TSlackNotification; projectSlackConfigDAL: Pick; projectDAL: Pick; kmsService: Pick; - feature: SlackTriggerFeature; }) => { + const { payloadMessage, payloadBlocks } = buildSlackPayload(notification); const project = await projectDAL.findById(projectId); const slackIntegration = await projectSlackConfigDAL.getIntegrationDetailsByProject(project.id); @@ -105,12 +141,12 @@ export const triggerSlackNotification = async ({ } let targetChannelIds: string[] = []; - if (feature === SlackTriggerFeature.ACCESS_REQUEST) { + if (notification.type === SlackTriggerFeature.ACCESS_REQUEST) { targetChannelIds = slackIntegration.accessRequestChannels?.split(", ") || []; if (!targetChannelIds.length || !slackIntegration.isAccessRequestNotificationEnabled) { return; } - } else if (feature === SlackTriggerFeature.SECRET_APPROVAL) { + } else if (notification.type === SlackTriggerFeature.SECRET_APPROVAL) { targetChannelIds = slackIntegration.secretRequestChannels?.split(", ") || []; if (!targetChannelIds.length || !slackIntegration.isSecretRequestNotificationEnabled) { return; @@ -136,6 +172,6 @@ export const triggerSlackNotification = async ({ text: payloadMessage, blocks: payloadBlocks }) - .catch((err) => void logger.error(err)); + .catch((err) => logger.error(err)); } }; diff --git a/backend/src/services/slack/slack-service.ts b/backend/src/services/slack/slack-service.ts index 41ba58b3b..8a0a1a7d3 100644 --- a/backend/src/services/slack/slack-service.ts +++ b/backend/src/services/slack/slack-service.ts @@ -158,7 +158,7 @@ export const slackServiceFactory = ({ message: `Invalid Slack configuration. ${ appCfg.isCloud ? "Please contact the Infisical team." - : "Contact your instance admin to setup Slack integration in the Admin settings." + : "Contact your instance admin to setup Slack integration in the Admin settings. Your configuration is missing Slack client ID and secret." }` }); } diff --git a/backend/src/services/slack/slack-types.ts b/backend/src/services/slack/slack-types.ts index 01090c065..a1914eee2 100644 --- a/backend/src/services/slack/slack-types.ts +++ b/backend/src/services/slack/slack-types.ts @@ -52,3 +52,28 @@ export enum SlackTriggerFeature { SECRET_APPROVAL = "secret-approval", ACCESS_REQUEST = "access-request" } + +export type TSlackNotification = + | { + type: SlackTriggerFeature.SECRET_APPROVAL; + payload: { + userEmail: string; + environment: string; + secretPath: string; + requestId: string; + projectId: string; + }; + } + | { + type: SlackTriggerFeature.ACCESS_REQUEST; + payload: { + requesterFullName: string; + requesterEmail: string; + isTemporary: boolean; + secretPath: string; + environment: string; + projectName: string; + permissions: string[]; + approvalUrl: string; + }; + }; diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 015506d0f..9fe8d7501 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -12,7 +12,6 @@ import { TAuthLoginFactory } from "../auth/auth-login-service"; import { AuthMethod } from "../auth/auth-type"; import { TKmsServiceFactory } from "../kms/kms-service"; import { TOrgServiceFactory } from "../org/org-service"; -import { getCustomSlackBotManifest } from "../slack/slack-fns"; import { TUserDALFactory } from "../user/user-dal"; import { TSuperAdminDALFactory } from "./super-admin-dal"; import { LoginMethod, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types"; @@ -90,9 +89,7 @@ export const superAdminServiceFactory = ({ data: TSuperAdminUpdate & { slackClientId?: string; slackClientSecret?: string }, userId: string ) => { - const updatedData = { - ...data - }; + const updatedData = data; if (data.enabledLoginMethods) { const superAdminUser = await userDAL.findById(userId); @@ -263,12 +260,6 @@ export const superAdminServiceFactory = ({ return user; }; - const getCustomSlackBotCreationUrl = async () => { - return `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent( - JSON.stringify(getCustomSlackBotManifest()) - )}`; - }; - const getAdminSlackConfig = async () => { const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); @@ -301,7 +292,6 @@ export const superAdminServiceFactory = ({ adminSignUp, getUsers, deleteUser, - getCustomSlackBotCreationUrl, getAdminSlackConfig }; }; diff --git a/frontend/src/hooks/api/admin/index.ts b/frontend/src/hooks/api/admin/index.ts index 6bd588d99..658feeaa3 100644 --- a/frontend/src/hooks/api/admin/index.ts +++ b/frontend/src/hooks/api/admin/index.ts @@ -4,9 +4,4 @@ export { useUpdateAdminSlackConfig, useUpdateServerConfig } from "./mutation"; -export { - useAdminGetUsers, - useGetAdminSlackConfig, - useGetCustomSlackAppCreationUrl, - useGetServerConfig -} from "./queries"; +export { useAdminGetUsers, useGetAdminSlackConfig, useGetServerConfig } from "./queries"; diff --git a/frontend/src/hooks/api/admin/queries.ts b/frontend/src/hooks/api/admin/queries.ts index fbcc6e946..653b124d7 100644 --- a/frontend/src/hooks/api/admin/queries.ts +++ b/frontend/src/hooks/api/admin/queries.ts @@ -12,7 +12,6 @@ export const adminStandaloneKeys = { export const adminQueryKeys = { serverConfig: () => ["server-config"] as const, getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const, - getCustomSlackAppCreationUrl: () => ["custom-slack-app-creation-url"] as const, getAdminSlackConfig: () => ["admin-slack-config"] as const }; @@ -62,18 +61,6 @@ export const useAdminGetUsers = (filters: AdminGetUsersFilters) => { }); }; -export const useGetCustomSlackAppCreationUrl = () => - useQuery({ - queryKey: adminQueryKeys.getCustomSlackAppCreationUrl(), - queryFn: async () => { - const { data } = await apiRequest.get( - "/api/v1/admin/integrations/slack/bot-creation-url" - ); - - return data; - } - }); - export const useGetAdminSlackConfig = () => useQuery({ queryKey: adminQueryKeys.getAdminSlackConfig(), diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgWorkflowIntegrationTab/SlackIntegrationForm.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgWorkflowIntegrationTab/SlackIntegrationForm.tsx index f8aea135c..87a49647e 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgWorkflowIntegrationTab/SlackIntegrationForm.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgWorkflowIntegrationTab/SlackIntegrationForm.tsx @@ -121,7 +121,11 @@ export const SlackIntegrationForm = ({ id, onClose }: Props) => { )}
-
-