mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat: slack integration deletion
This commit is contained in:
@@ -126,6 +126,46 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:slackIntegrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
slackIntegrationId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: SlackIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
teamName: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
// TODO: add audit logs
|
||||||
|
|
||||||
|
return server.services.slack.deleteSlackIntegration({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.slackIntegrationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/oauth_redirect",
|
url: "/oauth_redirect",
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ import { TProjectDALFactory } from "../project/project-dal";
|
|||||||
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
||||||
import {
|
import {
|
||||||
TCompleteSlackIntegrationDTO,
|
TCompleteSlackIntegrationDTO,
|
||||||
|
TDeleteSlackIntegrationDTO,
|
||||||
TGetSlackInstallUrlDTO,
|
TGetSlackInstallUrlDTO,
|
||||||
TGetSlackIntegrationByProjectId,
|
TGetSlackIntegrationByProjectIdDTO,
|
||||||
TUpdateSlackIntegration
|
TUpdateSlackIntegrationDTO
|
||||||
} from "./slack-types";
|
} from "./slack-types";
|
||||||
|
|
||||||
type TSlackServiceFactoryDep = {
|
type TSlackServiceFactoryDep = {
|
||||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "create" | "findOne" | "findById" | "updateById">;
|
slackIntegrationDAL: Pick<
|
||||||
|
TSlackIntegrationDALFactory,
|
||||||
|
"create" | "findOne" | "findById" | "updateById" | "deleteById"
|
||||||
|
>;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
@@ -156,7 +160,7 @@ export const slackServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
projectId
|
projectId
|
||||||
}: TGetSlackIntegrationByProjectId) => {
|
}: TGetSlackIntegrationByProjectIdDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -183,7 +187,7 @@ export const slackServiceFactory = ({
|
|||||||
accessRequestChannels,
|
accessRequestChannels,
|
||||||
isSecretRequestNotificationEnabled,
|
isSecretRequestNotificationEnabled,
|
||||||
secretRequestChannels
|
secretRequestChannels
|
||||||
}: TUpdateSlackIntegration) => {
|
}: TUpdateSlackIntegrationDTO) => {
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -208,11 +212,38 @@ export const slackServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteSlackIntegration = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
id
|
||||||
|
}: TDeleteSlackIntegrationDTO) => {
|
||||||
|
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||||
|
if (!slackIntegration) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Slack integration not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
slackIntegration.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
|
||||||
|
|
||||||
|
return slackIntegrationDAL.deleteById(id);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getInstallUrl,
|
getInstallUrl,
|
||||||
getSlackIntegrationByProjectId,
|
getSlackIntegrationByProjectId,
|
||||||
completeSlackIntegration,
|
completeSlackIntegration,
|
||||||
getSlackInstaller,
|
getSlackInstaller,
|
||||||
updateSlackIntegration
|
updateSlackIntegration,
|
||||||
|
deleteSlackIntegration
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
|
|
||||||
export type TGetSlackInstallUrlDTO = TProjectPermission;
|
export type TGetSlackInstallUrlDTO = TProjectPermission;
|
||||||
|
|
||||||
export type TGetSlackIntegrationByProjectId = TProjectPermission;
|
export type TGetSlackIntegrationByProjectIdDTO = TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateSlackIntegration = {
|
export type TUpdateSlackIntegrationDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
isAccessRequestNotificationEnabled?: boolean;
|
isAccessRequestNotificationEnabled?: boolean;
|
||||||
accessRequestChannels?: string;
|
accessRequestChannels?: string;
|
||||||
@@ -12,6 +12,10 @@ export type TUpdateSlackIntegration = {
|
|||||||
secretRequestChannels?: string;
|
secretRequestChannels?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeleteSlackIntegrationDTO = {
|
||||||
|
id: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCompleteSlackIntegrationDTO = {
|
export type TCompleteSlackIntegrationDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { useUpdateSlackIntegration } from "./mutation";
|
export { useDeleteSlackIntegration, useUpdateSlackIntegration } from "./mutation";
|
||||||
export { fetchSlackInstallUrl, useGetSlackIntegrationByProject } from "./queries";
|
export { fetchSlackInstallUrl, useGetSlackIntegrationByProject } from "./queries";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { slackKeys } from "./queries";
|
import { slackKeys } from "./queries";
|
||||||
import { TUpdateSlackIntegrationDTO } from "./types";
|
import { TDeleteSlackIntegrationDTO, TUpdateSlackIntegrationDTO } from "./types";
|
||||||
|
|
||||||
export const useUpdateSlackIntegration = () => {
|
export const useUpdateSlackIntegration = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -19,3 +19,18 @@ export const useUpdateSlackIntegration = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDeleteSlackIntegration = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TDeleteSlackIntegrationDTO>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.delete(`/api/v1/slack/${dto.id}`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(slackKeys.getSlackIntegrationByProject(workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,3 +15,8 @@ export type TUpdateSlackIntegrationDTO = {
|
|||||||
isSecretRequestNotificationEnabled?: boolean;
|
isSecretRequestNotificationEnabled?: boolean;
|
||||||
secretRequestChannels?: string;
|
secretRequestChannels?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TDeleteSlackIntegrationDTO = {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
|||||||
+50
-4
@@ -5,11 +5,19 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, ContentLoader, FormControl, Input, Switch } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
ContentLoader,
|
||||||
|
DeleteActionModal,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Switch
|
||||||
|
} from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
fetchSlackInstallUrl,
|
fetchSlackInstallUrl,
|
||||||
|
useDeleteSlackIntegration,
|
||||||
useGetSlackIntegrationByProject,
|
useGetSlackIntegrationByProject,
|
||||||
useUpdateSlackIntegration
|
useUpdateSlackIntegration
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
@@ -28,6 +36,11 @@ export const NotificationTab = () => {
|
|||||||
const { data: slackIntegration, isLoading: isSlackIntegrationLoading } =
|
const { data: slackIntegration, isLoading: isSlackIntegrationLoading } =
|
||||||
useGetSlackIntegrationByProject(currentWorkspace?.id);
|
useGetSlackIntegrationByProject(currentWorkspace?.id);
|
||||||
const { mutateAsync: updateSlackIntegration } = useUpdateSlackIntegration();
|
const { mutateAsync: updateSlackIntegration } = useUpdateSlackIntegration();
|
||||||
|
const { mutateAsync: deleteSlackIntegration } = useDeleteSlackIntegration();
|
||||||
|
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
|
||||||
|
"deleteSlackIntegration"
|
||||||
|
] as const);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
watch,
|
watch,
|
||||||
@@ -65,6 +78,23 @@ export const NotificationTab = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleIntegrationDelete = async () => {
|
||||||
|
if (!currentWorkspace || !slackIntegration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await deleteSlackIntegration({
|
||||||
|
workspaceId: currentWorkspace.id,
|
||||||
|
id: slackIntegration.id
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpToggle("deleteSlackIntegration", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully deleted slack integration"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (slackIntegration) {
|
if (slackIntegration) {
|
||||||
setValue(
|
setValue(
|
||||||
@@ -85,9 +115,12 @@ export const NotificationTab = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Slack Integration</h2>
|
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Slack Integration
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-4 text-gray-400">
|
<p className="mb-4 text-gray-400">
|
||||||
This integration allows you to send notifications to your Slack workspace in response to
|
This integration allows you to send notifications to your Slack workspace in response to
|
||||||
@@ -114,7 +147,12 @@ export const NotificationTab = () => {
|
|||||||
<Button colorSchema="secondary" size="xs">
|
<Button colorSchema="secondary" size="xs">
|
||||||
Reinstall integration
|
Reinstall integration
|
||||||
</Button>
|
</Button>
|
||||||
<Button colorSchema="secondary" size="xs" className="ml-2">
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
size="xs"
|
||||||
|
className="ml-2"
|
||||||
|
onClick={() => handlePopUpOpen("deleteSlackIntegration")}
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,5 +249,13 @@ export const NotificationTab = () => {
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteSlackIntegration.isOpen}
|
||||||
|
title="Are you sure want to delete your Slack integration?"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteSlackIntegration", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleIntegrationDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user