feat: integrated secret approval request

This commit is contained in:
Sheen Capadngan
2024-09-02 15:38:05 +08:00
parent fe096772e0
commit d604ef2480
8 changed files with 198 additions and 4 deletions
+88
View File
@@ -0,0 +1,88 @@
import { Block, WebClient } from "@slack/web-api";
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TProjectDALFactory } from "../project/project-dal";
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
import { SlackTriggerFeature } from "./slack-types";
export const triggerSlackNotification = async ({
projectId,
payloadBlocks,
payloadMessage,
slackIntegrationDAL,
projectDAL,
kmsService,
feature
}: {
projectId: string;
payloadBlocks: Block[];
payloadMessage: string;
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
feature: SlackTriggerFeature;
}) => {
const project = await projectDAL.findById(projectId);
const slackIntegration = await slackIntegrationDAL.findOne({
projectId
});
if (!slackIntegration) {
return;
}
let targetChannels: string[] = [];
if (feature === SlackTriggerFeature.ACCESS_REQUEST) {
targetChannels = slackIntegration.accessRequestChannels?.split(",") || [];
if (!targetChannels.length || !slackIntegration.isAccessRequestNotificationEnabled) {
return;
}
} else if (feature === SlackTriggerFeature.SECRET_APPROVAL) {
targetChannels = slackIntegration.secretRequestChannels?.split(",") || [];
if (!targetChannels.length || !slackIntegration.isSecretRequestNotificationEnabled) {
return;
}
}
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: project.orgId
});
const botKey = orgDataKeyDecryptor({
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) {
// we send both text and blocks for compatibility with barebone clients
await slackWebClient.chat.postMessage({
channel: conversationId,
text: payloadMessage,
blocks: payloadBlocks
});
}
};
+4 -1
View File
@@ -102,9 +102,12 @@ export const slackServiceFactory = ({
slackBotUserId: installation.bot?.userId || ""
});
},
// for our use-case we don't need to implement this because this will only be used
// when listening for events from slack
fetchInstallation: () => {
return {} as never;
},
// for our use-case we don't need to implement this yet
deleteInstallation: () => {
return {} as never;
}
@@ -132,7 +135,7 @@ export const slackServiceFactory = ({
const installer = await getSlackInstaller();
const url = await installer.generateInstallUrl({
scopes: ["chat:write"],
scopes: ["chat:write.public", "chat:write", "channels:read", "groups:read", "im:read", "mpim:read"],
metadata: JSON.stringify({
projectId: project.id
}),
@@ -12,3 +12,8 @@ export type TCompleteSlackIntegrationDTO = {
slackBotId: string;
slackBotUserId: string;
};
export enum SlackTriggerFeature {
SECRET_APPROVAL = "secret-approval",
ACCESS_REQUEST = "access-request"
}