From 7ae859e9aec9740876209d2887d0efaadc78fd71 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 8 Aug 2023 22:04:23 +0700 Subject: [PATCH] Update secret imports audit log v2 --- .../controllers/v1/secretImportController.ts | 68 ++++-- backend/src/ee/models/auditLog/types.ts | 26 ++- backend/src/models/index.ts | 7 + frontend/src/hooks/api/auditLogs/types.tsx | 26 ++- frontend/src/layouts/AppLayout/AppLayout.tsx | 8 +- .../pages/project/[id]/audit-logs/index.tsx | 202 ++--------------- .../src/pages/project/[id]/logs/index.tsx | 204 ++++++++++++++++-- .../SecretDetailDrawer/SecretDetailDrawer.tsx | 11 +- .../AuditLogsPage.tsx} | 2 +- .../components/LogsFilter.tsx | 7 +- .../components/LogsSection.tsx | 0 .../components/LogsTable.tsx | 0 .../components/LogsTableRow.tsx | 25 ++- .../components/index.tsx | 0 .../components/types.tsx | 0 .../src/views/Project/AuditLogsPage/index.tsx | 1 + frontend/src/views/Project/LogsPage/index.tsx | 1 - 17 files changed, 335 insertions(+), 253 deletions(-) rename frontend/src/views/Project/{LogsPage/LogsPage.tsx => AuditLogsPage/AuditLogsPage.tsx} (90%) rename frontend/src/views/Project/{LogsPage => AuditLogsPage}/components/LogsFilter.tsx (97%) rename frontend/src/views/Project/{LogsPage => AuditLogsPage}/components/LogsSection.tsx (100%) rename frontend/src/views/Project/{LogsPage => AuditLogsPage}/components/LogsTable.tsx (100%) rename frontend/src/views/Project/{LogsPage => AuditLogsPage}/components/LogsTableRow.tsx (91%) rename frontend/src/views/Project/{LogsPage => AuditLogsPage}/components/index.tsx (100%) rename frontend/src/views/Project/{LogsPage => AuditLogsPage}/components/types.tsx (100%) create mode 100644 frontend/src/views/Project/AuditLogsPage/index.tsx delete mode 100644 frontend/src/views/Project/LogsPage/index.tsx diff --git a/backend/src/controllers/v1/secretImportController.ts b/backend/src/controllers/v1/secretImportController.ts index 7cff0a792..08f4929ff 100644 --- a/backend/src/controllers/v1/secretImportController.ts +++ b/backend/src/controllers/v1/secretImportController.ts @@ -1,11 +1,12 @@ import { Request, Response } from "express"; import { validateMembership } from "../../helpers"; -import SecretImport from "../../models/secretImports"; +import { Folder, SecretImport } from "../../models"; import { getAllImportedSecrets } from "../../services/SecretImportService"; -import { BadRequestError } from "../../utils/errors"; +import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { ADMIN, MEMBER } from "../../variables"; import { EEAuditLogService } from "../../ee/services"; import { EventType } from "../../ee/models"; +import { getFolderPath } from "../../services/FolderService"; export const createSecretImport = async (req: Request, res: Response) => { const { workspaceId, environment, folderId, secretImport } = req.body; @@ -14,6 +15,17 @@ export const createSecretImport = async (req: Request, res: Response) => { environment, folderId }); + + const folders = await Folder.findOne({ + workspace: workspaceId, + environment, + }).lean(); + + if (!folders) throw ResourceNotFoundError({ + message: "Failed to find folder" + }); + + const importToSecretPath = await getFolderPath(folders, folderId); if (!importSecDoc) { const doc = new SecretImport({ @@ -29,11 +41,12 @@ export const createSecretImport = async (req: Request, res: Response) => { { type: EventType.CREATE_SECRET_IMPORT, metadata: { - environment, secretImportId: doc._id.toString(), folderId: doc.folderId.toString(), - importEnvironment: secretImport.environment, - importSecretPath: secretImport.secretPath + importFromEnvironment: secretImport.environment, + importFromSecretPath: secretImport.secretPath, + importToEnvironment: environment, + importToSecretPath } }, { @@ -61,11 +74,12 @@ export const createSecretImport = async (req: Request, res: Response) => { { type: EventType.CREATE_SECRET_IMPORT, metadata: { - environment, secretImportId: importSecDoc._id.toString(), folderId: importSecDoc.folderId.toString(), - importEnvironment: secretImport.environment, - importSecretPath: secretImport.secretPath + importFromEnvironment: secretImport.environment, + importFromSecretPath: secretImport.secretPath, + importToEnvironment: environment, + importToSecretPath } }, { @@ -91,17 +105,33 @@ export const updateSecretImport = async (req: Request, res: Response) => { acceptedRoles: [ADMIN, MEMBER] }); + const orderBefore = importSecDoc.imports; importSecDoc.imports = secretImports; + await importSecDoc.save(); + + const folders = await Folder.findOne({ + workspace: importSecDoc.workspace, + environment: importSecDoc.environment, + }).lean(); + + if (!folders) throw ResourceNotFoundError({ + message: "Failed to find folder" + }); + + const importToSecretPath = await getFolderPath(folders, importSecDoc.folderId); + await EEAuditLogService.createAuditLog( req.authData, { type: EventType.UPDATE_SECRET_IMPORT, metadata: { - environment: importSecDoc.environment, + importToEnvironment: importSecDoc.environment, + importToSecretPath, secretImportId: importSecDoc._id.toString(), folderId: importSecDoc.folderId.toString(), - numberOfImports: secretImports.length + orderBefore, + orderAfter: secretImports } }, { @@ -130,16 +160,28 @@ export const deleteSecretImport = async (req: Request, res: Response) => { ); await importSecDoc.save(); + const folders = await Folder.findOne({ + workspace: importSecDoc.workspace, + environment: importSecDoc.environment, + }).lean(); + + if (!folders) throw ResourceNotFoundError({ + message: "Failed to find folder" + }); + + const importToSecretPath = await getFolderPath(folders, importSecDoc.folderId); + await EEAuditLogService.createAuditLog( req.authData, { type: EventType.DELETE_SECRET_IMPORT, metadata: { - environment: importSecDoc.environment, secretImportId: importSecDoc._id.toString(), folderId: importSecDoc.folderId.toString(), - importEnvironment: secretImportEnv, - importSecretPath: secretImportPath + importFromEnvironment: secretImportEnv, + importFromSecretPath: secretImportPath, + importToEnvironment: importSecDoc.environment, + importToSecretPath } }, { diff --git a/backend/src/ee/models/auditLog/types.ts b/backend/src/ee/models/auditLog/types.ts index 45a006354..261421798 100644 --- a/backend/src/ee/models/auditLog/types.ts +++ b/backend/src/ee/models/auditLog/types.ts @@ -307,32 +307,42 @@ interface GetSecretImportsEvent { interface CreateSecretImportEvent { type: EventType.CREATE_SECRET_IMPORT, metadata: { - environment: string; secretImportId: string; folderId: string; - importEnvironment: string; - importSecretPath: string; + importFromEnvironment: string; + importFromSecretPath: string; + importToEnvironment: string; + importToSecretPath: string; } } interface UpdateSecretImportEvent { type: EventType.UPDATE_SECRET_IMPORT, metadata: { - environment: string; secretImportId: string; folderId: string; - numberOfImports: number; + importToEnvironment: string; + importToSecretPath: string; + orderBefore: { + environment: string; + secretPath: string; + }[], + orderAfter: { + environment: string; + secretPath: string; + }[] } } interface DeleteSecretImportEvent { type: EventType.DELETE_SECRET_IMPORT, metadata: { - environment: string; secretImportId: string; folderId: string; - importEnvironment: string; - importSecretPath: string; + importFromEnvironment: string; + importFromSecretPath: string; + importToEnvironment: string; + importToSecretPath: string; } } diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index b852b3245..f7197d7bd 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -10,6 +10,8 @@ import Membership, { IMembership } from "./membership"; import MembershipOrg, { IMembershipOrg } from "./membershipOrg"; import Organization, { IOrganization } from "./organization"; import Secret, { ISecret } from "./secret"; +import Folder, { TFolderRootSchema, TFolderSchema } from "./folder"; +import SecretImport, { ISecretImports } from "./secretImports"; import SecretBlindIndexData, { ISecretBlindIndexData } from "./secretBlindIndexData"; import ServiceToken, { IServiceToken } from "./serviceToken"; import ServiceAccount, { IServiceAccount } from "./serviceAccount"; // new @@ -51,6 +53,11 @@ export { IOrganization, Secret, ISecret, + Folder, + TFolderRootSchema, + TFolderSchema, + SecretImport, + ISecretImports, SecretBlindIndexData, ISecretBlindIndexData, ServiceToken, diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index bcfa8c228..9a41c85f0 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -309,32 +309,42 @@ interface GetSecretImportsEvent { interface CreateSecretImportEvent { type: EventType.CREATE_SECRET_IMPORT, metadata: { - environment: string; secretImportId: string; folderId: string; - importEnvironment: string; - importSecretPath: string; + importFromEnvironment: string; + importFromSecretPath: string; + importToEnvironment: string; + importToSecretPath: string; } } interface UpdateSecretImportEvent { type: EventType.UPDATE_SECRET_IMPORT, metadata: { - environment: string; secretImportId: string; folderId: string; - numberOfImports: number; + importToEnvironment: string; + importToSecretPath: string; + orderBefore: { + environment: string; + secretPath: string; + }[], + orderAfter: { + environment: string; + secretPath: string; + }[] } } interface DeleteSecretImportEvent { type: EventType.DELETE_SECRET_IMPORT, metadata: { - environment: string; secretImportId: string; folderId: string; - importEnvironment: string; - importSecretPath: string; + importFromEnvironment: string; + importFromSecretPath: string; + importToEnvironment: string; + importToSecretPath: string; } } diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 1df7517d4..543a83b55 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -483,11 +483,11 @@ export const AppLayout = ({ children }: LayoutProps) => { - + @@ -495,11 +495,11 @@ export const AppLayout = ({ children }: LayoutProps) => { - + diff --git a/frontend/src/pages/project/[id]/audit-logs/index.tsx b/frontend/src/pages/project/[id]/audit-logs/index.tsx index 0c44abbef..c44c3ac18 100644 --- a/frontend/src/pages/project/[id]/audit-logs/index.tsx +++ b/frontend/src/pages/project/[id]/audit-logs/index.tsx @@ -1,197 +1,23 @@ -import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Head from "next/head"; -import { useRouter } from "next/router"; -import Button from "@app/components/basic/buttons/Button"; -import EventFilter from "@app/components/basic/EventFilter"; -import { UpgradePlanModal } from "@app/components/v2"; -import { useSubscription } from "@app/context"; -import ActivitySideBar from "@app/ee/components/ActivitySideBar"; -import { usePopUp } from "@app/hooks/usePopUp"; +import { AuditLogsPage } from "@app/views/Project/AuditLogsPage"; -import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs"; -import ActivityTable from "../../../../ee/components/ActivityTable"; +const Logs = () => { + const { t } = useTranslation(); -interface LogData { - _id: string; - channel: string; - createdAt: string; - ipAddress: string; - user: { - email: string; - }; - serviceAccount?: { - string: string; - }, - serviceTokenData?: { - name: string; - } - actions: { - _id: string; - name: string; - payload: { - secretVersions: string[]; - }; - }[]; -} - -interface PayloadProps { - _id: string; - name: string; - secretVersions: string[]; -} - -interface LogDataPoint { - _id: string; - channel: string; - createdAt: string; - ipAddress: string; - user: string; - serviceAccount: { - name: string; - }; - serviceTokenData: { - name: string; - }; - payload: PayloadProps[]; -} - -/** - * This is the tab that includes all of the user activity logs - */ -export default function Activity() { - const router = useRouter(); - const [eventChosen, setEventChosen] = useState(""); - const [logsData, setLogsData] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [currentOffset, setCurrentOffset] = useState(0); - const currentLimit = 10; - const [currentSidebarAction, toggleSidebar] = useState(); - const { t } = useTranslation(); - const { subscription } = useSubscription(); - const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - "upgradePlan" - ] as const); - - // this use effect updates the data in case of a new filter being added - useEffect(() => { - setCurrentOffset(0); - const getLogData = async () => { - setIsLoading(true); - const tempLogsData = await getProjectLogs({ - workspaceId: String(router.query.id), - offset: 0, - limit: currentLimit, - userId: "", - actionNames: eventChosen - }); - - setLogsData( - tempLogsData.map((log: LogData) => ({ - _id: log._id, - channel: log.channel, - createdAt: log.createdAt, - ipAddress: log.ipAddress, - user: log?.user?.email, - serviceAccount: log?.serviceAccount, - serviceTokenData: log?.serviceTokenData, - payload: log.actions.map((action) => ({ - _id: action._id, - name: action.name, - secretVersions: action.payload.secretVersions - })) - })) - ); - setIsLoading(false); - }; - getLogData(); - }, [eventChosen]); - - // this use effect adds more data in case 'View More' button is clicked - useEffect(() => { - const getLogData = async () => { - setIsLoading(true); - const tempLogsData = await getProjectLogs({ - workspaceId: String(router.query.id), - offset: currentOffset, - limit: currentLimit, - userId: "", - actionNames: eventChosen - }); - setLogsData( - logsData.concat( - tempLogsData.map((log: LogData) => ({ - _id: log._id, - channel: log.channel, - createdAt: log.createdAt, - ipAddress: log.ipAddress, - user: log?.user?.email, - serviceAccount: log?.serviceAccount, - serviceTokenData: log?.serviceTokenData, - payload: log.actions.map((action) => ({ - _id: action._id, - name: action.name, - secretVersions: action.payload.secretVersions - })) - })) - ) - ); - setIsLoading(false); - }; - getLogData(); - }, [currentLimit, currentOffset]); - - const loadMoreLogs = () => { - if (subscription?.auditLogs === false) { - handlePopUpOpen("upgradePlan"); - } else { - setCurrentOffset(currentOffset + currentLimit); - } - }; - - return ( -
- - Audit Logs - - - - {currentSidebarAction && ( - - )} -
-
-

{t("activity.title")}

-
-

{t("activity.subtitle")}

-
-
- -
- -
-
-
-
- {subscription && ( - handlePopUpClose("upgradePlan")} - text={subscription.slug === null ? "You can see more logs under an Enterprise license" : "You can see more logs if you switch to Infisical's Business/Professional Plan."} - /> - )} + return ( +
+ + {t("common.head-title", { title: t("billing.title") })} + + + +
- ); + ); } -Activity.requireAuth = true; +export default Logs; +Logs.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/pages/project/[id]/logs/index.tsx b/frontend/src/pages/project/[id]/logs/index.tsx index ba24ba88f..0c44abbef 100644 --- a/frontend/src/pages/project/[id]/logs/index.tsx +++ b/frontend/src/pages/project/[id]/logs/index.tsx @@ -1,23 +1,197 @@ +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Head from "next/head"; +import { useRouter } from "next/router"; -import { LogsPage } from "@app/views/Project/LogsPage"; +import Button from "@app/components/basic/buttons/Button"; +import EventFilter from "@app/components/basic/EventFilter"; +import { UpgradePlanModal } from "@app/components/v2"; +import { useSubscription } from "@app/context"; +import ActivitySideBar from "@app/ee/components/ActivitySideBar"; +import { usePopUp } from "@app/hooks/usePopUp"; -const Logs = () => { - const { t } = useTranslation(); +import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs"; +import ActivityTable from "../../../../ee/components/ActivityTable"; - return ( -
- - {t("common.head-title", { title: t("billing.title") })} - - - - -
- ); +interface LogData { + _id: string; + channel: string; + createdAt: string; + ipAddress: string; + user: { + email: string; + }; + serviceAccount?: { + string: string; + }, + serviceTokenData?: { + name: string; + } + actions: { + _id: string; + name: string; + payload: { + secretVersions: string[]; + }; + }[]; } -export default Logs; +interface PayloadProps { + _id: string; + name: string; + secretVersions: string[]; +} + +interface LogDataPoint { + _id: string; + channel: string; + createdAt: string; + ipAddress: string; + user: string; + serviceAccount: { + name: string; + }; + serviceTokenData: { + name: string; + }; + payload: PayloadProps[]; +} + +/** + * This is the tab that includes all of the user activity logs + */ +export default function Activity() { + const router = useRouter(); + const [eventChosen, setEventChosen] = useState(""); + const [logsData, setLogsData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [currentOffset, setCurrentOffset] = useState(0); + const currentLimit = 10; + const [currentSidebarAction, toggleSidebar] = useState(); + const { t } = useTranslation(); + const { subscription } = useSubscription(); + const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + "upgradePlan" + ] as const); + + // this use effect updates the data in case of a new filter being added + useEffect(() => { + setCurrentOffset(0); + const getLogData = async () => { + setIsLoading(true); + const tempLogsData = await getProjectLogs({ + workspaceId: String(router.query.id), + offset: 0, + limit: currentLimit, + userId: "", + actionNames: eventChosen + }); + + setLogsData( + tempLogsData.map((log: LogData) => ({ + _id: log._id, + channel: log.channel, + createdAt: log.createdAt, + ipAddress: log.ipAddress, + user: log?.user?.email, + serviceAccount: log?.serviceAccount, + serviceTokenData: log?.serviceTokenData, + payload: log.actions.map((action) => ({ + _id: action._id, + name: action.name, + secretVersions: action.payload.secretVersions + })) + })) + ); + setIsLoading(false); + }; + getLogData(); + }, [eventChosen]); + + // this use effect adds more data in case 'View More' button is clicked + useEffect(() => { + const getLogData = async () => { + setIsLoading(true); + const tempLogsData = await getProjectLogs({ + workspaceId: String(router.query.id), + offset: currentOffset, + limit: currentLimit, + userId: "", + actionNames: eventChosen + }); + setLogsData( + logsData.concat( + tempLogsData.map((log: LogData) => ({ + _id: log._id, + channel: log.channel, + createdAt: log.createdAt, + ipAddress: log.ipAddress, + user: log?.user?.email, + serviceAccount: log?.serviceAccount, + serviceTokenData: log?.serviceTokenData, + payload: log.actions.map((action) => ({ + _id: action._id, + name: action.name, + secretVersions: action.payload.secretVersions + })) + })) + ) + ); + setIsLoading(false); + }; + getLogData(); + }, [currentLimit, currentOffset]); + + const loadMoreLogs = () => { + if (subscription?.auditLogs === false) { + handlePopUpOpen("upgradePlan"); + } else { + setCurrentOffset(currentOffset + currentLimit); + } + }; + + return ( +
+ + Audit Logs + + + + {currentSidebarAction && ( + + )} +
+
+

{t("activity.title")}

+
+

{t("activity.subtitle")}

+
+
+ +
+ +
+
+
+
+ {subscription && ( + handlePopUpClose("upgradePlan")} + text={subscription.slug === null ? "You can see more logs under an Enterprise license" : "You can see more logs if you switch to Infisical's Business/Professional Plan."} + /> + )} +
+ ); +} + +Activity.requireAuth = true; -Logs.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx b/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx index ab5c8719f..182eea8f4 100644 --- a/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx +++ b/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx @@ -27,7 +27,7 @@ type Props = { onEnvCompare: (secretKey: string) => void; secretVersion?: Array<{ id: string; createdAt: string; value: string }>; // to record the ids of deleted ones - onSecretDelete: (index: number, id?: string, overrideId?: string) => void; + onSecretDelete: (index: number, secretName: string, id?: string, overrideId?: string) => void; onSave: () => void; }; @@ -45,6 +45,15 @@ export const SecretDetailDrawer = ({ const [canRevealSecOverride, setCanRevealSecOverride] = useToggle(); const { register, setValue, control, getValues } = useFormContext(); + + const secKey = useWatch({ + control, + name: `secrets.${index}.key`, + disabled: false, + exact: true + }); + + console.log("secKeyyy", secKey); const overrideAction = useWatch({ control, name: `secrets.${index}.overrideAction` }); const isOverridden = diff --git a/frontend/src/views/Project/LogsPage/LogsPage.tsx b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx similarity index 90% rename from frontend/src/views/Project/LogsPage/LogsPage.tsx rename to frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx index c17fd7092..0a192110b 100644 --- a/frontend/src/views/Project/LogsPage/LogsPage.tsx +++ b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx @@ -2,7 +2,7 @@ import { LogsSection } from "./components"; -export const LogsPage = () => { +export const AuditLogsPage = () => { return (
diff --git a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx similarity index 97% rename from frontend/src/views/Project/LogsPage/components/LogsFilter.tsx rename to frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx index 88523e012..9b2be6f4c 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx @@ -73,7 +73,8 @@ export const LogsFilter = ({ className="w-40 mr-4" >