mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Connected version history to backend
This commit is contained in:
@ -149,6 +149,7 @@ const SideBar = ({
|
||||
isFull={true}
|
||||
/>
|
||||
</div> */}
|
||||
<SecretVersionList secretId={data[0].id} />
|
||||
<CommentField comment={data.filter(secret => secret.type == "shared")[0]?.comment} modifyComment={modifyComment} position={data[0].pos} />
|
||||
</div>
|
||||
<div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}>
|
||||
|
@ -15,6 +15,7 @@ interface SecretProps {
|
||||
value: string;
|
||||
type: 'personal' | 'shared';
|
||||
comment: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@ -85,6 +86,7 @@ const getSecretsForProject = async ({
|
||||
}
|
||||
|
||||
tempFileState.push({
|
||||
id: secretPair._id,
|
||||
key: plainTextKey,
|
||||
value: plainTextValue,
|
||||
type: secretPair.type,
|
||||
@ -97,7 +99,7 @@ const getSecretsForProject = async ({
|
||||
setData(
|
||||
tempFileState.map((line, index) => {
|
||||
return {
|
||||
id: guidGenerator(),
|
||||
id: line['id'],
|
||||
pos: index,
|
||||
key: line['key'],
|
||||
value: line['value'],
|
||||
@ -109,7 +111,7 @@ const getSecretsForProject = async ({
|
||||
|
||||
return tempFileState.map((line, index) => {
|
||||
return {
|
||||
id: guidGenerator(),
|
||||
id: line['id'],
|
||||
pos: index,
|
||||
key: line['key'],
|
||||
value: line['value'],
|
||||
|
40
frontend/ee/api/secrets/GetSecretVersions.ts
Normal file
40
frontend/ee/api/secrets/GetSecretVersions.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
|
||||
|
||||
interface secretVersionProps {
|
||||
secretId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fetches the versions of a specific secret
|
||||
* @param {object} obj
|
||||
* @param {string} obj.secretId - the id of a secret for which we are fetching the version history
|
||||
* @param {number} obj.offset - the start of our query
|
||||
* @param {number} obj.limit - how far our query goes
|
||||
* @returns
|
||||
*/
|
||||
const getSecretVersions = async ({ secretId, offset, limit }: secretVersionProps) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/secret/' + secretId + '/secret-versions?'+
|
||||
new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit)
|
||||
}),
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
return await res.json();
|
||||
} else {
|
||||
console.log('Failed to get project secrets');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getSecretVersions;
|
@ -1,38 +1,95 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCircle, faDotCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import getSecretVersions from 'ee/api/secrets/GetSecretVersions';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface SecretVersionListProps {}
|
||||
import { decryptAssymmetric, decryptSymmetric } from '~/components/utilities/cryptography/crypto';
|
||||
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
|
||||
|
||||
interface DecryptedSecretVersionListProps {
|
||||
createdAt: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface EncrypetedSecretVersionListProps {
|
||||
createdAt: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
}
|
||||
|
||||
const versionData = [{
|
||||
value: "Value1",
|
||||
date: "Date1",
|
||||
user: "vlad@infisical.com"
|
||||
}, {
|
||||
value: "Value2",
|
||||
date: "Date2",
|
||||
user: "tony@infisical.com"
|
||||
}]
|
||||
|
||||
/**
|
||||
* @returns a list of the versions for a specific secret
|
||||
* @returns a list of versions for a specific secret
|
||||
*/
|
||||
const SecretVersionList = () => {
|
||||
const SecretVersionList = ({ secretId }: { secretId: string; }) => {
|
||||
const router = useRouter();
|
||||
const [secretVersions, setSecretVersions] = useState<DecryptedSecretVersionListProps[]>([{createdAt: "123", value: "124"}]);
|
||||
|
||||
useEffect(() => {
|
||||
const getSecretVersionHistory = async () => {
|
||||
try {
|
||||
const encryptedSecretVersions = await getSecretVersions({ secretId, offset: 0, limit: 10});
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) })
|
||||
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
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 = encryptedSecretVersions.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => {
|
||||
return {
|
||||
createdAt: encryptedSecretVersion.createdAt,
|
||||
value: decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.secretValueCiphertext,
|
||||
iv: encryptedSecretVersion.secretValueIV,
|
||||
tag: encryptedSecretVersion.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setSecretVersions(decryptedSecretVersions);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
};
|
||||
getSecretVersionHistory();
|
||||
}, []);
|
||||
|
||||
return <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'>
|
||||
<p className=''>Version History</p>
|
||||
<div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none'>
|
||||
<div className='h-48 overflow-y-scroll overflow-x-none'>
|
||||
{versionData.map((version, index) =>
|
||||
<div className='h-48 overflow-y-auto overflow-x-none'>
|
||||
{secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
.map((version: DecryptedSecretVersionListProps, index: number) =>
|
||||
<div key={index} className='flex flex-row'>
|
||||
<div className='pr-1 flex flex-col items-center'>
|
||||
<div className='p-1'><FontAwesomeIcon icon={index == 0 ? faDotCircle : faCircle} /></div>
|
||||
<div className='w-0 h-full border-l mt-1'></div>
|
||||
</div>
|
||||
<div className='flex flex-col w-full max-w-[calc(100%-2.3rem)]'>
|
||||
<div className='pr-2 pt-1'>{version.date}</div>
|
||||
<div className='pr-2 pt-1'>
|
||||
{(new Date(version.createdAt)).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
<div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5'>Value:</span>{version.value}</p></div>
|
||||
<div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5'>Updated by:</span>{version.user}</p></div>
|
||||
{/* <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5'>Updated by:</span>{version.user}</p></div> */}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
305
frontend/public/images/invitation-expired.svg
Normal file
305
frontend/public/images/invitation-expired.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 124 KiB |
Reference in New Issue
Block a user