mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 12:38:36 +00:00
feat: slack channel suggestions
This commit is contained in:
@@ -1,11 +1,44 @@
|
||||
import { Block, WebClient } from "@slack/web-api";
|
||||
|
||||
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 fetchSlackChannels = async (botKey: string) => {
|
||||
const slackChannels: {
|
||||
name: string;
|
||||
id: string;
|
||||
}[] = [];
|
||||
|
||||
const slackWebClient = new WebClient(botKey);
|
||||
let cursor;
|
||||
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const response = await slackWebClient.conversations.list({
|
||||
cursor,
|
||||
limit: 1000,
|
||||
types: "public_channel,private_channel"
|
||||
});
|
||||
|
||||
response.channels?.forEach((channel) =>
|
||||
slackChannels.push({
|
||||
name: channel.name_normalized as string,
|
||||
id: channel.id as string
|
||||
})
|
||||
);
|
||||
|
||||
// Set the cursor for the next page
|
||||
cursor = response.response_metadata?.next_cursor;
|
||||
} while (cursor); // Continue while there is a cursor
|
||||
|
||||
return slackChannels;
|
||||
};
|
||||
|
||||
export const triggerSlackNotification = async ({
|
||||
projectId,
|
||||
payloadBlocks,
|
||||
@@ -30,15 +63,15 @@ export const triggerSlackNotification = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let targetChannels: string[] = [];
|
||||
let targetChannelIds: string[] = [];
|
||||
if (feature === SlackTriggerFeature.ACCESS_REQUEST) {
|
||||
targetChannels = slackIntegration.accessRequestChannels?.split(", ") || [];
|
||||
if (!targetChannels.length || !slackIntegration.isAccessRequestNotificationEnabled) {
|
||||
targetChannelIds = slackIntegration.accessRequestChannels?.split(", ") || [];
|
||||
if (!targetChannelIds.length || !slackIntegration.isAccessRequestNotificationEnabled) {
|
||||
return;
|
||||
}
|
||||
} else if (feature === SlackTriggerFeature.SECRET_APPROVAL) {
|
||||
targetChannels = slackIntegration.secretRequestChannels?.split(", ") || [];
|
||||
if (!targetChannels.length || !slackIntegration.isSecretRequestNotificationEnabled) {
|
||||
targetChannelIds = slackIntegration.secretRequestChannels?.split(", ") || [];
|
||||
if (!targetChannelIds.length || !slackIntegration.isSecretRequestNotificationEnabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -52,35 +85,16 @@ export const triggerSlackNotification = async ({
|
||||
cipherTextBlob: slackIntegration.encryptedBotAccessToken
|
||||
}).toString("utf8");
|
||||
|
||||
const targetChannelSet = new Set<string>(targetChannels);
|
||||
const slackWebClient = new WebClient(botKey);
|
||||
const channelIdsToSendNotif: string[] = [];
|
||||
let cursor;
|
||||
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const response = await slackWebClient.conversations.list({
|
||||
cursor,
|
||||
limit: 1000,
|
||||
types: "public_channel,private_channel"
|
||||
});
|
||||
|
||||
response.channels?.forEach((channel) => {
|
||||
if (channel.name_normalized && targetChannelSet.has(channel.name_normalized)) {
|
||||
channelIdsToSendNotif.push(channel.id as string);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the cursor for the next page
|
||||
cursor = response.response_metadata?.next_cursor;
|
||||
} while (cursor); // Continue while there is a cursor
|
||||
|
||||
for await (const conversationId of channelIdsToSendNotif) {
|
||||
for await (const conversationId of targetChannelIds) {
|
||||
// we send both text and blocks for compatibility with barebone clients
|
||||
await slackWebClient.chat.postMessage({
|
||||
channel: conversationId,
|
||||
text: payloadMessage,
|
||||
blocks: payloadBlocks
|
||||
});
|
||||
await slackWebClient.chat
|
||||
.postMessage({
|
||||
channel: conversationId,
|
||||
text: payloadMessage,
|
||||
blocks: payloadBlocks
|
||||
})
|
||||
.catch((err) => void logger.error(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { fetchSlackChannels } from "./slack-fns";
|
||||
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
||||
import {
|
||||
TCompleteSlackIntegrationDTO,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
TGetSlackInstallUrlDTO,
|
||||
TGetSlackIntegrationByIdDTO,
|
||||
TGetSlackIntegrationByOrgDTO,
|
||||
TGetSlackIntegrationChannelsDTO,
|
||||
TReinstallSlackIntegrationDTO,
|
||||
TUpdateSlackIntegrationDTO
|
||||
} from "./slack-types";
|
||||
@@ -285,6 +287,42 @@ export const slackServiceFactory = ({
|
||||
return slackIntegration;
|
||||
};
|
||||
|
||||
const getSlackIntegrationChannels = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
id
|
||||
}: TGetSlackIntegrationChannelsDTO) => {
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found."
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
slackIntegration.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||
|
||||
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
orgId: slackIntegration.orgId,
|
||||
type: KmsDataKey.Organization
|
||||
});
|
||||
|
||||
const botKey = orgDataKeyDecryptor({
|
||||
cipherTextBlob: slackIntegration.encryptedBotAccessToken
|
||||
}).toString("utf8");
|
||||
|
||||
return fetchSlackChannels(botKey);
|
||||
};
|
||||
|
||||
const updateSlackIntegration = async ({
|
||||
actorId,
|
||||
actor,
|
||||
@@ -352,6 +390,7 @@ export const slackServiceFactory = ({
|
||||
completeSlackIntegration,
|
||||
getSlackInstaller,
|
||||
updateSlackIntegration,
|
||||
deleteSlackIntegration
|
||||
deleteSlackIntegration,
|
||||
getSlackIntegrationChannels
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ export type TGetSlackIntegrationByOrgDTO = Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TGetSlackIntegrationByIdDTO = { id: string } & Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TGetSlackIntegrationChannelsDTO = { id: string } & Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TUpdateSlackIntegrationDTO = { id: string; slug?: string; description?: string } & Omit<
|
||||
TOrgPermission,
|
||||
"orgId"
|
||||
|
||||
Reference in New Issue
Block a user