diff --git a/backend/src/db/migrations/20240830142938_native-slack-integration.ts b/backend/src/db/migrations/20240830142938_native-slack-integration.ts index d4fb22241..5e80bdb77 100644 --- a/backend/src/db/migrations/20240830142938_native-slack-integration.ts +++ b/backend/src/db/migrations/20240830142938_native-slack-integration.ts @@ -16,10 +16,10 @@ export async function up(knex: Knex): Promise { tb.binary("encryptedBotAccessToken").notNullable(); tb.string("slackBotId").notNullable(); tb.string("slackBotUserId").notNullable(); - tb.boolean("isAccessRequestNotificationEnabled").defaultTo(false); - tb.string("accessRequestChannels").defaultTo(""); - tb.boolean("isSecretRequestNotificationEnabled").defaultTo(false); - tb.string("secretRequestChannels").defaultTo(""); + tb.boolean("isAccessRequestNotificationEnabled").notNullable().defaultTo(false); + tb.string("accessRequestChannels").notNullable().defaultTo(""); + tb.boolean("isSecretRequestNotificationEnabled").notNullable().defaultTo(false); + tb.string("secretRequestChannels").notNullable().defaultTo(""); tb.timestamps(true, true, true); }); diff --git a/backend/src/db/schemas/slack-integrations.ts b/backend/src/db/schemas/slack-integrations.ts index 39e00c22b..2f938d94b 100644 --- a/backend/src/db/schemas/slack-integrations.ts +++ b/backend/src/db/schemas/slack-integrations.ts @@ -19,10 +19,10 @@ export const SlackIntegrationsSchema = z.object({ encryptedBotAccessToken: zodBuffer, slackBotId: z.string(), slackBotUserId: z.string(), - isAccessRequestNotificationEnabled: z.boolean().nullable().optional(), - accessRequestChannels: z.string().nullable().optional(), - isSecretRequestNotificationEnabled: z.boolean().nullable().optional(), - secretRequestChannels: z.string().nullable().optional(), + isAccessRequestNotificationEnabled: z.boolean().default(false), + accessRequestChannels: z.string().default(""), + isSecretRequestNotificationEnabled: z.boolean().default(false), + secretRequestChannels: z.string().default(""), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 981b3777e..3fa9260aa 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -169,7 +169,11 @@ export enum EventType { GET_CERTIFICATE_TEMPLATE = "get-certificate-template", CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "create-certificate-template-est-config", UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "update-certificate-template-est-config", - GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config" + GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config", + ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration", + GET_SLACK_INTEGRATION = "get-slack-integration", + UPDATE_SLACK_INTEGRATION = "update-slack-integration", + DELETE_SLACK_INTEGRATION = "delete-slack-integration" } interface UserActorMetadata { @@ -1446,6 +1450,38 @@ interface GetCertificateTemplateEstConfig { }; } +interface AttemptCreateSlackIntegration { + type: EventType.ATTEMPT_CREATE_SLACK_INTEGRATION; + metadata: { + projectId?: string; + }; // no metadata +} + +interface UpdateSlackIntegration { + type: EventType.UPDATE_SLACK_INTEGRATION; + metadata: { + id: string; + isAccessRequestNotificationEnabled: boolean; + accessRequestChannels: string; + isSecretRequestNotificationEnabled: boolean; + secretRequestChannels: string; + }; +} + +interface DeleteSlackIntegration { + type: EventType.DELETE_SLACK_INTEGRATION; + metadata: { + id: string; + }; +} + +interface GetSlackIntegration { + type: EventType.GET_SLACK_INTEGRATION; + metadata: { + id: string; + }; +} + export type Event = | GetSecretsEvent | GetSecretEvent @@ -1576,4 +1612,8 @@ export type Event = | DeleteCertificateTemplate | CreateCertificateTemplateEstConfig | UpdateCertificateTemplateEstConfig - | GetCertificateTemplateEstConfig; + | GetCertificateTemplateEstConfig + | AttemptCreateSlackIntegration + | UpdateSlackIntegration + | DeleteSlackIntegration + | GetSlackIntegration; diff --git a/backend/src/server/routes/v1/slack-router.ts b/backend/src/server/routes/v1/slack-router.ts index ac4800ccb..a4787987c 100644 --- a/backend/src/server/routes/v1/slack-router.ts +++ b/backend/src/server/routes/v1/slack-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { SlackIntegrationsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { getConfig } from "@app/lib/config/env"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -31,14 +32,24 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - // TODO: add audit logs - return server.services.slack.getInstallUrl({ + const url = await server.services.slack.getInstallUrl({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.query.projectId }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.query.projectId, + event: { + type: EventType.ATTEMPT_CREATE_SLACK_INTEGRATION, + metadata: {} + } + }); + + return url; } }); @@ -70,13 +81,26 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - return server.services.slack.getSlackIntegrationByProjectId({ + const slackIntegration = await server.services.slack.getSlackIntegrationByProjectId({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.query.projectId }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.query.projectId, + event: { + type: EventType.GET_SLACK_INTEGRATION, + metadata: { + id: slackIntegration?.id + } + } + }); + + return slackIntegration; } }); @@ -114,8 +138,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - // TODO: add audit logs - return server.services.slack.updateSlackIntegration({ + const updatedSlackIntegration = await server.services.slack.updateSlackIntegration({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -123,6 +146,23 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => { id: req.params.slackIntegrationId, ...req.body }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: updatedSlackIntegration.projectId, + event: { + type: EventType.UPDATE_SLACK_INTEGRATION, + metadata: { + id: updatedSlackIntegration.id, + isAccessRequestNotificationEnabled: updatedSlackIntegration.isAccessRequestNotificationEnabled, + accessRequestChannels: updatedSlackIntegration.accessRequestChannels, + isSecretRequestNotificationEnabled: updatedSlackIntegration.isSecretRequestNotificationEnabled, + secretRequestChannels: updatedSlackIntegration.secretRequestChannels + } + } + }); + + return updatedSlackIntegration; } }); @@ -154,15 +194,26 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - // TODO: add audit logs - - return server.services.slack.deleteSlackIntegration({ + const deletedSlackIntegration = await server.services.slack.deleteSlackIntegration({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.slackIntegrationId }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: deletedSlackIntegration.projectId, + event: { + type: EventType.DELETE_SLACK_INTEGRATION, + metadata: { + id: deletedSlackIntegration.id + } + } + }); + + return deletedSlackIntegration; } }); diff --git a/backend/src/services/slack/slack-service.ts b/backend/src/services/slack/slack-service.ts index aa72d0d82..c32c1e466 100644 --- a/backend/src/services/slack/slack-service.ts +++ b/backend/src/services/slack/slack-service.ts @@ -162,7 +162,7 @@ export const slackServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Settings); const project = await projectDAL.findById(projectId); if (!project) { throw new NotFoundError({ diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index b9ba07264..7e59e639f 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -77,7 +77,11 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG]: "Create certificate template EST configuration", [EventType.UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG]: - "Update certificate template EST configuration" + "Update certificate template EST configuration", + [EventType.UPDATE_SLACK_INTEGRATION]: "Update slack integration", + [EventType.DELETE_SLACK_INTEGRATION]: "Delete slack integration", + [EventType.GET_SLACK_INTEGRATION]: "Get slack integration", + [EventType.ATTEMPT_CREATE_SLACK_INTEGRATION]: "Initiate create slack integration flow" }; export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = { diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 6df32aa0b..5d58b2be1 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -89,5 +89,9 @@ export enum EventType { GET_CERTIFICATE_TEMPLATE = "get-certificate-template", CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "create-certificate-template-est-config", UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "update-certificate-template-est-config", - GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config" + GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config", + ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration", + GET_SLACK_INTEGRATION = "get-slack-integration", + UPDATE_SLACK_INTEGRATION = "update-slack-integration", + DELETE_SLACK_INTEGRATION = "delete-slack-integration" } diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index f9b53d037..959f9aaa3 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -742,6 +742,31 @@ interface GetCertificateTemplateEstConfig { }; } +interface UpdateSlackIntegration { + type: EventType.UPDATE_SLACK_INTEGRATION; + metadata: { + id: string; + isAccessRequestNotificationEnabled: boolean; + accessRequestChannels: string; + isSecretRequestNotificationEnabled: boolean; + secretRequestChannels: string; + }; +} + +interface DeleteSlackIntegration { + type: EventType.DELETE_SLACK_INTEGRATION; + metadata: { + id: string; + }; +} + +interface GetSlackIntegration { + type: EventType.GET_SLACK_INTEGRATION; + metadata: { + id: string; + }; +} + export type Event = | GetSecretsEvent | GetSecretEvent @@ -817,7 +842,10 @@ export type Event = | DeleteCertificateTemplate | UpdateCertificateTemplateEstConfig | CreateCertificateTemplateEstConfig - | GetCertificateTemplateEstConfig; + | GetCertificateTemplateEstConfig + | UpdateSlackIntegration + | DeleteSlackIntegration + | GetSlackIntegration; export type AuditLog = { id: string; diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx index c1652aaba..ec53eec3b 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx @@ -442,6 +442,23 @@ export const LogsTableRow = ({ auditLog }: Props) => {

{`Certificate Template ID: ${event.metadata.certificateTemplateId}`}

); + case EventType.UPDATE_SLACK_INTEGRATION: + return ( + +

{`Slack integration ID: ${event.metadata.id}`}

+

{`Access Request Notification Status: ${event.metadata.isAccessRequestNotificationEnabled}`}

+

{`Access Request Channels: ${event.metadata.accessRequestChannels}`}

+

{`Secret Approval Request Notification Status: ${event.metadata.isSecretRequestNotificationEnabled}`}

+

{`Secret Request Channels: ${event.metadata.secretRequestChannels}`}

+ + ); + case EventType.DELETE_SLACK_INTEGRATION: + case EventType.GET_SLACK_INTEGRATION: + return ( + +

{`Slack integration ID: ${event.metadata.id}`}

+ + ); default: return ; }