diff --git a/frontend/components/basic/EventFilter.tsx b/frontend/components/basic/EventFilter.tsx index fc0c4e1e1..c9b31fd43 100644 --- a/frontend/components/basic/EventFilter.tsx +++ b/frontend/components/basic/EventFilter.tsx @@ -6,6 +6,7 @@ import { faEye, faPlus, faShuffle, + faTrash, faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -28,6 +29,10 @@ const eventOptions = [ { name: 'updateSecrets', icon: faShuffle + }, + { + name: 'deleteSecrets', + icon: faTrash } ]; diff --git a/frontend/ee/api/secrets/GetActionData.ts b/frontend/ee/api/secrets/GetActionData.ts new file mode 100644 index 000000000..122870d69 --- /dev/null +++ b/frontend/ee/api/secrets/GetActionData.ts @@ -0,0 +1,32 @@ +import SecurityClient from '~/utilities/SecurityClient'; + + +interface workspaceProps { + actionId: string; +} + +/** + * This function fetches the data for a certain action performed by a user + * @param {object} obj + * @param {string} obj.actionId - id of an action for which we are trying to get data + * @returns + */ +const getActionData = async ({ actionId }: workspaceProps) => { + return SecurityClient.fetchCall( + '/api/v1/action/' + actionId, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + ).then(async (res) => { + console.log(188, res) + if (res && res.status == 200) { + return (await res.json()).action; + } else { + console.log('Failed to get the info about an action'); + } + }); +}; + +export default getActionData; diff --git a/frontend/ee/components/ActivitySideBar.tsx b/frontend/ee/components/ActivitySideBar.tsx index d96a3f6ab..e0a634d45 100644 --- a/frontend/ee/components/ActivitySideBar.tsx +++ b/frontend/ee/components/ActivitySideBar.tsx @@ -1,79 +1,184 @@ +import { useEffect, useState } from "react"; +import Image from "next/image"; +import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; import { faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import getActionData from "ee/api/secrets/GetActionData"; import patienceDiff from 'ee/utilities/findTextDifferences'; +import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; + import DashboardInputField from '../../components/dashboard/DashboardInputField'; -const secretChanges = [{ - "oldSecret": "secret1", - "newSecret": "ecret2" -}, { - "oldSecret": "secret1", - "newSecret": "sercet2" -}, { - "oldSecret": "localhosta:8080", - "newSecret": "aaaalocalhoats:3000" -}] +const { + decryptAssymmetric, + decryptSymmetric +} = require('../../components/utilities/cryptography/crypto'); +const nacl = require('tweetnacl'); +nacl.util = require('tweetnacl-util'); interface SideBarProps { - toggleSidebar: (value: string[]) => void; - sidebarData: string[]; - currentEvent: string; + toggleSidebar: (value: string) => void; + currentAction: string; +} + +interface SecretProps { + secret: string; + secretKeyCiphertext: string; + secretKeyHash: string; + secretKeyIV: string; + secretKeyTag: string; + secretValueCiphertext: string; + secretValueHash: string; + secretValueIV: string; + secretValueTag: string; +} + +interface DecryptedSecretProps { + newSecretVersion: { + key: string; + value: string; + } + oldSecretVersion: { + key: string; + value: string; + } +} + +interface ActionProps { + name: string; } /** * @param {object} obj * @param {function} obj.toggleSidebar - function that opens or closes the sidebar - * @param {string[]} obj.secretIds - data of payload - * @param {string} obj.currentEvent - the event name for which a sidebar is being displayed + * @param {string} obj.currentAction - the action id for which a sidebar is being displayed * @returns the sidebar with the payload of user activity logs */ const ActivitySideBar = ({ toggleSidebar, - sidebarData, - currentEvent + currentAction }: SideBarProps) => { const { t } = useTranslation(); + const router = useRouter(); + const [actionData, setActionData] = useState(); + const [actionMetaData, setActionMetaData] = useState(); + const [isLoading, setIsLoading] = useState(false); - return
-
-
-

{t("activity:event." + currentEvent)}

-
toggleSidebar([])}> - + useEffect(() => { + const getLogData = async () => { + setIsLoading(true); + const tempActionData = await getActionData({ actionId: currentAction }); + const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) }) + const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY'); + + // #TODO: make this a separate function and reuse across the app + let decryptedLatestKey: string; + if (latestKey) { + // assymmetrically decrypt symmetric key with local private key + decryptedLatestKey = decryptAssymmetric({ + ciphertext: latestKey.latestKey.encryptedKey, + nonce: latestKey.latestKey.nonce, + publicKey: latestKey.latestKey.sender.publicKey, + privateKey: String(PRIVATE_KEY) + }); + } + + const decryptedSecretVersions = tempActionData.payload.secretVersions.map((encryptedSecretVersion: { + newSecretVersion?: SecretProps; + oldSecretVersion?: SecretProps; + }) => { + return { + newSecretVersion: { + key: decryptSymmetric({ + ciphertext: encryptedSecretVersion.newSecretVersion!.secretKeyCiphertext, + iv: encryptedSecretVersion.newSecretVersion!.secretKeyIV, + tag: encryptedSecretVersion.newSecretVersion!.secretKeyTag, + key: decryptedLatestKey + }), + value: decryptSymmetric({ + ciphertext: encryptedSecretVersion.newSecretVersion!.secretValueCiphertext, + iv: encryptedSecretVersion.newSecretVersion!.secretValueIV, + tag: encryptedSecretVersion.newSecretVersion!.secretValueTag, + key: decryptedLatestKey + }) + }, + oldSecretVersion: { + key: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext + ? decryptSymmetric({ + ciphertext: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext, + iv: encryptedSecretVersion.oldSecretVersion?.secretKeyIV, + tag: encryptedSecretVersion.oldSecretVersion?.secretKeyTag, + key: decryptedLatestKey + }): undefined, + value: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext + ? decryptSymmetric({ + ciphertext: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext, + iv: encryptedSecretVersion.oldSecretVersion?.secretValueIV, + tag: encryptedSecretVersion.oldSecretVersion?.secretValueTag, + key: decryptedLatestKey + }): undefined + } + } + }) + + setActionData(decryptedSecretVersions); + setActionMetaData({name: tempActionData.name}); + setIsLoading(false); + } + getLogData(); + }, [currentAction]); + + return
+ {isLoading ? ( +
+ infisical loading indicator +
+ ) : ( +
+
+

{t("activity:event." + actionMetaData?.name)}

+
toggleSidebar("")}> + +
+
+
+ {(actionMetaData?.name == 'readSecrets' + || actionMetaData?.name == 'addSecrets' + || actionMetaData?.name == 'deleteSecrets') && actionData?.map((item, id) => +
+
{item.newSecretVersion.key}
+ {}} + type="value" + position={1} + value={item.newSecretVersion.value} + isDuplicate={false} + blurred={false} + /> +
+ )} + {actionMetaData?.name == 'updateSecrets' && actionData?.map((item, id) => + <> +
{item.newSecretVersion.key}
+
+
- {patienceDiff(item.oldSecretVersion.value.split(''), item.newSecretVersion.value.split(''), false).lines.map((character, id) => character.bIndex != -1 && {character.line})}
+
+ {patienceDiff(item.oldSecretVersion.value.split(''), item.newSecretVersion.value.split(''), false).lines.map((character, id) => character.aIndex != -1 && {character.line})}
+
+ + )}
-
- {currentEvent == 'readSecrets' && sidebarData.map((item, id) => - <> -
Key {id}
- {}} - type="varName" - position={1} - value={"a" + item} - isDuplicate={false} - blurred={false} - /> - - )} - {currentEvent == 'updateSecrets' && sidebarData.map((item, id) => - secretChanges.map(secretChange => - <> -
Secret Name {id}
-
-
- {patienceDiff(secretChange.oldSecret.split(''), secretChange.newSecret.split(''), false).lines.map((character, id) => character.aIndex != -1 && {character.line})}
-
+ {patienceDiff(secretChange.oldSecret.split(''), secretChange.newSecret.split('')).lines.map((character, id) => character.bIndex != -1 && {character.line})}
-
- - ))} -
-
- + )}
}; diff --git a/frontend/ee/components/ActivityTable.tsx b/frontend/ee/components/ActivityTable.tsx index a25920202..607b25c5d 100644 --- a/frontend/ee/components/ActivityTable.tsx +++ b/frontend/ee/components/ActivityTable.tsx @@ -4,8 +4,7 @@ import { useTranslation } from "next-i18next"; import { faAngleDown, faAngleRight, - faUpRightFromSquare, - faX + faUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import timeSince from 'ee/utilities/timeSince'; @@ -14,6 +13,7 @@ import guidGenerator from '../../components/utilities/randomId'; interface PayloadProps { + _id: string; name: string; secretVersions: string[]; } @@ -29,25 +29,26 @@ interface logData { /** - * + * This is a single row of the activity table * @param obj - * @param {function} obj.setCurrentEvent - specify the name of the event for which the sidebar is being opened + * @param {logData} obj.row - data for a certain event + * @param {function} obj.toggleSidebar - open and close sidebar that displays data for a specific event * @returns */ -const ActivityLogsRow = ({ row, toggleSidebar, setCurrentEvent }: { row: logData, toggleSidebar: (value: string[]) => void; setCurrentEvent: (value: string) => void; }) => { +const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar: (value: string) => void; }) => { const [payloadOpened, setPayloadOpened] = useState(false); const { t } = useTranslation(); return ( <> - + setPayloadOpened(!payloadOpened)} className="border-mineshaft-700 border-t text-gray-300 flex items-center cursor-pointer" > @@ -66,26 +67,23 @@ const ActivityLogsRow = ({ row, toggleSidebar, setCurrentEvent }: { row: logData {payloadOpened && - + Timestamp {row.createdAt} } {payloadOpened && row.payload?.map((action, index) => - + {t("activity:event." + action.name)} - { - toggleSidebar(action.secretVersions); - setCurrentEvent(action.name); - }}> + toggleSidebar(action._id)}> {action.secretVersions.length + (action.secretVersions.length != 1 ? " secrets" : " secret")} )} {payloadOpened && - + IP Address {row.ipAddress} @@ -99,28 +97,27 @@ const ActivityLogsRow = ({ row, toggleSidebar, setCurrentEvent }: { row: logData * @param {object} obj * @param {logData} obj.data - data for user activity logs * @param {function} obj.toggleSidebar - function that opens or closes the sidebar - * @param {function} obj.setCurrentEvent - specify the name of the event for which the sidebar is being opened * @returns */ -const ActivityTable = ({ data, toggleSidebar, setCurrentEvent }: { data: logData[], toggleSidebar: (value: string[]) => void; setCurrentEvent: (value: string) => void; }) => { +const ActivityTable = ({ data, toggleSidebar }: { data: logData[], toggleSidebar: (value: string) => void; }) => { return (
-
+
- + - - - - + + + + {data?.map((row, index) => { - return ; + return ; })}
EventUserSourceTimeEVENTUSERSOURCETIME
diff --git a/frontend/pages/activity/[id].tsx b/frontend/pages/activity/[id].tsx index ed5a6def0..974607d0a 100644 --- a/frontend/pages/activity/[id].tsx +++ b/frontend/pages/activity/[id].tsx @@ -21,6 +21,7 @@ interface logData { email: string; }; actions: { + _id: string; name: string; payload: { secretVersions: string[]; @@ -29,6 +30,7 @@ interface logData { } interface PayloadProps { + _id: string; name: string; secretVersions: string[]; } @@ -51,8 +53,7 @@ export default function Activity() { const [logsData, setLogsData] = useState([]); const [currentOffset, setCurrentOffset] = useState(0); const currentLimit = 10; - const [sidebarData, toggleSidebar] = useState([]) - const [currentEvent, setCurrentEvent] = useState(""); + const [currentSidebarAction, toggleSidebar] = useState() const { t } = useTranslation(); // this use effect updates the data in case of a new filter being added @@ -69,6 +70,7 @@ export default function Activity() { user: log.user.email, payload: log.actions.map(action => { return { + _id: action._id, name: action.name, secretVersions: action.payload.secretVersions } @@ -92,6 +94,7 @@ export default function Activity() { user: log.user.email, payload: log.actions.map(action => { return { + _id: action._id, name: action.name, secretVersions: action.payload.secretVersions } @@ -109,13 +112,13 @@ export default function Activity() { return (
- {sidebarData.length > 0 && } + {currentSidebarAction && }

Activity Logs

- Event history limited to the last 12 months. + Event history for this Infisical project.

@@ -127,7 +130,6 @@ export default function Activity() {