mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
Changed frontend to use the new secrets routes
This commit is contained in:
@@ -273,7 +273,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
await Secret.bulkWrite(ops);
|
await Secret.bulkWrite(ops);
|
||||||
|
|
||||||
let newSecretsObj: { [key: string]: PatchSecret } = {};
|
const newSecretsObj: { [key: string]: PatchSecret } = {};
|
||||||
req.body.secrets.forEach((secret: PatchSecret) => {
|
req.body.secrets.forEach((secret: PatchSecret) => {
|
||||||
newSecretsObj[secret.id] = secret;
|
newSecretsObj[secret.id] = secret;
|
||||||
});
|
});
|
||||||
@@ -323,7 +323,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// group secrets into workspaces so updated secrets can
|
// group secrets into workspaces so updated secrets can
|
||||||
// be logged and snapshotted separately for each workspace
|
// be logged and snapshotted separately for each workspace
|
||||||
let workspaceSecretObj: any = {};
|
const workspaceSecretObj: any = {};
|
||||||
req.secrets.forEach((s: any) => {
|
req.secrets.forEach((s: any) => {
|
||||||
if (s.workspace.toString() in workspaceSecretObj) {
|
if (s.workspace.toString() in workspaceSecretObj) {
|
||||||
workspaceSecretObj[s.workspace.toString()].push(s);
|
workspaceSecretObj[s.workspace.toString()].push(s);
|
||||||
@@ -399,7 +399,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// group secrets into workspaces so deleted secrets can
|
// group secrets into workspaces so deleted secrets can
|
||||||
// be logged and snapshotted separately for each workspace
|
// be logged and snapshotted separately for each workspace
|
||||||
let workspaceSecretObj: any = {};
|
const workspaceSecretObj: any = {};
|
||||||
req.secrets.forEach((s: any) => {
|
req.secrets.forEach((s: any) => {
|
||||||
if (s.workspace.toString() in workspaceSecretObj) {
|
if (s.workspace.toString() in workspaceSecretObj) {
|
||||||
workspaceSecretObj[s.workspace.toString()].push(s);
|
workspaceSecretObj[s.workspace.toString()].push(s);
|
||||||
@@ -445,7 +445,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(400).send({
|
return res.status(200).send({
|
||||||
secrets: req.secrets
|
secrets: req.secrets
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/',
|
'/',
|
||||||
[
|
[
|
||||||
check('secretIds')
|
body('secretIds')
|
||||||
.exists()
|
.exists()
|
||||||
.custom((value) => {
|
.custom((value) => {
|
||||||
// case: delete 1 secret
|
// case: delete 1 secret
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import {
|
|||||||
} from '../config';
|
} from '../config';
|
||||||
import { getLogger } from '../utils/logger';
|
import { getLogger } from '../utils/logger';
|
||||||
|
|
||||||
if(TELEMETRY_ENABLED){
|
if(!TELEMETRY_ENABLED){
|
||||||
getLogger("backend-main").info([
|
getLogger("backend-main").info([
|
||||||
"",
|
"",
|
||||||
"Infisical collects telemetry data about general usage.",
|
"To improve, Infisical collects telemetry data about general usage.",
|
||||||
"The data helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth for investors as we support Infisical as open-source software.",
|
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
||||||
"To opt out of telemetry, you can set `TELEMETRY_ENABLED=false` within the environment variables",
|
"To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.",
|
||||||
].join('\n'))
|
].join('\n'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ interface ToggleProps {
|
|||||||
* @param {string} obj.value - value of a certain secret
|
* @param {string} obj.value - value of a certain secret
|
||||||
* @param {number} obj.pos - position of a certain secret
|
* @param {number} obj.pos - position of a certain secret
|
||||||
#TODO: make the secret id persistent?
|
#TODO: make the secret id persistent?
|
||||||
* @param {string} obj.id - id of a certain secret
|
* @param {string} obj.id - id of a certain secret (NOTE: THIS IS THE ID OF THE MAIN SECRET - NOT OF AN OVERRIDE)
|
||||||
* @param {function} obj.deleteOverride - a function that deleted an override for a certain secret
|
* @param {function} obj.deleteOverride - a function that deleted an override for a certain secret
|
||||||
* @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden.
|
* @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden.
|
||||||
* @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually
|
* @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import { useTranslation } from "next-i18next";
|
|||||||
const CommentField = ({ comment, modifyComment, position }: { comment: string; modifyComment: (value: string, posistion: number) => void; position: number;}) => {
|
const CommentField = ({ comment, modifyComment, position }: { comment: string; modifyComment: (value: string, posistion: number) => void; position: number;}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return <div className={`relative mt-4 px-4 pt-4`}>
|
return <div className={`relative mt-4 px-4 pt-6`}>
|
||||||
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.comments")}</p>
|
<p className='text-sm text-bunker-300 pl-0.5'>{t("dashboard:sidebar.comments")}</p>
|
||||||
<textarea
|
<textarea
|
||||||
className="bg-bunker-800 h-32 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
className="bg-bunker-800 placeholder:text-bunker-400 h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => modifyComment(e.target.value, position)}
|
onChange={(e) => modifyComment(e.target.value, position)}
|
||||||
placeholder="Leave any comments here..."
|
placeholder="Leave any comments here..."
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Fragment } from 'react';
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { Menu, Transition } from '@headlessui/react';
|
||||||
|
|
||||||
|
import Button from '../basic/buttons/Button';
|
||||||
|
import downloadDotEnv from '../utilities/secrets/downloadDotEnv';
|
||||||
|
import downloadYaml from '../utilities/secrets/downloadYaml';
|
||||||
|
|
||||||
|
|
||||||
|
interface SecretDataProps {
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
pos: number;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the menu that is used to download secrets as .env ad .yml files (in future we may have more options)
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {SecretDataProps[]} obj.data - secrets that we want to downlaod
|
||||||
|
* @param {string} obj.env - the environment which we're downloading (used for naming the file)
|
||||||
|
*/
|
||||||
|
const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: string; }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <Menu
|
||||||
|
as="div"
|
||||||
|
className="relative inline-block text-left"
|
||||||
|
>
|
||||||
|
<Menu.Button
|
||||||
|
as="div"
|
||||||
|
className="inline-flex w-full justify-center text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="mineshaft"
|
||||||
|
size="icon-md"
|
||||||
|
icon={faDownload}
|
||||||
|
onButtonPressed={() => {}}
|
||||||
|
/>
|
||||||
|
</Menu.Button>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items className="absolute z-50 drop-shadow-xl right-0 mt-0.5 w-[12rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none p-2 space-y-2">
|
||||||
|
<Menu.Item>
|
||||||
|
<Button
|
||||||
|
color="mineshaft"
|
||||||
|
onButtonPressed={() => downloadDotEnv({ data, env })}
|
||||||
|
size="md"
|
||||||
|
text="Download as .env"
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
<Button
|
||||||
|
color="mineshaft"
|
||||||
|
onButtonPressed={() => downloadYaml({ data, env })}
|
||||||
|
size="md"
|
||||||
|
text="Download as .yml"
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DownloadSecretMenu;
|
||||||
@@ -158,7 +158,7 @@ const SideBar = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SecretVersionList secretId={data[0]?.id} />
|
<SecretVersionList secretId={data[0]?.id} />
|
||||||
<CommentField comment={data.filter(secret => secret.type == "shared")[0]?.comment} modifyComment={modifyComment} position={data[0]?.pos} />
|
<CommentField comment={data.filter(secret => secret.type == "shared")[0]?.comment} modifyComment={modifyComment} position={data.filter(secret => secret.type == "shared")[0]?.pos} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}>
|
<div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}>
|
||||||
|
|||||||
+27
-34
@@ -1,35 +1,46 @@
|
|||||||
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
|
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
|
||||||
import login1 from '~/pages/api/auth/Login1';
|
import login1 from '~/pages/api/auth/Login1';
|
||||||
import login2 from '~/pages/api/auth/Login2';
|
import login2 from '~/pages/api/auth/Login2';
|
||||||
|
import addSecrets from '~/pages/api/files/AddSecrets';
|
||||||
import getOrganizations from '~/pages/api/organization/getOrgs';
|
import getOrganizations from '~/pages/api/organization/getOrgs';
|
||||||
import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
|
import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
|
||||||
|
|
||||||
import pushKeys from './secrets/pushKeys';
|
import encryptSecrets from './secrets/encryptSecrets';
|
||||||
import Telemetry from './telemetry/Telemetry';
|
import Telemetry from './telemetry/Telemetry';
|
||||||
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
||||||
import SecurityClient from './SecurityClient';
|
import SecurityClient from './SecurityClient';
|
||||||
|
|
||||||
|
interface SecretDataProps {
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
pos: number;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
const nacl = require('tweetnacl');
|
const nacl = require('tweetnacl');
|
||||||
nacl.util = require('tweetnacl-util');
|
nacl.util = require('tweetnacl-util');
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
const client = new jsrp.client();
|
const client = new jsrp.client();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function loggs in the user (whether it's right after signup, or a normal login)
|
* This function logs in the user (whether it's right after signup, or a normal login)
|
||||||
* @param {*} email
|
* @param {string} email - email of the user logging in
|
||||||
* @param {*} password
|
* @param {string} password - password of the user logging in
|
||||||
* @param {*} setErrorLogin
|
* @param {function} setErrorLogin - function that visually dispay an error is something is wrong
|
||||||
* @param {*} router
|
* @param {*} router
|
||||||
* @param {*} isSignUp
|
* @param {boolean} isSignUp - whether this log in is a part of signup
|
||||||
|
* @param {boolean} isLogin - ?
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const attemptLogin = async (
|
const attemptLogin = async (
|
||||||
email,
|
email: string,
|
||||||
password,
|
password: string,
|
||||||
setErrorLogin,
|
setErrorLogin: (value: boolean) => void,
|
||||||
router,
|
router: any,
|
||||||
isSignUp,
|
isSignUp: boolean,
|
||||||
isLogin
|
isLogin: boolean
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const telemetry = new Telemetry().getInstance();
|
const telemetry = new Telemetry().getInstance();
|
||||||
@@ -76,7 +87,7 @@ const attemptLogin = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await getOrganizations();
|
||||||
const userOrgsData = userOrgs.map((org) => org._id);
|
const userOrgsData = userOrgs.map((org: { _id: string; }) => org._id);
|
||||||
|
|
||||||
let orgToLogin;
|
let orgToLogin;
|
||||||
if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
|
if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
|
||||||
@@ -90,7 +101,7 @@ const attemptLogin = async (
|
|||||||
orgId: orgToLogin
|
orgId: orgToLogin
|
||||||
});
|
});
|
||||||
|
|
||||||
orgUserProjects = orgUserProjects?.map((project) => project._id);
|
orgUserProjects = orgUserProjects?.map((project: { _id: string; }) => project._id);
|
||||||
let projectToLogin;
|
let projectToLogin;
|
||||||
if (
|
if (
|
||||||
orgUserProjects.includes(localStorage.getItem('projectData.id'))
|
orgUserProjects.includes(localStorage.getItem('projectData.id'))
|
||||||
@@ -104,26 +115,7 @@ const attemptLogin = async (
|
|||||||
console.log('ERROR: User likely has no projects. ', error);
|
console.log('ERROR: User likely has no projects. ', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user is logging in for the first time, add the example keys
|
|
||||||
if (isSignUp) {
|
|
||||||
await pushKeys({
|
|
||||||
obj: {
|
|
||||||
sDATABASE_URL: [
|
|
||||||
'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
|
||||||
'This is an example of secret referencing.'
|
|
||||||
],
|
|
||||||
sDB_USERNAME: ['OVERRIDE_THIS', ''],
|
|
||||||
sDB_PASSWORD: ['OVERRIDE_THIS', ''],
|
|
||||||
pDB_USERNAME: ['user1234', 'This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need.'],
|
|
||||||
pDB_PASSWORD: ['example_password', 'This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need.'],
|
|
||||||
sTWILIO_AUTH_TOKEN: ['example_twillio_token', ''],
|
|
||||||
sWEBSITE_URL: ['http://localhost:3000', ''],
|
|
||||||
},
|
|
||||||
workspaceId: projectToLogin,
|
|
||||||
env: 'Development'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (email) {
|
if (email) {
|
||||||
telemetry.identify(email);
|
telemetry.identify(email);
|
||||||
telemetry.capture('User Logged In');
|
telemetry.capture('User Logged In');
|
||||||
@@ -133,6 +125,7 @@ const attemptLogin = async (
|
|||||||
router.push('/dashboard/');
|
router.push('/dashboard/');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
setErrorLogin(true);
|
setErrorLogin(true);
|
||||||
console.log('Login response not available');
|
console.log('Login response not available');
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
interface SecretDataProps {
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
pos: number;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function downloads the secrets as a .env file
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {SecretDataProps[]} obj.data - secrets that we want to check for overrides
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const checkOverrides = async ({ data }: { data: SecretDataProps[]; }) => {
|
||||||
|
let secrets : SecretDataProps[] = data!.map((secret) => Object.create(secret));
|
||||||
|
const overridenSecrets = data!.filter(
|
||||||
|
(secret) => secret.type === 'personal'
|
||||||
|
);
|
||||||
|
if (overridenSecrets.length) {
|
||||||
|
overridenSecrets.forEach((secret) => {
|
||||||
|
const index = secrets!.findIndex(
|
||||||
|
(_secret) => _secret.key === secret.key && _secret.type === 'shared'
|
||||||
|
);
|
||||||
|
secrets![index].value = secret.value;
|
||||||
|
});
|
||||||
|
secrets = secrets!.filter((secret) => secret.type === 'shared');
|
||||||
|
}
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkOverrides;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { envMapping } from "../../../public/data/frequentConstants";
|
||||||
|
import checkOverrides from './checkOverrides';
|
||||||
|
|
||||||
|
|
||||||
|
interface SecretDataProps {
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
pos: number;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function downloads the secrets as a .env file
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {SecretDataProps[]} obj.data - secrets that we want to download
|
||||||
|
* @param {string} obj.env - the environment which we're downloading (used for naming the file)
|
||||||
|
*/
|
||||||
|
const downloadDotEnv = async ({ data, env }: { data: SecretDataProps[]; env: string; }) => {
|
||||||
|
if (!data) return;
|
||||||
|
const secrets = await checkOverrides({ data });
|
||||||
|
|
||||||
|
const file = secrets!
|
||||||
|
.map(
|
||||||
|
(item: SecretDataProps) =>
|
||||||
|
`${
|
||||||
|
item.comment
|
||||||
|
? item.comment
|
||||||
|
.split('\n')
|
||||||
|
.map((comment) => '# '.concat(comment))
|
||||||
|
.join('\n') + '\n'
|
||||||
|
: ''
|
||||||
|
}` + [item.key, item.value].join('=')
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const blob = new Blob([file]);
|
||||||
|
const fileDownloadUrl = URL.createObjectURL(blob);
|
||||||
|
const alink = document.createElement('a');
|
||||||
|
alink.href = fileDownloadUrl;
|
||||||
|
alink.download = envMapping[env] + '.env';
|
||||||
|
alink.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default downloadDotEnv;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import YAML from 'yaml';
|
||||||
|
import { YAMLSeq } from 'yaml/types'
|
||||||
|
|
||||||
|
import { envMapping } from "../../../public/data/frequentConstants";
|
||||||
|
import checkOverrides from './checkOverrides';
|
||||||
|
|
||||||
|
|
||||||
|
interface SecretDataProps {
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
pos: number;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function downloads the secrets as a .yml file
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {SecretDataProps[]} obj.data - secrets that we want to download
|
||||||
|
* @param {string} obj.env - used for naming the file
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const downloadYaml = async ({ data, env }: { data: SecretDataProps[]; env: string; }) => {
|
||||||
|
if (!data) return;
|
||||||
|
const doc = new YAML.Document();
|
||||||
|
doc.contents = new YAMLSeq()
|
||||||
|
const secrets = await checkOverrides({ data });
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
const pair = YAML.createNode({ [secret.key]: secret.value });
|
||||||
|
pair.commentBefore = secret.comment
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => (line ? ' '.concat(line) : ''))
|
||||||
|
.join('\n');
|
||||||
|
doc.add(pair);
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = doc
|
||||||
|
.toString()
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => (line.startsWith('-') ? line.replace('- ', '') : line))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const blob = new Blob([file]);
|
||||||
|
const fileDownloadUrl = URL.createObjectURL(blob);
|
||||||
|
const alink = document.createElement('a');
|
||||||
|
alink.href = fileDownloadUrl;
|
||||||
|
alink.download = envMapping[env] + '.yml';
|
||||||
|
alink.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default downloadYaml;
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||||
|
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const {
|
||||||
|
decryptAssymmetric,
|
||||||
|
encryptSymmetric,
|
||||||
|
} = require("../cryptography/crypto");
|
||||||
|
const nacl = require("tweetnacl");
|
||||||
|
nacl.util = require("tweetnacl-util");
|
||||||
|
|
||||||
|
|
||||||
|
interface SecretDataProps {
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
pos: number;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EncryptedSecretProps {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
environment: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
type: "personal" | "shared";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encypt secrets before pushing the to the DB
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {object} obj.secretsToEncrypt - secrets that we want to encrypt
|
||||||
|
* @param {object} obj.workspaceId - the id of a project in which we are encrypting secrets
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const encryptSecrets = async ({ secretsToEncrypt, workspaceId, env }: { secretsToEncrypt: SecretDataProps[]; workspaceId: string; env: string; }) => {
|
||||||
|
const sharedKey = await getLatestFileKey({ workspaceId });
|
||||||
|
|
||||||
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
|
let randomBytes: string;
|
||||||
|
if (Object.keys(sharedKey).length > 0) {
|
||||||
|
// case: a (shared) key exists for the workspace
|
||||||
|
randomBytes = decryptAssymmetric({
|
||||||
|
ciphertext: sharedKey.latestKey.encryptedKey,
|
||||||
|
nonce: sharedKey.latestKey.nonce,
|
||||||
|
publicKey: sharedKey.latestKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// case: a (shared) key does not exist for the workspace
|
||||||
|
randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
const secrets = secretsToEncrypt.map((secret) => {
|
||||||
|
// encrypt key
|
||||||
|
const {
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
iv: secretKeyIV,
|
||||||
|
tag: secretKeyTag,
|
||||||
|
} = encryptSymmetric({
|
||||||
|
plaintext: secret.key,
|
||||||
|
key: randomBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// encrypt value
|
||||||
|
const {
|
||||||
|
ciphertext: secretValueCiphertext,
|
||||||
|
iv: secretValueIV,
|
||||||
|
tag: secretValueTag,
|
||||||
|
} = encryptSymmetric({
|
||||||
|
plaintext: secret.value,
|
||||||
|
key: randomBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// encrypt comment
|
||||||
|
const {
|
||||||
|
ciphertext: secretCommentCiphertext,
|
||||||
|
iv: secretCommentIV,
|
||||||
|
tag: secretCommentTag,
|
||||||
|
} = encryptSymmetric({
|
||||||
|
plaintext: secret.comment ?? '',
|
||||||
|
key: randomBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result: EncryptedSecretProps = {
|
||||||
|
id: secret.id,
|
||||||
|
createdAt: '',
|
||||||
|
environment: env,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
type: secret.type,
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default encryptSecrets;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import getSecrets from '~/pages/api/files/GetSecrets';
|
import getSecrets from '~/pages/api/files/GetSecrets';
|
||||||
|
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
|
||||||
|
|
||||||
import { envMapping } from '../../../public/data/frequentConstants';
|
import { envMapping } from '../../../public/data/frequentConstants';
|
||||||
import guidGenerator from '../randomId';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
@@ -10,6 +10,22 @@ const {
|
|||||||
const nacl = require('tweetnacl');
|
const nacl = require('tweetnacl');
|
||||||
nacl.util = require('tweetnacl-util');
|
nacl.util = require('tweetnacl-util');
|
||||||
|
|
||||||
|
interface EncryptedSecretProps {
|
||||||
|
_id: string;
|
||||||
|
createdAt: string;
|
||||||
|
environment: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
type: "personal" | "shared";
|
||||||
|
}
|
||||||
|
|
||||||
interface SecretProps {
|
interface SecretProps {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -18,107 +34,102 @@ interface SecretProps {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface FunctionProps {
|
||||||
env: keyof typeof envMapping;
|
env: keyof typeof envMapping;
|
||||||
setFileState: any;
|
|
||||||
setIsKeyAvailable: any;
|
setIsKeyAvailable: any;
|
||||||
setData: any;
|
setData: any;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the secrets for a certain project
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.env - environment for which we are getting secrets
|
||||||
|
* @param {boolean} obj.isKeyAvailable - if a person is able to create new key pairs
|
||||||
|
* @param {function} obj.setData - state function that manages the state of secrets in the dashboard
|
||||||
|
* @param {string} obj.workspaceId - id of a workspace for which we are getting secrets
|
||||||
|
*/
|
||||||
const getSecretsForProject = async ({
|
const getSecretsForProject = async ({
|
||||||
env,
|
env,
|
||||||
setFileState,
|
|
||||||
setIsKeyAvailable,
|
setIsKeyAvailable,
|
||||||
setData,
|
setData,
|
||||||
workspaceId
|
workspaceId
|
||||||
}: Props) => {
|
}: FunctionProps) => {
|
||||||
try {
|
try {
|
||||||
let file;
|
let encryptedSecrets;
|
||||||
try {
|
try {
|
||||||
file = await getSecrets(workspaceId, envMapping[env]);
|
encryptedSecrets = await getSecrets(workspaceId, envMapping[env]);
|
||||||
|
|
||||||
setFileState(file);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('ERROR: Not able to access the latest file');
|
console.log('ERROR: Not able to access the latest version of secrets');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const latestKey = await getLatestFileKey({ workspaceId })
|
||||||
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
|
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
|
||||||
setIsKeyAvailable(!file.key ? file.secrets.length == 0 : true);
|
setIsKeyAvailable(!latestKey ? encryptedSecrets.length == 0 : true);
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||||
|
|
||||||
const tempFileState: SecretProps[] = [];
|
const tempDecryptedSecrets: SecretProps[] = [];
|
||||||
if (file.key) {
|
if (latestKey) {
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: file.key.encryptedKey,
|
ciphertext: latestKey.latestKey.encryptedKey,
|
||||||
nonce: file.key.nonce,
|
nonce: latestKey.latestKey.nonce,
|
||||||
publicKey: file.key.sender.publicKey,
|
publicKey: latestKey.latestKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
file.secrets.map((secretPair: any) => {
|
// decrypt secret keys, values, and comments
|
||||||
// decrypt .env file with symmetric key
|
encryptedSecrets.map((secret: EncryptedSecretProps) => {
|
||||||
const plainTextKey = decryptSymmetric({
|
const plainTextKey = decryptSymmetric({
|
||||||
ciphertext: secretPair.secretKey.ciphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secretPair.secretKey.iv,
|
iv: secret.secretKeyIV,
|
||||||
tag: secretPair.secretKey.tag,
|
tag: secret.secretKeyTag,
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const plainTextValue = decryptSymmetric({
|
const plainTextValue = decryptSymmetric({
|
||||||
ciphertext: secretPair.secretValue.ciphertext,
|
ciphertext: secret.secretValueCiphertext,
|
||||||
iv: secretPair.secretValue.iv,
|
iv: secret.secretValueIV,
|
||||||
tag: secretPair.secretValue.tag,
|
tag: secret.secretValueTag,
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
let plainTextComment;
|
let plainTextComment;
|
||||||
if (secretPair.secretComment.ciphertext) {
|
if (secret.secretCommentCiphertext) {
|
||||||
plainTextComment = decryptSymmetric({
|
plainTextComment = decryptSymmetric({
|
||||||
ciphertext: secretPair.secretComment.ciphertext,
|
ciphertext: secret.secretCommentCiphertext,
|
||||||
iv: secretPair.secretComment.iv,
|
iv: secret.secretCommentIV,
|
||||||
tag: secretPair.secretComment.tag,
|
tag: secret.secretCommentTag,
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
plainTextComment = "";
|
plainTextComment = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
tempFileState.push({
|
tempDecryptedSecrets.push({
|
||||||
id: secretPair._id,
|
id: secret._id,
|
||||||
key: plainTextKey,
|
key: plainTextKey,
|
||||||
value: plainTextValue,
|
value: plainTextValue,
|
||||||
type: secretPair.type,
|
type: secret.type,
|
||||||
comment: plainTextComment
|
comment: plainTextComment
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setFileState(tempFileState);
|
|
||||||
|
|
||||||
setData(
|
const result = tempDecryptedSecrets.map((secret, index) => {
|
||||||
tempFileState.map((line, index) => {
|
|
||||||
return {
|
|
||||||
id: line['id'],
|
|
||||||
pos: index,
|
|
||||||
key: line['key'],
|
|
||||||
value: line['value'],
|
|
||||||
type: line['type'],
|
|
||||||
comment: line['comment']
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return tempFileState.map((line, index) => {
|
|
||||||
return {
|
return {
|
||||||
id: line['id'],
|
id: secret['id'],
|
||||||
pos: index,
|
pos: index,
|
||||||
key: line['key'],
|
key: secret['key'],
|
||||||
value: line['value'],
|
value: secret['value'],
|
||||||
type: line['type'],
|
type: secret['type'],
|
||||||
comment: line['comment']
|
comment: secret['comment']
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setData(result);
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Something went wrong during accessing or decripting secrets.');
|
console.log('Something went wrong during accessing or decripting secrets.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
import uploadSecrets from "~/pages/api/files/UploadSecrets";
|
|
||||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
|
||||||
import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys";
|
|
||||||
|
|
||||||
import { envMapping } from "../../../public/data/frequentConstants";
|
|
||||||
|
|
||||||
const crypto = require("crypto");
|
|
||||||
const {
|
|
||||||
decryptAssymmetric,
|
|
||||||
encryptSymmetric,
|
|
||||||
encryptAssymmetric,
|
|
||||||
} = require("../cryptography/crypto");
|
|
||||||
const nacl = require("tweetnacl");
|
|
||||||
nacl.util = require("tweetnacl-util");
|
|
||||||
|
|
||||||
export interface IK {
|
|
||||||
publicKey: string;
|
|
||||||
userId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function pushes the keys to the database after decrypting them end-to-end
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {object} obj.obj - object with all the key pairs
|
|
||||||
* @param {object} obj.workspaceId - the id of a project to which a user is pushing
|
|
||||||
* @param {object} obj.env - which environment a user is pushing to
|
|
||||||
*/
|
|
||||||
const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: string; env: string; }) => {
|
|
||||||
const sharedKey = await getLatestFileKey({ workspaceId });
|
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
|
||||||
|
|
||||||
let randomBytes: string;
|
|
||||||
if (Object.keys(sharedKey).length > 0) {
|
|
||||||
// case: a (shared) key exists for the workspace
|
|
||||||
randomBytes = decryptAssymmetric({
|
|
||||||
ciphertext: sharedKey.latestKey.encryptedKey,
|
|
||||||
nonce: sharedKey.latestKey.nonce,
|
|
||||||
publicKey: sharedKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: PRIVATE_KEY,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// case: a (shared) key does not exist for the workspace
|
|
||||||
randomBytes = crypto.randomBytes(16).toString("hex");
|
|
||||||
}
|
|
||||||
|
|
||||||
const secrets = Object.keys(obj).map((key) => {
|
|
||||||
// encrypt key
|
|
||||||
const {
|
|
||||||
ciphertext: secretKeyCiphertext,
|
|
||||||
iv: secretKeyIV,
|
|
||||||
tag: secretKeyTag,
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: key.slice(1),
|
|
||||||
key: randomBytes,
|
|
||||||
});
|
|
||||||
|
|
||||||
// encrypt value
|
|
||||||
const {
|
|
||||||
ciphertext: secretValueCiphertext,
|
|
||||||
iv: secretValueIV,
|
|
||||||
tag: secretValueTag,
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: obj[key as keyof typeof obj][0],
|
|
||||||
key: randomBytes,
|
|
||||||
});
|
|
||||||
|
|
||||||
// encrypt comment
|
|
||||||
const {
|
|
||||||
ciphertext: secretCommentCiphertext,
|
|
||||||
iv: secretCommentIV,
|
|
||||||
tag: secretCommentTag,
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: obj[key as keyof typeof obj][1],
|
|
||||||
key: randomBytes,
|
|
||||||
});
|
|
||||||
|
|
||||||
const visibility = key.charAt(0) == "p" ? "personal" : "shared";
|
|
||||||
|
|
||||||
return {
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretKeyHash: crypto.createHash("sha256").update(key.slice(1)).digest("hex"),
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
secretValueHash: crypto.createHash("sha256").update(obj[key as keyof typeof obj][0]).digest("hex"),
|
|
||||||
secretCommentCiphertext,
|
|
||||||
secretCommentIV,
|
|
||||||
secretCommentTag,
|
|
||||||
secretCommentHash: crypto.createHash("sha256").update(obj[key as keyof typeof obj][1]).digest("hex"),
|
|
||||||
type: visibility,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// obtain public keys of all receivers (i.e. members in workspace)
|
|
||||||
const publicKeys = await getWorkspaceKeys({
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// assymmetrically encrypt key with each receiver public keys
|
|
||||||
const keys = publicKeys.map((k: IK) => {
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
|
||||||
plaintext: randomBytes,
|
|
||||||
publicKey: k.publicKey,
|
|
||||||
privateKey: PRIVATE_KEY,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
encryptedKey: ciphertext,
|
|
||||||
nonce,
|
|
||||||
userId: k.userId,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// send payload
|
|
||||||
await uploadSecrets({
|
|
||||||
workspaceId,
|
|
||||||
secrets,
|
|
||||||
keys,
|
|
||||||
environment: envMapping[env as keyof typeof envMapping],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default pushKeys;
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import publicKeyInfical from '~/pages/api/auth/publicKeyInfisical';
|
|
||||||
import changeHerokuConfigVars from '~/pages/api/integrations/ChangeHerokuConfigVars';
|
|
||||||
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const {
|
|
||||||
encryptSymmetric,
|
|
||||||
encryptAssymmetric
|
|
||||||
} = require('../cryptography/crypto');
|
|
||||||
const nacl = require('tweetnacl');
|
|
||||||
nacl.util = require('tweetnacl-util');
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
obj: Record<string, string>;
|
|
||||||
integrationId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushKeysIntegration = async ({ obj, integrationId }: Props) => {
|
|
||||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
|
||||||
|
|
||||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
|
||||||
|
|
||||||
const secrets = Object.keys(obj).map((key) => {
|
|
||||||
// encrypt key
|
|
||||||
const {
|
|
||||||
ciphertext: ciphertextKey,
|
|
||||||
iv: ivKey,
|
|
||||||
tag: tagKey
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: key,
|
|
||||||
key: randomBytes
|
|
||||||
});
|
|
||||||
|
|
||||||
// encrypt value
|
|
||||||
const {
|
|
||||||
ciphertext: ciphertextValue,
|
|
||||||
iv: ivValue,
|
|
||||||
tag: tagValue
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: obj[key],
|
|
||||||
key: randomBytes
|
|
||||||
});
|
|
||||||
|
|
||||||
const visibility = 'shared';
|
|
||||||
|
|
||||||
return {
|
|
||||||
ciphertextKey,
|
|
||||||
ivKey,
|
|
||||||
tagKey,
|
|
||||||
hashKey: crypto.createHash('sha256').update(key).digest('hex'),
|
|
||||||
ciphertextValue,
|
|
||||||
ivValue,
|
|
||||||
tagValue,
|
|
||||||
hashValue: crypto.createHash('sha256').update(obj[key]).digest('hex'),
|
|
||||||
type: visibility
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// obtain public keys of all receivers (i.e. members in workspace)
|
|
||||||
const publicKeyInfisical = await publicKeyInfical();
|
|
||||||
|
|
||||||
const publicKey = (await publicKeyInfisical.json()).publicKey;
|
|
||||||
|
|
||||||
// assymmetrically encrypt key with each receiver public keys
|
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
|
||||||
plaintext: randomBytes,
|
|
||||||
publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
const key = {
|
|
||||||
encryptedKey: ciphertext,
|
|
||||||
nonce
|
|
||||||
};
|
|
||||||
|
|
||||||
changeHerokuConfigVars({ integrationId, key, secrets });
|
|
||||||
};
|
|
||||||
|
|
||||||
export default pushKeysIntegration;
|
|
||||||
@@ -20,7 +20,6 @@ const getActionData = async ({ actionId }: workspaceProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
console.log(188, res)
|
|
||||||
if (res && res.status == 200) {
|
if (res && res.status == 200) {
|
||||||
return (await res.json()).action;
|
return (await res.json()).action;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function performs a rollback of secrets in a certain project
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.workspaceId - id of the project for which we are rolling back data
|
||||||
|
* @param {number} obj.version - version to which we are rolling back
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const performSecretRollback = async ({ workspaceId, version }: { workspaceId: string; version: number; }) => {
|
||||||
|
return SecurityClient.fetchCall(
|
||||||
|
'/api/v1/workspace/' + workspaceId + "/secret-snapshots/rollback", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return (await res.json());
|
||||||
|
} else {
|
||||||
|
console.log('Failed to perform the secret rollback');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default performSecretRollback;
|
||||||
@@ -111,7 +111,7 @@ const PITRecoverySidebar = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setSnapshotData({ id: secretSnapshotData._id, createdAt: secretSnapshotData.createdAt, secretVersions: decryptedSecretVersions })
|
setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: decryptedSecretVersions })
|
||||||
}
|
}
|
||||||
|
|
||||||
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-40 shadow-xl flex flex-col justify-between`}>
|
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-40 shadow-xl flex flex-col justify-between`}>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
interface EncryptedSecretProps {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
environment: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
type: "personal" | "shared";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function adds secrets to a certain project
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {EncryptedSecretProps} obj.secrets - the ids of secrets that we want to add
|
||||||
|
* @param {string} obj.env - the environment to which we are adding secrets
|
||||||
|
* @param {string} obj.workspaceId - the project to which we are adding secrets
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const addSecrets = async ({ secrets, env, workspaceId }: { secrets: EncryptedSecretProps[]; env: string; workspaceId: string; }) => {
|
||||||
|
return SecurityClient.fetchCall('/api/v2/secrets', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
environment: env,
|
||||||
|
workspaceId,
|
||||||
|
secrets
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return await res.json();
|
||||||
|
} else {
|
||||||
|
console.log('Failed to add certain project secrets');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addSecrets;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function deletes certain secrets from a certain project
|
||||||
|
* @param {string[]} secretIds - the ids of secrets that we want to be deleted
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const deleteSecrets = async ({ secretIds }: { secretIds: string[] }) => {
|
||||||
|
return SecurityClient.fetchCall('/api/v2/secrets', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
secretIds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return await res.json();
|
||||||
|
} else {
|
||||||
|
console.log('Failed to delete certain project secrets');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteSecrets;
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
import SecurityClient from '~/utilities/SecurityClient';
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function fetches the encrypted secrets from the .env file
|
* This function fetches the encrypted secrets for a certain project
|
||||||
* @param {string} workspaceId - project is for which a user is trying to get secrets
|
* @param {string} workspaceId - project is for which a user is trying to get secrets
|
||||||
* @param {string} env - environment of a project for which a user is trying ot get secrets
|
* @param {string} env - environment of a project for which a user is trying ot get secrets
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getSecrets = async (workspaceId: string, env: string) => {
|
const getSecrets = async (workspaceId: string, env: string) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
'/api/v1/secret/' +
|
'/api/v2/secrets?' +
|
||||||
workspaceId +
|
|
||||||
'?' +
|
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
environment: env,
|
environment: env,
|
||||||
channel: 'web'
|
workspaceId
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -23,7 +21,7 @@ const getSecrets = async (workspaceId: string, env: string) => {
|
|||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res && res.status == 200) {
|
if (res && res.status == 200) {
|
||||||
return await res.json();
|
return (await res.json()).secrets;
|
||||||
} else {
|
} else {
|
||||||
console.log('Failed to get project secrets');
|
console.log('Failed to get project secrets');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
interface EncryptedSecretProps {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
environment: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
type: "personal" | "shared";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function updates certain secrets in a certain project
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {EncryptedSecretProps[]} obj.secrets - the ids of secrets that we want to update
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const updateSecrets = async ({ secrets }: { secrets: EncryptedSecretProps[] }) => {
|
||||||
|
return SecurityClient.fetchCall('/api/v2/secrets', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
secrets
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return await res.json();
|
||||||
|
} else {
|
||||||
|
console.log('Failed to update certain project secrets');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateSecrets;
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
faCheck,
|
faCheck,
|
||||||
faClockRotateLeft,
|
faClockRotateLeft,
|
||||||
faDownload,
|
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faFolderOpen,
|
faFolderOpen,
|
||||||
@@ -18,28 +17,32 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import getProjectSercetSnapshotsCount from 'ee/api/secrets/GetProjectSercetSnapshotsCount';
|
import getProjectSercetSnapshotsCount from 'ee/api/secrets/GetProjectSercetSnapshotsCount';
|
||||||
|
import performSecretRollback from 'ee/api/secrets/PerformSecretRollback';
|
||||||
import PITRecoverySidebar from 'ee/components/PITRecoverySidebar';
|
import PITRecoverySidebar from 'ee/components/PITRecoverySidebar';
|
||||||
|
|
||||||
import Button from '~/components/basic/buttons/Button';
|
import Button from '~/components/basic/buttons/Button';
|
||||||
import ListBox from '~/components/basic/Listbox';
|
import ListBox from '~/components/basic/Listbox';
|
||||||
import BottonRightPopup from '~/components/basic/popups/BottomRightPopup';
|
import BottonRightPopup from '~/components/basic/popups/BottomRightPopup';
|
||||||
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
|
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
|
||||||
|
import DownloadSecretMenu from '~/components/dashboard/DownloadSecretsMenu';
|
||||||
import DropZone from '~/components/dashboard/DropZone';
|
import DropZone from '~/components/dashboard/DropZone';
|
||||||
import KeyPair from '~/components/dashboard/KeyPair';
|
import KeyPair from '~/components/dashboard/KeyPair';
|
||||||
import SideBar from '~/components/dashboard/SideBar';
|
import SideBar from '~/components/dashboard/SideBar';
|
||||||
import NavHeader from '~/components/navigation/NavHeader';
|
import NavHeader from '~/components/navigation/NavHeader';
|
||||||
|
import encryptSecrets from '~/components/utilities/secrets/encryptSecrets';
|
||||||
import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject';
|
import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject';
|
||||||
import pushKeys from '~/components/utilities/secrets/pushKeys';
|
|
||||||
import { getTranslatedServerSideProps } from '~/components/utilities/withTranslateProps';
|
import { getTranslatedServerSideProps } from '~/components/utilities/withTranslateProps';
|
||||||
import guidGenerator from '~/utilities/randomId';
|
import guidGenerator from '~/utilities/randomId';
|
||||||
|
|
||||||
import { envMapping, reverseEnvMapping } from '../../public/data/frequentConstants';
|
import { envMapping, reverseEnvMapping } from '../../public/data/frequentConstants';
|
||||||
|
import addSecrets from '../api/files/AddSecrets';
|
||||||
|
import deleteSecrets from '../api/files/DeleteSecrets';
|
||||||
|
import updateSecrets from '../api/files/UpdateSecrets';
|
||||||
import getUser from '../api/user/getUser';
|
import getUser from '../api/user/getUser';
|
||||||
import checkUserAction from '../api/userActions/checkUserAction';
|
import checkUserAction from '../api/userActions/checkUserAction';
|
||||||
import registerUserAction from '../api/userActions/registerUserAction';
|
import registerUserAction from '../api/userActions/registerUserAction';
|
||||||
import getWorkspaces from '../api/workspace/getWorkspaces';
|
import getWorkspaces from '../api/workspace/getWorkspaces';
|
||||||
|
|
||||||
const queryString = require("query-string");
|
|
||||||
|
|
||||||
interface SecretDataProps {
|
interface SecretDataProps {
|
||||||
type: 'personal' | 'shared';
|
type: 'personal' | 'shared';
|
||||||
@@ -50,9 +53,18 @@ interface SecretDataProps {
|
|||||||
comment: string;
|
comment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface overrideProps {
|
||||||
|
id: string;
|
||||||
|
keyName: string;
|
||||||
|
value: string;
|
||||||
|
pos: number;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SnapshotProps {
|
interface SnapshotProps {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
version: number;
|
||||||
secretVersions: {
|
secretVersions: {
|
||||||
id: string;
|
id: string;
|
||||||
pos: number;
|
pos: number;
|
||||||
@@ -87,7 +99,7 @@ function findDuplicates(arr: any[]) {
|
|||||||
*/
|
*/
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [data, setData] = useState<SecretDataProps[] | null>();
|
const [data, setData] = useState<SecretDataProps[] | null>();
|
||||||
const [fileState, setFileState] = useState<SecretDataProps[]>([]);
|
const [initialData, setInitialData] = useState<SecretDataProps[]>([]);
|
||||||
const [buttonReady, setButtonReady] = useState(false);
|
const [buttonReady, setButtonReady] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [workspaceId, setWorkspaceId] = useState('');
|
const [workspaceId, setWorkspaceId] = useState('');
|
||||||
@@ -194,11 +206,11 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
const dataToSort = await getSecretsForProject({
|
const dataToSort = await getSecretsForProject({
|
||||||
env,
|
env,
|
||||||
setFileState,
|
|
||||||
setIsKeyAvailable,
|
setIsKeyAvailable,
|
||||||
setData,
|
setData,
|
||||||
workspaceId: String(router.query.id)
|
workspaceId: String(router.query.id)
|
||||||
});
|
});
|
||||||
|
setInitialData(dataToSort);
|
||||||
reorderRows(dataToSort);
|
reorderRows(dataToSort);
|
||||||
|
|
||||||
setSharedToHide(
|
setSharedToHide(
|
||||||
@@ -234,14 +246,6 @@ export default function Dashboard() {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface overrideProps {
|
|
||||||
id: string;
|
|
||||||
keyName: string;
|
|
||||||
value: string;
|
|
||||||
pos: number;
|
|
||||||
comment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function add an ovverrided version of a certain secret to the current user
|
* This function add an ovverrided version of a certain secret to the current user
|
||||||
* @param {object} obj
|
* @param {object} obj
|
||||||
@@ -273,12 +277,12 @@ export default function Dashboard() {
|
|||||||
text: `${secretName} has been deleted. Remember to save changes.`,
|
text: `${secretName} has been deleted. Remember to save changes.`,
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
setData(data!.filter((row: SecretDataProps) => !ids.includes(row.id)));
|
sortValuesHandler(data!.filter((row: SecretDataProps) => !ids.includes(row.id)), sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function deleted the override of a certain secrer
|
* This function deleted the override of a certain secrer
|
||||||
* @param {string} id - id of a secret to be deleted
|
* @param {string} id - id of a shared secret; the override with the same key should be deleted
|
||||||
*/
|
*/
|
||||||
const deleteOverride = (id: string) => {
|
const deleteOverride = (id: string) => {
|
||||||
setButtonReady(true);
|
setButtonReady(true);
|
||||||
@@ -291,7 +295,7 @@ export default function Dashboard() {
|
|||||||
setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride))
|
setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride))
|
||||||
|
|
||||||
// resort secrets
|
// resort secrets
|
||||||
const tempData = data!.filter((row: SecretDataProps) => !(row.id == id && row.type == 'personal'))
|
const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal'))
|
||||||
sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical")
|
sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical")
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -311,14 +315,6 @@ export default function Dashboard() {
|
|||||||
setButtonReady(true);
|
setButtonReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyVisibility = (value: "shared" | "personal", pos: number) => {
|
|
||||||
setData((oldData) => {
|
|
||||||
oldData![pos].type = value;
|
|
||||||
return [...oldData!];
|
|
||||||
});
|
|
||||||
setButtonReady(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const modifyComment = (value: string, pos: number) => {
|
const modifyComment = (value: string, pos: number) => {
|
||||||
setData((oldData) => {
|
setData((oldData) => {
|
||||||
oldData![pos].comment = value;
|
oldData![pos].comment = value;
|
||||||
@@ -336,10 +332,6 @@ export default function Dashboard() {
|
|||||||
modifyKey(value, pos);
|
modifyKey(value, pos);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listenChangeVisibility = useCallback((value: "shared" | "personal", pos: number) => {
|
|
||||||
modifyVisibility(value, pos);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const listenChangeComment = useCallback((value: string, pos: number) => {
|
const listenChangeComment = useCallback((value: string, pos: number) => {
|
||||||
modifyComment(value, pos);
|
modifyComment(value, pos);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -347,22 +339,20 @@ export default function Dashboard() {
|
|||||||
/**
|
/**
|
||||||
* Save the changes of environment variables and push them to the database
|
* Save the changes of environment variables and push them to the database
|
||||||
*/
|
*/
|
||||||
const savePush = async (dataToPush?: any[], envToPush?: string) => {
|
const savePush = async (dataToPush?: SecretDataProps[]) => {
|
||||||
let obj;
|
let newData: SecretDataProps[] | null | undefined;
|
||||||
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
|
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
|
||||||
if ((dataToPush ?? [])?.length > 0) {
|
if ((dataToPush ?? [])?.length > 0) {
|
||||||
obj = Object.assign(
|
newData = dataToPush;
|
||||||
{},
|
|
||||||
...dataToPush!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] }))
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Format the new object with environment variables
|
newData = data;
|
||||||
obj = Object.assign(
|
|
||||||
{},
|
|
||||||
...data!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] }))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const obj = Object.assign(
|
||||||
|
{},
|
||||||
|
...newData!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] }))
|
||||||
|
);
|
||||||
|
|
||||||
// Checking if any of the secret keys start with a number - if so, don't do anything
|
// Checking if any of the secret keys start with a number - if so, don't do anything
|
||||||
const nameErrors = !Object.keys(obj)
|
const nameErrors = !Object.keys(obj)
|
||||||
.map((key) => !isNaN(Number(key[0].charAt(0))))
|
.map((key) => !isNaN(Number(key[0].charAt(0))))
|
||||||
@@ -383,10 +373,37 @@ export default function Dashboard() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once "Save changed is clicked", disable that button
|
// Once "Save changes" is clicked, disable that button
|
||||||
setButtonReady(false);
|
setButtonReady(false);
|
||||||
console.log(envToPush ? envToPush : env, env, envToPush)
|
|
||||||
pushKeys({ obj, workspaceId: String(router.query.id), env: envToPush ? envToPush : env });
|
const secretsToBeDeleted
|
||||||
|
= initialData
|
||||||
|
.filter(initDataPoint => !newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id))
|
||||||
|
.map(secret => secret.id);
|
||||||
|
|
||||||
|
const secretsToBeAdded
|
||||||
|
= newData!
|
||||||
|
.filter(newDataPoint => !initialData.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id));
|
||||||
|
|
||||||
|
const secretsToBeUpdated
|
||||||
|
= newData!.filter(newDataPoint => initialData
|
||||||
|
.filter(initDataPoint => newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)
|
||||||
|
&& (newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].value != initDataPoint.value
|
||||||
|
|| newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key
|
||||||
|
|| newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment))
|
||||||
|
.map(secret => secret.id).includes(newDataPoint.id));
|
||||||
|
|
||||||
|
if (secretsToBeDeleted.length > 0) {
|
||||||
|
await deleteSecrets({ secretIds: secretsToBeDeleted });
|
||||||
|
}
|
||||||
|
if (secretsToBeAdded.length > 0) {
|
||||||
|
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId, env: envMapping[env] })
|
||||||
|
await addSecrets({ secrets, env: envMapping[env], workspaceId });
|
||||||
|
}
|
||||||
|
if (secretsToBeUpdated.length > 0) {
|
||||||
|
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated, workspaceId, env: envMapping[env] })
|
||||||
|
await updateSecrets({ secrets });
|
||||||
|
}
|
||||||
|
|
||||||
// If this user has never saved environment variables before, show them a prompt to read docs
|
// If this user has never saved environment variables before, show them a prompt to read docs
|
||||||
if (!hasUserEverPushed) {
|
if (!hasUserEverPushed) {
|
||||||
@@ -425,19 +442,6 @@ export default function Dashboard() {
|
|||||||
setData(sortedData);
|
setData(sortedData);
|
||||||
};
|
};
|
||||||
|
|
||||||
// This function downloads the secrets as a .env file
|
|
||||||
const download = () => {
|
|
||||||
const file = data!
|
|
||||||
.map((item: SecretDataProps) => [item.key, item.value].join('='))
|
|
||||||
.join('\n');
|
|
||||||
const blob = new Blob([file]);
|
|
||||||
const fileDownloadUrl = URL.createObjectURL(blob);
|
|
||||||
const alink = document.createElement('a');
|
|
||||||
alink.href = fileDownloadUrl;
|
|
||||||
alink.download = envMapping[env] + '.env';
|
|
||||||
alink.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteCertainRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => {
|
const deleteCertainRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => {
|
||||||
deleteRow({ids, secretName});
|
deleteRow({ids, secretName});
|
||||||
};
|
};
|
||||||
@@ -535,8 +539,6 @@ export default function Dashboard() {
|
|||||||
<Button
|
<Button
|
||||||
text={String(t("Rollback to this snapshot"))}
|
text={String(t("Rollback to this snapshot"))}
|
||||||
onButtonPressed={async () => {
|
onButtonPressed={async () => {
|
||||||
const envsToRollback = snapshotData.secretVersions.map(sv => sv.environment).filter((v, i, a) => a.indexOf(v) === i);
|
|
||||||
|
|
||||||
// Update secrets in the state only for the current environment
|
// Update secrets in the state only for the current environment
|
||||||
setData(
|
setData(
|
||||||
snapshotData.secretVersions
|
snapshotData.secretVersions
|
||||||
@@ -548,18 +550,9 @@ export default function Dashboard() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Rollback each of the environments in the snapshot
|
// Perform the rollback globally
|
||||||
// #TODO: clean up other environments
|
performSecretRollback({ workspaceId, version: snapshotData.version })
|
||||||
envsToRollback.map(async (envToRollback) => {
|
|
||||||
await savePush(
|
|
||||||
snapshotData.secretVersions
|
|
||||||
.filter(row => row.environment == envToRollback)
|
|
||||||
.map((sv, position) => {
|
|
||||||
return {id: sv.id, pos: position, type: sv.type, key: sv.key, value: sv.value, comment: ''}
|
|
||||||
}),
|
|
||||||
reverseEnvMapping[envToRollback]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
setSnapshotData(undefined);
|
setSnapshotData(undefined);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Rollback has been performed successfully.`,
|
text: `Rollback has been performed successfully.`,
|
||||||
@@ -614,12 +607,7 @@ export default function Dashboard() {
|
|||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
{!snapshotData && <div className="ml-2 min-w-max flex flex-row items-start justify-start">
|
{!snapshotData && <div className="ml-2 min-w-max flex flex-row items-start justify-start">
|
||||||
<Button
|
<DownloadSecretMenu data={data} env={env} />
|
||||||
onButtonPressed={download}
|
|
||||||
color="mineshaft"
|
|
||||||
size="icon-md"
|
|
||||||
icon={faDownload}
|
|
||||||
/>
|
|
||||||
</div>}
|
</div>}
|
||||||
<div className="ml-2 min-w-max flex flex-row items-start justify-start">
|
<div className="ml-2 min-w-max flex flex-row items-start justify-start">
|
||||||
<Button
|
<Button
|
||||||
@@ -672,7 +660,9 @@ export default function Dashboard() {
|
|||||||
modifyValue={listenChangeValue}
|
modifyValue={listenChangeValue}
|
||||||
modifyKey={listenChangeKey}
|
modifyKey={listenChangeKey}
|
||||||
isBlurred={blurred}
|
isBlurred={blurred}
|
||||||
isDuplicate={findDuplicates(data?.map((item) => item.key + item.type))?.includes(keyPair.key + keyPair.type)}
|
isDuplicate={findDuplicates(
|
||||||
|
data?.map((item) => item.key + item.type)
|
||||||
|
)?.includes(keyPair.key + keyPair.type)}
|
||||||
toggleSidebar={toggleSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
sidebarSecretId={sidebarSecretId}
|
sidebarSecretId={sidebarSecretId}
|
||||||
isSnapshot={false}
|
isSnapshot={false}
|
||||||
@@ -694,7 +684,9 @@ export default function Dashboard() {
|
|||||||
modifyValue={listenChangeValue}
|
modifyValue={listenChangeValue}
|
||||||
modifyKey={listenChangeKey}
|
modifyKey={listenChangeKey}
|
||||||
isBlurred={blurred}
|
isBlurred={blurred}
|
||||||
isDuplicate={findDuplicates(data?.map((item) => item.key + item.type))?.includes(keyPair.key + keyPair.type)}
|
isDuplicate={findDuplicates(
|
||||||
|
data?.map((item) => item.key + item.type)
|
||||||
|
)?.includes(keyPair.key + keyPair.type)}
|
||||||
toggleSidebar={toggleSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
sidebarSecretId={sidebarSecretId}
|
sidebarSecretId={sidebarSecretId}
|
||||||
isSnapshot={true}
|
isSnapshot={true}
|
||||||
@@ -728,7 +720,6 @@ export default function Dashboard() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{
|
{
|
||||||
// fileState.message == 'Access needed to pull the latest file' ||
|
|
||||||
(!isKeyAvailable && (
|
(!isKeyAvailable && (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
|||||||
Reference in New Issue
Block a user