mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 21:29:20 +00:00
misc: addressed review comments
This commit is contained in:
@@ -1,11 +1,6 @@
|
|||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
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";
|
import { TVerifyPermission } from "./access-approval-request-types";
|
||||||
|
|
||||||
@@ -56,67 +51,3 @@ export const verifyRequestedPermissions = ({ permissions }: TVerifyPermission) =
|
|||||||
accessTypes: requestedPermissions.filter(filterUnique)
|
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<TProjectDALFactory, "findById" | "findProjectWithOrg">;
|
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
|
||||||
}) => {
|
|
||||||
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
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
+18
-11
@@ -10,6 +10,8 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-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 { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
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 { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
|
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 { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-request-reviewer-dal";
|
||||||
import {
|
import {
|
||||||
ApprovalStatus,
|
ApprovalStatus,
|
||||||
@@ -178,19 +180,24 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||||
const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`;
|
const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`;
|
||||||
|
|
||||||
await triggerAccessRequestSlackNotif({
|
await triggerSlackNotification({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
projectName: project.name,
|
projectSlackConfigDAL,
|
||||||
requesterFullName,
|
|
||||||
isTemporary,
|
|
||||||
requesterEmail: requestedByUser.email as string,
|
|
||||||
secretPath,
|
|
||||||
environment: envSlug,
|
|
||||||
permissions: accessTypes,
|
|
||||||
approvalUrl,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
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({
|
await smtpService.sendMail({
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { TSecretApprovalRequests } from "@app/db/schemas";
|
import { TSecretApprovalRequests } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
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 { 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";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
|
|
||||||
@@ -18,68 +13,6 @@ type TSendApprovalEmails = {
|
|||||||
secretApprovalRequest: TSecretApprovalRequests;
|
secretApprovalRequest: TSecretApprovalRequests;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TTriggerSecretApprovalSlackNotif = {
|
|
||||||
environment: string;
|
|
||||||
projectId: string;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectWithOrg">;
|
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
|
||||||
secretApprovalRequest: TSecretApprovalRequests;
|
|
||||||
secretPath: string;
|
|
||||||
userDAL: Pick<TUserDALFactory, "findById">;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 ({
|
export const sendApprovalEmailsFn = async ({
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
|||||||
+29
-13
@@ -48,6 +48,8 @@ import {
|
|||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-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 { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
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 { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
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 { TSecretApprovalRequestReviewerDALFactory } from "./secret-approval-request-reviewer-dal";
|
||||||
import { TSecretApprovalRequestSecretDALFactory } from "./secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "./secret-approval-request-secret-dal";
|
||||||
import {
|
import {
|
||||||
@@ -1073,15 +1075,22 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
await triggerSecretApprovalSlackNotif({
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
|
await triggerSlackNotification({
|
||||||
projectId,
|
projectId,
|
||||||
secretPath: policy.secretPath as string,
|
|
||||||
environment: env.name,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
secretApprovalRequest,
|
projectSlackConfigDAL,
|
||||||
userDAL,
|
notification: {
|
||||||
projectSlackConfigDAL
|
type: SlackTriggerFeature.SECRET_APPROVAL,
|
||||||
|
payload: {
|
||||||
|
userEmail: user.email as string,
|
||||||
|
environment: env.name,
|
||||||
|
secretPath,
|
||||||
|
projectId,
|
||||||
|
requestId: secretApprovalRequest.id
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendApprovalEmailsFn({
|
await sendApprovalEmailsFn({
|
||||||
@@ -1346,16 +1355,23 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
return { ...doc, commits: approvalCommits };
|
return { ...doc, commits: approvalCommits };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
await triggerSecretApprovalSlackNotif({
|
await triggerSlackNotification({
|
||||||
secretPath: policy.secretPath as string,
|
|
||||||
environment: env.name,
|
|
||||||
projectId,
|
projectId,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
secretApprovalRequest,
|
projectSlackConfigDAL,
|
||||||
userDAL,
|
notification: {
|
||||||
projectSlackConfigDAL
|
type: SlackTriggerFeature.SECRET_APPROVAL,
|
||||||
|
payload: {
|
||||||
|
userEmail: user.email as string,
|
||||||
|
environment: env.name,
|
||||||
|
secretPath,
|
||||||
|
projectId,
|
||||||
|
requestId: secretApprovalRequest.id
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendApprovalEmailsFn({
|
await sendApprovalEmailsFn({
|
||||||
|
|||||||
@@ -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({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/integrations/slack/config",
|
url: "/integrations/slack/config",
|
||||||
|
|||||||
@@ -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 { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { KmsDataKey } from "../kms/kms-types";
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "./project-slack-config-dal";
|
import { TProjectSlackConfigDALFactory } from "./project-slack-config-dal";
|
||||||
import { SlackTriggerFeature } from "./slack-types";
|
import { SlackTriggerFeature, TSlackNotification } 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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchSlackChannels = async (botKey: string) => {
|
export const fetchSlackChannels = async (botKey: string) => {
|
||||||
const slackChannels: {
|
const slackChannels: {
|
||||||
@@ -80,23 +41,98 @@ export const fetchSlackChannels = async (botKey: string) => {
|
|||||||
return slackChannels;
|
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 ({
|
export const triggerSlackNotification = async ({
|
||||||
projectId,
|
projectId,
|
||||||
payloadBlocks,
|
notification,
|
||||||
payloadMessage,
|
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService
|
||||||
feature
|
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
payloadBlocks: Block[];
|
notification: TSlackNotification;
|
||||||
payloadMessage: string;
|
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
feature: SlackTriggerFeature;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { payloadMessage, payloadBlocks } = buildSlackPayload(notification);
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
const slackIntegration = await projectSlackConfigDAL.getIntegrationDetailsByProject(project.id);
|
const slackIntegration = await projectSlackConfigDAL.getIntegrationDetailsByProject(project.id);
|
||||||
|
|
||||||
@@ -105,12 +141,12 @@ export const triggerSlackNotification = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let targetChannelIds: string[] = [];
|
let targetChannelIds: string[] = [];
|
||||||
if (feature === SlackTriggerFeature.ACCESS_REQUEST) {
|
if (notification.type === SlackTriggerFeature.ACCESS_REQUEST) {
|
||||||
targetChannelIds = slackIntegration.accessRequestChannels?.split(", ") || [];
|
targetChannelIds = slackIntegration.accessRequestChannels?.split(", ") || [];
|
||||||
if (!targetChannelIds.length || !slackIntegration.isAccessRequestNotificationEnabled) {
|
if (!targetChannelIds.length || !slackIntegration.isAccessRequestNotificationEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (feature === SlackTriggerFeature.SECRET_APPROVAL) {
|
} else if (notification.type === SlackTriggerFeature.SECRET_APPROVAL) {
|
||||||
targetChannelIds = slackIntegration.secretRequestChannels?.split(", ") || [];
|
targetChannelIds = slackIntegration.secretRequestChannels?.split(", ") || [];
|
||||||
if (!targetChannelIds.length || !slackIntegration.isSecretRequestNotificationEnabled) {
|
if (!targetChannelIds.length || !slackIntegration.isSecretRequestNotificationEnabled) {
|
||||||
return;
|
return;
|
||||||
@@ -136,6 +172,6 @@ export const triggerSlackNotification = async ({
|
|||||||
text: payloadMessage,
|
text: payloadMessage,
|
||||||
blocks: payloadBlocks
|
blocks: payloadBlocks
|
||||||
})
|
})
|
||||||
.catch((err) => void logger.error(err));
|
.catch((err) => logger.error(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const slackServiceFactory = ({
|
|||||||
message: `Invalid Slack configuration. ${
|
message: `Invalid Slack configuration. ${
|
||||||
appCfg.isCloud
|
appCfg.isCloud
|
||||||
? "Please contact the Infisical team."
|
? "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."
|
||||||
}`
|
}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,3 +52,28 @@ export enum SlackTriggerFeature {
|
|||||||
SECRET_APPROVAL = "secret-approval",
|
SECRET_APPROVAL = "secret-approval",
|
||||||
ACCESS_REQUEST = "access-request"
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { TAuthLoginFactory } from "../auth/auth-login-service";
|
|||||||
import { AuthMethod } from "../auth/auth-type";
|
import { AuthMethod } from "../auth/auth-type";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { getCustomSlackBotManifest } from "../slack/slack-fns";
|
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||||
import { LoginMethod, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
|
import { LoginMethod, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
|
||||||
@@ -90,9 +89,7 @@ export const superAdminServiceFactory = ({
|
|||||||
data: TSuperAdminUpdate & { slackClientId?: string; slackClientSecret?: string },
|
data: TSuperAdminUpdate & { slackClientId?: string; slackClientSecret?: string },
|
||||||
userId: string
|
userId: string
|
||||||
) => {
|
) => {
|
||||||
const updatedData = {
|
const updatedData = data;
|
||||||
...data
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.enabledLoginMethods) {
|
if (data.enabledLoginMethods) {
|
||||||
const superAdminUser = await userDAL.findById(userId);
|
const superAdminUser = await userDAL.findById(userId);
|
||||||
@@ -263,12 +260,6 @@ export const superAdminServiceFactory = ({
|
|||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCustomSlackBotCreationUrl = async () => {
|
|
||||||
return `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(
|
|
||||||
JSON.stringify(getCustomSlackBotManifest())
|
|
||||||
)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAdminSlackConfig = async () => {
|
const getAdminSlackConfig = async () => {
|
||||||
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
|
||||||
@@ -301,7 +292,6 @@ export const superAdminServiceFactory = ({
|
|||||||
adminSignUp,
|
adminSignUp,
|
||||||
getUsers,
|
getUsers,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
getCustomSlackBotCreationUrl,
|
|
||||||
getAdminSlackConfig
|
getAdminSlackConfig
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,9 +4,4 @@ export {
|
|||||||
useUpdateAdminSlackConfig,
|
useUpdateAdminSlackConfig,
|
||||||
useUpdateServerConfig
|
useUpdateServerConfig
|
||||||
} from "./mutation";
|
} from "./mutation";
|
||||||
export {
|
export { useAdminGetUsers, useGetAdminSlackConfig, useGetServerConfig } from "./queries";
|
||||||
useAdminGetUsers,
|
|
||||||
useGetAdminSlackConfig,
|
|
||||||
useGetCustomSlackAppCreationUrl,
|
|
||||||
useGetServerConfig
|
|
||||||
} from "./queries";
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const adminStandaloneKeys = {
|
|||||||
export const adminQueryKeys = {
|
export const adminQueryKeys = {
|
||||||
serverConfig: () => ["server-config"] as const,
|
serverConfig: () => ["server-config"] as const,
|
||||||
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
|
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
|
||||||
getCustomSlackAppCreationUrl: () => ["custom-slack-app-creation-url"] as const,
|
|
||||||
getAdminSlackConfig: () => ["admin-slack-config"] 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<string>(
|
|
||||||
"/api/v1/admin/integrations/slack/bot-creation-url"
|
|
||||||
);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useGetAdminSlackConfig = () =>
|
export const useGetAdminSlackConfig = () =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: adminQueryKeys.getAdminSlackConfig(),
|
queryKey: adminQueryKeys.getAdminSlackConfig(),
|
||||||
|
|||||||
+5
-1
@@ -121,7 +121,11 @@ export const SlackIntegrationForm = ({ id, onClose }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
<div className="mt-6 flex items-center space-x-4">
|
<div className="mt-6 flex items-center space-x-4">
|
||||||
<Button type="submit" isLoading={isSubmitting || isConnectLoading} isDisabled={!isDirty}>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting || isConnectLoading}
|
||||||
|
isDisabled={!isDirty || isConnectLoading || isSubmitting}
|
||||||
|
>
|
||||||
{slackIntegration ? "Save" : "Connect Slack"}
|
{slackIntegration ? "Save" : "Connect Slack"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline_bg" onClick={onClose}>
|
<Button variant="outline_bg" onClick={onClose}>
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
import {
|
import { useGetAdminSlackConfig, useUpdateServerConfig } from "@app/hooks/api";
|
||||||
useGetAdminSlackConfig,
|
|
||||||
useGetCustomSlackAppCreationUrl,
|
|
||||||
useUpdateServerConfig
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
|
|
||||||
const slackFormSchema = z.object({
|
const slackFormSchema = z.object({
|
||||||
clientId: z.string(),
|
clientId: z.string(),
|
||||||
@@ -18,6 +14,45 @@ const slackFormSchema = z.object({
|
|||||||
|
|
||||||
type TSlackForm = z.infer<typeof slackFormSchema>;
|
type TSlackForm = z.infer<typeof slackFormSchema>;
|
||||||
|
|
||||||
|
const getCustomSlackAppCreationUrl = () =>
|
||||||
|
`https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(
|
||||||
|
JSON.stringify({
|
||||||
|
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: [`${window.origin}/api/v1/workflow-integrations/slack/oauth_redirect`],
|
||||||
|
scopes: {
|
||||||
|
bot: ["chat:write.public", "chat:write"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
org_deploy_enabled: false,
|
||||||
|
socket_mode_enabled: false,
|
||||||
|
token_rotation_enabled: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)}`;
|
||||||
|
|
||||||
export const IntegrationPanel = () => {
|
export const IntegrationPanel = () => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -28,9 +63,7 @@ export const IntegrationPanel = () => {
|
|||||||
resolver: zodResolver(slackFormSchema)
|
resolver: zodResolver(slackFormSchema)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: customSlackAppCreationUrl } = useGetCustomSlackAppCreationUrl();
|
|
||||||
const { data: adminSlackConfig } = useGetAdminSlackConfig();
|
const { data: adminSlackConfig } = useGetAdminSlackConfig();
|
||||||
|
|
||||||
const { mutateAsync: updateAdminServerConfig } = useUpdateServerConfig();
|
const { mutateAsync: updateAdminServerConfig } = useUpdateServerConfig();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -63,7 +96,10 @@ export const IntegrationPanel = () => {
|
|||||||
Step 1: Create your Infisical Slack App
|
Step 1: Create your Infisical Slack App
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Button colorSchema="secondary" onClick={() => window.open(customSlackAppCreationUrl)}>
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => window.open(getCustomSlackAppCreationUrl())}
|
||||||
|
>
|
||||||
Create Slack App
|
Create Slack App
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user