Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into activity-logs
  • Loading branch information
dangtony98 committed Jan 3, 2023
2 parents 1c2a43c + ae5320e commit 1428679
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 99 deletions.
6 changes: 4 additions & 2 deletions backend/src/ee/controllers/v1/workspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
let logs
try {
const { workspaceId } = req.params;
const { userId, actionNames } = req.query;

const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
const userId: string = req.query.userId as string;
const actionNames: string = req.query.actionNames as string;

logs = await Log.find({
workspace: workspaceId,
Expand All @@ -53,11 +54,12 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
actionNames
? {
actionNames: {
$in: actionNames
$in: actionNames.split(',')
}
} : {}
)
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit)
.populate('actions')
Expand Down
10 changes: 7 additions & 3 deletions frontend/components/basic/EventFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
faEye,
faPlus,
faShuffle,
faTrash,
faX
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand All @@ -28,6 +29,10 @@ const eventOptions = [
{
name: 'updateSecrets',
icon: faShuffle
},
{
name: 'deleteSecrets',
icon: faTrash
}
];

Expand All @@ -48,7 +53,7 @@ export default function EventFilter({
<div className="relative">
<Listbox.Button className="bg-mineshaft-800 hover:bg-mineshaft-700 duration-200 cursor-pointer rounded-md h-10 flex items-center justify-between pl-4 pr-2 w-52 text-bunker-200 text-sm">
{selected != '' ? (
<p className="select-none text-bunker-100">{selected}</p>
<p className="select-none text-bunker-100">{t("activity:event." + selected)}</p>
) : (
<p className="select-none">Select an event</p>
)}
Expand Down Expand Up @@ -76,7 +81,7 @@ export default function EventFilter({
className={`px-4 h-10 flex items-center text-sm cursor-pointer hover:bg-mineshaft-700 text-bunker-200 rounded-md ${
selected == t("activity:event." + event.name) && 'bg-mineshaft-700'
}`}
value={t("activity:event." + event.name)}
value={event.name}
>
{({ selected }) => (
<>
Expand All @@ -90,7 +95,6 @@ export default function EventFilter({
</span>
</>
)}
{/* <FontAwesomeIcon icon={event.icon} className="pr-4" /> {event.name} */}
</Listbox.Option>
);
})}
Expand Down
32 changes: 32 additions & 0 deletions frontend/ee/api/secrets/GetActionData.ts
Original file line number Diff line number Diff line change
@@ -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;
41 changes: 33 additions & 8 deletions frontend/ee/api/secrets/GetProjectLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ interface workspaceProps {
workspaceId: string;
offset: number;
limit: number;
filters: object;
userId: string;
actionNames: string;
}

/**
Expand All @@ -14,17 +15,41 @@ interface workspaceProps {
* @param {string} obj.workspaceId - workspace id for which we are trying to get project log
* @param {object} obj.offset - teh starting point of logs that we want to pull
* @param {object} obj.limit - how many logs will we output
* @param {object} obj.filters
* @param {object} obj.userId - optional userId filter - will only query logs for that user
* @param {string} obj.actionNames - optional actionNames filter - will only query logs for those actions
* @returns
*/
const getProjectLogs = async ({ workspaceId, offset, limit, filters }: workspaceProps) => {
const getProjectLogs = async ({ workspaceId, offset, limit, userId, actionNames }: workspaceProps) => {
let payload;
if (userId != "" && actionNames != '') {
payload = {
offset: String(offset),
limit: String(limit),
userId: JSON.stringify(userId),
actionNames: actionNames
}
} else if (userId != "") {
payload = {
offset: String(offset),
limit: String(limit),
userId: JSON.stringify(userId)
}
} else if (actionNames != "") {
payload = {
offset: String(offset),
limit: String(limit),
actionNames: actionNames
}
} else {
payload = {
offset: String(offset),
limit: String(limit)
}
}

return SecurityClient.fetchCall(
'/api/v1/workspace/' + workspaceId + '/logs?' +
new URLSearchParams({
offset: String(offset),
limit: String(limit),
filters: JSON.stringify(filters)
}),
new URLSearchParams(payload),
{
method: 'GET',
headers: {
Expand Down
205 changes: 155 additions & 50 deletions frontend/ee/components/ActivitySideBar.tsx
Original file line number Diff line number Diff line change
@@ -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<DecryptedSecretProps[]>();
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
const [isLoading, setIsLoading] = useState(false);

return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'>
<div className='h-min overflow-y-auto'>
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
<p className="font-semibold text-lg text-bunker-200">{t("activity:event." + currentEvent)}</p>
<div className='p-1' onClick={() => toggleSidebar([])}>
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
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 <div className={`absolute border-l border-mineshaft-500 ${isLoading ? "bg-bunker-800" : "bg-bunker"} fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between`}>
{isLoading ? (
<div className="flex items-center justify-center h-full mb-8">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
></Image>
</div>
) : (
<div className='h-min overflow-y-auto'>
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
<p className="font-semibold text-lg text-bunker-200">{t("activity:event." + actionMetaData?.name)}</p>
<div className='p-1' onClick={() => toggleSidebar("")}>
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
</div>
</div>
</div>
<div className='flex flex-col px-4'>
{currentEvent == 'readSecrets' && sidebarData.map((item, id) =>
<>
<div className='text-sm text-bunker-200 mt-4 pl-1'>Key {id}</div>
<DashboardInputField
key={id}
onChangeHandler={() => {}}
type="varName"
position={1}
value={"a" + item}
isDuplicate={false}
blurred={false}
/>
</>
)}
{currentEvent == 'updateSecrets' && sidebarData.map((item, id) =>
secretChanges.map(secretChange =>
<>
<div className='text-sm text-bunker-200 mt-4 pl-1'>Secret Name {id}</div>
<div className='text-bunker-100 font-mono rounded-md overflow-hidden'>
<div className='bg-red/30 px-2'>- {patienceDiff(secretChange.oldSecret.split(''), secretChange.newSecret.split(''), false).lines.map((character, id) => character.aIndex != -1 && <span key={id} className={`${character.bIndex == -1 && "bg-red-700/80"}`}>{character.line}</span>)}</div>
<div className='bg-green-500/30 px-2'>+ {patienceDiff(secretChange.oldSecret.split(''), secretChange.newSecret.split('')).lines.map((character, id) => character.bIndex != -1 && <span key={id} className={`${character.aIndex == -1 && "bg-green-700/80"}`}>{character.line}</span>)}</div>
<div className='flex flex-col px-4'>
{(actionMetaData?.name == 'readSecrets'
|| actionMetaData?.name == 'addSecrets'
|| actionMetaData?.name == 'deleteSecrets') && actionData?.map((item, id) =>
<div key={id}>
<div className='text-xs text-bunker-200 mt-4 pl-1'>{item.newSecretVersion.key}</div>
<DashboardInputField
key={id}
onChangeHandler={() => {}}
type="value"
position={1}
value={item.newSecretVersion.value}
isDuplicate={false}
blurred={false}
/>
</div>
</>
))}
)}
{actionMetaData?.name == 'updateSecrets' && actionData?.map((item, id) =>
<>
<div className='text-xs text-bunker-200 mt-4 pl-1'>{item.newSecretVersion.key}</div>
<div className='text-bunker-100 font-mono rounded-md overflow-hidden'>
<div className='bg-red/30 px-2'>- {patienceDiff(item.oldSecretVersion.value.split(''), item.newSecretVersion.value.split(''), false).lines.map((character, id) => character.bIndex != -1 && <span key={id} className={`${character.aIndex == -1 && "bg-red-700/80"}`}>{character.line}</span>)}</div>
<div className='bg-green-500/30 px-2'>+ {patienceDiff(item.oldSecretVersion.value.split(''), item.newSecretVersion.value.split(''), false).lines.map((character, id) => character.aIndex != -1 && <span key={id} className={`${character.bIndex == -1 && "bg-green-700/80"}`}>{character.line}</span>)}</div>
</div>
</>
)}
</div>
</div>
</div>

)}
</div>
};

Expand Down
Loading

0 comments on commit 1428679

Please sign in to comment.