mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
Begin marking endpoints for deprecation, clean unused frontend code
This commit is contained in:
@@ -260,22 +260,6 @@ export const createOrganizationPortalSession = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return organization subscriptions
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getOrganizationSubscriptions = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => {
|
|
||||||
return res.status(200).send({
|
|
||||||
subscriptions: []
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a org id, return the projects each member of the org belongs to
|
* Given a org id, return the projects each member of the org belongs to
|
||||||
* @param req
|
* @param req
|
||||||
|
|||||||
@@ -148,20 +148,6 @@ router.post(
|
|||||||
organizationController.createOrganizationPortalSession
|
organizationController.createOrganizationPortalSession
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/:organizationId/subscriptions", // TODO endpoint: deprecate
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
|
||||||
}),
|
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganizationSubscriptions
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/workspace-memberships",
|
"/:organizationId/workspace-memberships",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -1,271 +0,0 @@
|
|||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
import { Fragment, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
|
|
||||||
import addServiceToken from "@app/pages/api/serviceToken/addServiceToken";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptSymmetric } from "../../utilities/cryptography/crypto";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import InputField from "../InputField";
|
|
||||||
import ListBox from "../Listbox";
|
|
||||||
|
|
||||||
const expiryMapping = {
|
|
||||||
"1 day": 86400,
|
|
||||||
"7 days": 604800,
|
|
||||||
"1 month": 2592000,
|
|
||||||
"6 months": 15552000,
|
|
||||||
"12 months": 31104000
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean;
|
|
||||||
closeModal: () => void;
|
|
||||||
workspaceId: string;
|
|
||||||
workspaceName: string;
|
|
||||||
serviceTokens: any[];
|
|
||||||
environments: Array<{ name: string; slug: string }>;
|
|
||||||
setServiceTokens: (arg: any[]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddServiceTokenDialog = ({
|
|
||||||
isOpen,
|
|
||||||
closeModal,
|
|
||||||
workspaceId,
|
|
||||||
workspaceName,
|
|
||||||
serviceTokens,
|
|
||||||
environments,
|
|
||||||
setServiceTokens
|
|
||||||
}: Props) => {
|
|
||||||
const [serviceToken, setServiceToken] = useState("");
|
|
||||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
|
||||||
const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(environments?.[0]);
|
|
||||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
|
||||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const generateServiceToken = async () => {
|
|
||||||
const latestFileKey = await getLatestFileKey({ workspaceId });
|
|
||||||
|
|
||||||
const key = decryptAssymmetric({
|
|
||||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestFileKey.latestKey.nonce,
|
|
||||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
|
||||||
});
|
|
||||||
|
|
||||||
const randomBytes = crypto.randomBytes(16).toString("hex");
|
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
|
||||||
plaintext: key,
|
|
||||||
key: randomBytes
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
1234,
|
|
||||||
selectedServiceTokenEnv,
|
|
||||||
environments,
|
|
||||||
selectedServiceTokenEnv?.slug ? selectedServiceTokenEnv.slug : environments[0]?.slug
|
|
||||||
);
|
|
||||||
const newServiceToken = await addServiceToken({
|
|
||||||
name: serviceTokenName,
|
|
||||||
workspaceId,
|
|
||||||
environment: selectedServiceTokenEnv?.slug
|
|
||||||
? selectedServiceTokenEnv.slug
|
|
||||||
: environments[0]?.slug,
|
|
||||||
expiresIn: expiryMapping[serviceTokenExpiresIn as keyof typeof expiryMapping],
|
|
||||||
encryptedKey: ciphertext,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
});
|
|
||||||
|
|
||||||
setServiceTokens(serviceTokens.concat([newServiceToken.serviceTokenData]));
|
|
||||||
setServiceToken(`${newServiceToken.serviceToken}.${randomBytes}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
function copyToClipboard() {
|
|
||||||
// Get the text field
|
|
||||||
const copyText = document.getElementById("serviceToken") as HTMLInputElement;
|
|
||||||
|
|
||||||
// Select the text field
|
|
||||||
copyText.select();
|
|
||||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
|
||||||
|
|
||||||
// Copy the text inside the text field
|
|
||||||
navigator.clipboard.writeText(copyText.value);
|
|
||||||
|
|
||||||
setServiceTokenCopied(true);
|
|
||||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
|
||||||
// Alert the copied text
|
|
||||||
// alert("Copied the text: " + copyText.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeAddServiceTokenModal = () => {
|
|
||||||
closeModal();
|
|
||||||
setServiceTokenName("");
|
|
||||||
setServiceToken("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="z-50">
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0 scale-95"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100 scale-100"
|
|
||||||
leaveTo="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
{serviceToken === "" ? (
|
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="z-50 text-lg font-medium leading-6 text-gray-400"
|
|
||||||
>
|
|
||||||
{t("section.token.add-dialog.title", {
|
|
||||||
target: workspaceName
|
|
||||||
})}
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="mt-2 mb-4">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{t("section.token.add-dialog.description")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2 max-h-28">
|
|
||||||
<InputField
|
|
||||||
label={t("section.token.add-dialog.name")}
|
|
||||||
onChangeHandler={setServiceTokenName}
|
|
||||||
type="varName"
|
|
||||||
value={serviceTokenName}
|
|
||||||
placeholder=""
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2 max-h-28">
|
|
||||||
<ListBox
|
|
||||||
isSelected={
|
|
||||||
selectedServiceTokenEnv?.name
|
|
||||||
? selectedServiceTokenEnv?.name
|
|
||||||
: environments[0]?.name
|
|
||||||
}
|
|
||||||
data={environments.map(({ name }) => name)}
|
|
||||||
onChange={(envName) =>
|
|
||||||
setSelectedServiceTokenEnv(
|
|
||||||
environments.find(({ name }) => envName === name) || {
|
|
||||||
name: "unknown",
|
|
||||||
slug: "unknown"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isFull
|
|
||||||
text={`${t("common.environment")}: `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="max-h-28">
|
|
||||||
<ListBox
|
|
||||||
isSelected={serviceTokenExpiresIn}
|
|
||||||
onChange={setServiceTokenExpiresIn}
|
|
||||||
data={["1 day", "7 days", "1 month", "6 months", "12 months"]}
|
|
||||||
isFull
|
|
||||||
text={`${t("common.expired-in")}: `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="max-w-max">
|
|
||||||
<div className="mt-6 flex w-max flex-col justify-start">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => generateServiceToken()}
|
|
||||||
color="mineshaft"
|
|
||||||
text={t("section.token.add-dialog.add") as string}
|
|
||||||
textDisabled={t("section.token.add-dialog.add") as string}
|
|
||||||
size="md"
|
|
||||||
active={serviceTokenName !== ""}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
) : (
|
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="z-50 text-lg font-medium leading-6 text-gray-400"
|
|
||||||
>
|
|
||||||
{t("section.token.add-dialog.copy-service-token")}
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="mt-2 mb-4">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{t("section.token.add-dialog.copy-service-token-description")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="mt-2 mr-2 flex h-20 w-full items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={serviceToken}
|
|
||||||
id="serviceToken"
|
|
||||||
className="invisible w-full min-w-full bg-white/0 py-2 px-2 text-gray-400 outline-none"
|
|
||||||
/>
|
|
||||||
<div className="w-full max-w-md break-words bg-white/0 py-2 pl-14 pr-2 text-sm text-gray-400 outline-none">
|
|
||||||
{serviceToken}
|
|
||||||
</div>
|
|
||||||
<div className="group relative inline-block h-full font-normal text-gray-400 underline duration-200 hover:text-primary">
|
|
||||||
<button
|
|
||||||
onClick={copyToClipboard}
|
|
||||||
type="button"
|
|
||||||
className="h-full border-l border-white/20 py-2 pl-3.5 pr-4 duration-200 hover:bg-white/[0.12]"
|
|
||||||
>
|
|
||||||
{serviceTokenCopied ? (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="pr-0.5" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCopy} />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-chicago-900 px-3 py-2 text-center text-sm text-gray-400 duration-300 group-hover:flex group-hover:animate-popup">
|
|
||||||
{t("common.click-to-copy")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex w-max flex-col justify-start">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => closeAddServiceTokenModal()}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Close"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
)}
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddServiceTokenDialog;
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { faPencil, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { plans } from "public/data/frequentConstants";
|
|
||||||
|
|
||||||
import { usePopUp } from "../../../hooks/usePopUp";
|
|
||||||
import getOrganizationSubscriptions from "../../../pages/api/organization/GetOrgSubscription";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import { AddUpdateEnvironmentDialog } from "../dialog/AddUpdateEnvironmentDialog";
|
|
||||||
import DeleteActionModal from "../dialog/DeleteActionModal";
|
|
||||||
import UpgradePlanModal from "../dialog/UpgradePlan";
|
|
||||||
|
|
||||||
type Env = { name: string; slug: string };
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
data: Env[];
|
|
||||||
onCreateEnv: (arg0: Env) => Promise<void>;
|
|
||||||
onUpdateEnv: (oldSlug: string, arg0: Env) => Promise<void>;
|
|
||||||
onDeleteEnv: (slug: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }: Props) => {
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
|
||||||
"createUpdateEnv",
|
|
||||||
"deleteEnv",
|
|
||||||
"upgradePlan"
|
|
||||||
] as const);
|
|
||||||
const [plan, setPlan] = useState("");
|
|
||||||
const host = window.location.origin;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// on initial load - run auth check
|
|
||||||
(async () => {
|
|
||||||
const orgId = localStorage.getItem("orgData.id") as string;
|
|
||||||
const subscriptions = await getOrganizationSubscriptions({
|
|
||||||
orgId
|
|
||||||
});
|
|
||||||
if (subscriptions) {
|
|
||||||
setPlan(subscriptions.data[0].plan.product)
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onEnvCreateCB = async (env: Env) => {
|
|
||||||
try {
|
|
||||||
await onCreateEnv(env);
|
|
||||||
handlePopUpClose("createUpdateEnv");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEnvUpdateCB = async (env: Env) => {
|
|
||||||
try {
|
|
||||||
await onUpdateEnv((popUp.createUpdateEnv?.data as Pick<Env, "slug">)?.slug, env);
|
|
||||||
handlePopUpClose("createUpdateEnv");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEnvDeleteCB = async () => {
|
|
||||||
try {
|
|
||||||
await onDeleteEnv((popUp.deleteEnv?.data as Pick<Env, "slug">)?.slug);
|
|
||||||
handlePopUpClose("deleteEnv");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex w-full flex-row justify-between">
|
|
||||||
<div className="flex w-full flex-col">
|
|
||||||
<p className="mb-3 text-xl font-semibold">Project Environments</p>
|
|
||||||
<p className="mb-4 text-base text-gray-400">
|
|
||||||
Choose which environments will show up in your dashboard like development, staging,
|
|
||||||
production
|
|
||||||
</p>
|
|
||||||
<p className="mr-1 self-start text-sm text-gray-500">
|
|
||||||
Note: the text in slugs shows how these environmant should be accessed in CLI.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-48">
|
|
||||||
<Button
|
|
||||||
text="Add New Environment"
|
|
||||||
onButtonPressed={() => {
|
|
||||||
if (plan !== plans.starter || host !== "https://app.infisical.com" || localStorage.getItem("projectData.id") === "63ea8121b6e2b0543ba79616") {
|
|
||||||
handlePopUpOpen("createUpdateEnv");
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="mineshaft"
|
|
||||||
icon={faPlus}
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="table-container relative mb-6 mt-1 w-full rounded-md border border-mineshaft-700 bg-bunker">
|
|
||||||
<div className="absolute h-12 w-full rounded-t-md bg-white/5" />
|
|
||||||
<table className="my-1 w-full">
|
|
||||||
<thead className="text-bunker-300">
|
|
||||||
<tr>
|
|
||||||
<th className="pl-6 pt-2.5 pb-2 text-left">Name</th>
|
|
||||||
<th className="pl-6 pt-2.5 pb-2 text-left">Slug</th>
|
|
||||||
<th aria-label="buttons" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data?.length > 0 ? (
|
|
||||||
data.map(({ name, slug }) => (
|
|
||||||
<tr key={name} className="bg-bunker-800 duration-100 hover:bg-bunker-800/5">
|
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{name}</td>
|
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{slug}</td>
|
|
||||||
<td className="flex justify-end border-t border-mineshaft-700 py-2">
|
|
||||||
<div className="mr-2 flex items-center duration-200 hover:opacity-100">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => {
|
|
||||||
if (plan !== plans.starter || host !== "https://app.infisical.com") {
|
|
||||||
handlePopUpOpen("createUpdateEnv", { name, slug });
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="mineshaft"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faPencil}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mr-6 flex items-center opacity-50 duration-200 hover:opacity-100">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => {
|
|
||||||
if (plan !== plans.starter || host !== "https://app.infisical.com") {
|
|
||||||
handlePopUpOpen("deleteEnv", { name, slug });
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={4} className="pt-7 pb-4 text-center text-bunker-400">
|
|
||||||
No environments found
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<DeleteActionModal
|
|
||||||
isOpen={popUp.deleteEnv.isOpen}
|
|
||||||
title={`Are you sure want to delete ${
|
|
||||||
(popUp?.deleteEnv?.data as { name: string })?.name || " "
|
|
||||||
}?`}
|
|
||||||
deleteKey={(popUp?.deleteEnv?.data as { slug: string })?.slug || ""}
|
|
||||||
onClose={() => handlePopUpClose("deleteEnv")}
|
|
||||||
onSubmit={onEnvDeleteCB}
|
|
||||||
/>
|
|
||||||
<AddUpdateEnvironmentDialog
|
|
||||||
isOpen={popUp.createUpdateEnv.isOpen}
|
|
||||||
isEditMode={Boolean(popUp.createUpdateEnv?.data)}
|
|
||||||
initialValues={popUp?.createUpdateEnv?.data as any}
|
|
||||||
onClose={() => handlePopUpClose("createUpdateEnv")}
|
|
||||||
onCreateSubmit={onEnvCreateCB}
|
|
||||||
onEditSubmit={onEnvUpdateCB}
|
|
||||||
/>
|
|
||||||
<UpgradePlanModal
|
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
|
||||||
onClose={() => handlePopUpClose("upgradePlan")}
|
|
||||||
text="You can add custom environments if you switch to Infisical's Team plan."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EnvironmentTable;
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { faX } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
|
||||||
|
|
||||||
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
|
|
||||||
import guidGenerator from "../../utilities/randomId";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
|
|
||||||
interface TokenProps {
|
|
||||||
_id: string;
|
|
||||||
name: string;
|
|
||||||
environment: string;
|
|
||||||
expiresAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServiceTokensProps {
|
|
||||||
data: TokenProps[];
|
|
||||||
workspaceName: string;
|
|
||||||
setServiceTokens: (value: TokenProps[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the component that we utilize for the service token table
|
|
||||||
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {any[]} obj.data - current state of the service token table
|
|
||||||
* @param {string} obj.workspaceName - name of the current project
|
|
||||||
* @param {function} obj.setServiceTokens - updating the state of the service token table
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTokensProps) => {
|
|
||||||
const { createNotification } = useNotificationContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
|
||||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5" />
|
|
||||||
<table className="w-full my-1">
|
|
||||||
<thead className="text-bunker-300 text-sm font-light">
|
|
||||||
<tr>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">TOKEN NAME</th>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">PROJECT</th>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">ENVIRONMENT</th>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">VAILD UNTIL</th>
|
|
||||||
<th aria-label="button" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data?.length > 0 ? (
|
|
||||||
data?.map((row) => (
|
|
||||||
<tr
|
|
||||||
key={guidGenerator()}
|
|
||||||
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
|
|
||||||
>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.name}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{workspaceName}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.environment}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{new Date(row.expiresAt).toUTCString()}
|
|
||||||
</td>
|
|
||||||
<td className="py-2 border-mineshaft-700 border-t">
|
|
||||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => {
|
|
||||||
deleteServiceToken({ serviceTokenId: row._id });
|
|
||||||
setServiceTokens(data.filter((token) => token._id !== row._id));
|
|
||||||
createNotification({
|
|
||||||
text: `'${row.name}' token has been revoked.`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={4} className="text-center pt-7 pb-5 text-bunker-300 text-sm">
|
|
||||||
No service tokens yet
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServiceTokenTable;
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { faX } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
import changeUserRoleInOrganization from "@app/pages/api/organization/changeUserRoleInOrganization";
|
|
||||||
import deleteUserFromOrganization from "@app/pages/api/organization/deleteUserFromOrganization";
|
|
||||||
import getOrganizationProjectMemberships from "@app/pages/api/organization/GetOrgProjectMemberships";
|
|
||||||
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
|
||||||
import guidGenerator from "../../utilities/randomId";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import Listbox from "../Listbox";
|
|
||||||
|
|
||||||
// const roles = ['admin', 'user'];
|
|
||||||
// TODO: Set type for this
|
|
||||||
type Props = {
|
|
||||||
userData: any[];
|
|
||||||
changeData: (users: any[]) => void;
|
|
||||||
myUser: string;
|
|
||||||
filter: string;
|
|
||||||
resendInvite: (email: string) => void;
|
|
||||||
isOrg: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
|
|
||||||
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
|
||||||
* @param {*} props
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }: Props) => {
|
|
||||||
const [roleSelected, setRoleSelected] = useState(
|
|
||||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
|
||||||
);
|
|
||||||
const router = useRouter();
|
|
||||||
const [myRole, setMyRole] = useState("member");
|
|
||||||
const [userProjectMemberships, setUserProjectMemberships] = useState<any[]>([]);
|
|
||||||
|
|
||||||
const workspaceId = router.query.id as string;
|
|
||||||
// Delete the row in the table (e.g. a user)
|
|
||||||
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
|
||||||
const handleDelete = (membershipId: string, index: number) => {
|
|
||||||
// setUserIdToBeDeleted(userId);
|
|
||||||
// onClick();
|
|
||||||
if (isOrg) {
|
|
||||||
deleteUserFromOrganization(membershipId);
|
|
||||||
} else {
|
|
||||||
deleteUserFromWorkspace(membershipId);
|
|
||||||
}
|
|
||||||
changeData(userData.filter((v, i) => i !== index));
|
|
||||||
setRoleSelected([
|
|
||||||
...roleSelected.slice(0, index),
|
|
||||||
...roleSelected.slice(index + 1, userData?.length)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the role of a certain user
|
|
||||||
const handleRoleUpdate = (index: number, e: string) => {
|
|
||||||
changeUserRoleInOrganization(String(localStorage.getItem("orgData.id")), userData[index].membershipId, e);
|
|
||||||
changeData([
|
|
||||||
...userData.slice(0, index),
|
|
||||||
...[
|
|
||||||
{
|
|
||||||
key: userData[index].key,
|
|
||||||
firstName: userData[index].firstName,
|
|
||||||
lastName: userData[index].lastName,
|
|
||||||
email: userData[index].email,
|
|
||||||
role: e,
|
|
||||||
status: userData[index].status,
|
|
||||||
userId: userData[index].userId,
|
|
||||||
membershipId: userData[index].membershipId,
|
|
||||||
publicKey: userData[index].publicKey
|
|
||||||
}
|
|
||||||
],
|
|
||||||
...userData.slice(index + 1, userData?.length)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
|
|
||||||
(async () => {
|
|
||||||
const result = await getOrganizationProjectMemberships({ orgId: String(localStorage.getItem("orgData.id"))})
|
|
||||||
setUserProjectMemberships(result);
|
|
||||||
})();
|
|
||||||
}, [userData, myUser]);
|
|
||||||
|
|
||||||
const grantAccess = async (id: string, publicKey: string) => {
|
|
||||||
const result = await getLatestFileKey({ workspaceId });
|
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
|
||||||
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
|
||||||
const key = decryptAssymmetric({
|
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
|
||||||
nonce: result.latestKey.nonce,
|
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
|
||||||
plaintext: key,
|
|
||||||
publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadKeys(workspaceId, id, ciphertext, nonce);
|
|
||||||
router.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteMembershipAndResendInvite = (email: string) => {
|
|
||||||
// deleteUserFromWorkspace(membershipId);
|
|
||||||
resendInvite(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max w-full">
|
|
||||||
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
|
|
||||||
<table className="w-full my-0.5">
|
|
||||||
<thead className="text-gray-400 text-sm font-light">
|
|
||||||
<tr>
|
|
||||||
<th className="text-left pl-4 py-3.5">NAME</th>
|
|
||||||
<th className="text-left pl-4 py-3.5">EMAIL</th>
|
|
||||||
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
|
|
||||||
<th className="text-left pl-6 pr-10 py-3.5">PROJECTS</th>
|
|
||||||
<th aria-label="buttons" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{userData?.filter(
|
|
||||||
(user) =>
|
|
||||||
user.firstName?.toLowerCase().includes(filter) ||
|
|
||||||
user.lastName?.toLowerCase().includes(filter) ||
|
|
||||||
user.email?.toLowerCase().includes(filter)
|
|
||||||
).length > 0 &&
|
|
||||||
userData
|
|
||||||
?.filter(
|
|
||||||
(user) =>
|
|
||||||
user.firstName?.toLowerCase().includes(filter) ||
|
|
||||||
user.lastName?.toLowerCase().includes(filter) ||
|
|
||||||
user.email?.toLowerCase().includes(filter)
|
|
||||||
)
|
|
||||||
.map((row, index) => (
|
|
||||||
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
|
|
||||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.firstName} {row.lastName}
|
|
||||||
</td>
|
|
||||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.email}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
<div className="justify-start h-full flex flex-row items-center">
|
|
||||||
{row.status === "accepted" &&
|
|
||||||
((myRole === "admin" && row.role !== "owner") || myRole === "owner") &&
|
|
||||||
(myUser !== row.email) ? (
|
|
||||||
<Listbox
|
|
||||||
isSelected={row.role}
|
|
||||||
onChange={(e) => handleRoleUpdate(index, e)}
|
|
||||||
data={
|
|
||||||
myRole === "owner" ? ["owner", "admin", "member"] : ["admin", "member"]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
row.status !== "invited" &&
|
|
||||||
row.status !== "verified" && (
|
|
||||||
<Listbox
|
|
||||||
isSelected={row.role}
|
|
||||||
onChange={() => {
|
|
||||||
throw new Error("Function not implemented.");
|
|
||||||
}}
|
|
||||||
data={null}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
{(row.status === "invited" || row.status === "verified") && (
|
|
||||||
<div className="w-full pr-20">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => deleteMembershipAndResendInvite(row.email)}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Resend Invite"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{row.status === "completed" && myUser !== row.email && (
|
|
||||||
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => grantAccess(row.userId, row.publicKey)}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Grant Access"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
<div className="flex items-center max-h-16 overflow-x-auto w-full max-w-xl break-all">
|
|
||||||
{userProjectMemberships[row.userId]
|
|
||||||
? userProjectMemberships[row.userId]?.map((project: any) => (
|
|
||||||
<div key={project._id} className='mx-1 min-w-max px-1.5 bg-mineshaft-500 rounded-sm text-sm text-bunker-200 flex items-center'>
|
|
||||||
<span className='mb-0.5 cursor-default'>{project.name}</span>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: <span className='ml-1 text-bunker-100 rounded-sm px-1 py-0.5 text-sm bg-red/80'>This user isn't part of any projects yet.</span>}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
|
||||||
{myUser !== row.email &&
|
|
||||||
// row.role !== "admin" &&
|
|
||||||
myRole !== "member" ? (
|
|
||||||
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => handleDelete(row.membershipId, index)}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="w-9 h-9" />
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserTable;
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { SetStateAction, useEffect, useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
|
||||||
|
|
||||||
import getSecretsForProject from "../utilities/secrets/getSecretsForProject";
|
|
||||||
import { Modal, ModalContent } from "../v2";
|
|
||||||
|
|
||||||
interface Secrets {
|
|
||||||
label: string;
|
|
||||||
secret: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CompareSecretsModalProps {
|
|
||||||
compareModal: boolean;
|
|
||||||
setCompareModal: React.Dispatch<SetStateAction<boolean>>;
|
|
||||||
selectedEnv: WorkspaceEnv;
|
|
||||||
workspaceEnvs: WorkspaceEnv[];
|
|
||||||
workspaceId: string;
|
|
||||||
currentSecret: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CompareSecretsModal = ({
|
|
||||||
compareModal,
|
|
||||||
setCompareModal,
|
|
||||||
selectedEnv,
|
|
||||||
workspaceEnvs,
|
|
||||||
workspaceId,
|
|
||||||
currentSecret
|
|
||||||
}: CompareSecretsModalProps) => {
|
|
||||||
const [secrets, setSecrets] = useState<Secrets[]>([]);
|
|
||||||
|
|
||||||
const getEnvSecrets = async () => {
|
|
||||||
const workspaceEnvironments = workspaceEnvs?.filter((env) => env !== selectedEnv);
|
|
||||||
const newSecrets = await Promise.all(
|
|
||||||
workspaceEnvironments.map(async (env) => {
|
|
||||||
// #TODO: optimize this query somehow...
|
|
||||||
const allSecrets = await getSecretsForProject({ env: env.slug, workspaceId });
|
|
||||||
const secret =
|
|
||||||
allSecrets.find((item) => item.key === currentSecret.key)?.value ?? "Not found";
|
|
||||||
return { label: env.name, secret };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setSecrets([{ label: selectedEnv.name, secret: currentSecret.value }, ...newSecrets]);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (compareModal) {
|
|
||||||
(async () => {
|
|
||||||
await getEnvSecrets();
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}, [compareModal]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={compareModal} onOpenChange={setCompareModal}>
|
|
||||||
<ModalContent title={currentSecret?.key} onOpenAutoFocus={(e) => e.preventDefault()}>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{secrets.length === 0 ? (
|
|
||||||
<div className="flex items-center bg-bunker-900 justify-center h-full py-4 rounded-md">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
secrets.map((item) => (
|
|
||||||
<div key={`${currentSecret.key}${item.label}`} className="space-y-0.5">
|
|
||||||
<p className="text-sm text-bunker-300">{item.label}</p>
|
|
||||||
<input
|
|
||||||
defaultValue={item.secret}
|
|
||||||
className="h-no-capture border border-mineshaft-500 text-md min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full rounded-md bg-bunker-800 px-2 py-1.5 font-mono text-gray-400 caret-white outline-none duration-200 no-scrollbar focus:ring-2 focus:ring-primary/50 "
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default CompareSecretsModal;
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
/* eslint-disable react/no-unused-prop-types */
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import SecretVersionList from "@app/ee/components/SecretVersionList";
|
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
|
||||||
|
|
||||||
import Button from "../basic/buttons/Button";
|
|
||||||
import Toggle from "../basic/Toggle";
|
|
||||||
import CommentField from "./CommentField";
|
|
||||||
import CompareSecretsModal from "./CompareSecretsModal";
|
|
||||||
import DashboardInputField from "./DashboardInputField";
|
|
||||||
import { DeleteActionButton } from "./DeleteActionButton";
|
|
||||||
import GenerateSecretMenu from "./GenerateSecretMenu";
|
|
||||||
|
|
||||||
interface SecretProps {
|
|
||||||
key: string;
|
|
||||||
value: string | undefined;
|
|
||||||
valueOverride: string | undefined;
|
|
||||||
pos: number;
|
|
||||||
id: string;
|
|
||||||
comment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteRowFunctionProps {
|
|
||||||
ids: string[];
|
|
||||||
secretName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SideBarProps {
|
|
||||||
toggleSidebar: (value: string) => void;
|
|
||||||
data: SecretProps[];
|
|
||||||
modifyKey: (value: string, id: string) => void;
|
|
||||||
modifyValue: (value: string, id: string) => void;
|
|
||||||
modifyValueOverride: (value: string | undefined, id: string) => void;
|
|
||||||
modifyComment: (value: string, id: string) => void;
|
|
||||||
buttonReady: boolean;
|
|
||||||
savePush: () => void;
|
|
||||||
sharedToHide: string[];
|
|
||||||
setSharedToHide: (values: string[]) => void;
|
|
||||||
deleteRow: (props: DeleteRowFunctionProps) => void;
|
|
||||||
workspaceEnvs: WorkspaceEnv[];
|
|
||||||
selectedEnv: WorkspaceEnv;
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
|
|
||||||
* @param {SecretProps[]} obj.data - data of a certain key valeu pair
|
|
||||||
* @param {function} obj.modifyKey - function that modifies the secret key
|
|
||||||
* @param {function} obj.modifyValue - function that modifies the secret value
|
|
||||||
* @param {function} obj.modifyValueOverride - function that modifies the secret value if it is an override
|
|
||||||
* @param {boolean} obj.buttonReady - is the button for saving chagnes active
|
|
||||||
* @param {function} obj.savePush - save changes andp ush secrets
|
|
||||||
* @param {function} obj.deleteRow - a function to delete a certain keyPair
|
|
||||||
* @returns the sidebar with 'secret's settings'
|
|
||||||
*/
|
|
||||||
const SideBar = ({
|
|
||||||
toggleSidebar,
|
|
||||||
data,
|
|
||||||
modifyKey,
|
|
||||||
modifyValue,
|
|
||||||
modifyValueOverride,
|
|
||||||
modifyComment,
|
|
||||||
buttonReady,
|
|
||||||
savePush,
|
|
||||||
deleteRow,
|
|
||||||
workspaceEnvs,
|
|
||||||
selectedEnv,
|
|
||||||
workspaceId
|
|
||||||
}: SideBarProps) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [overrideEnabled, setOverrideEnabled] = useState(data[0]?.valueOverride !== undefined);
|
|
||||||
const [compareModal, setCompareModal] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-w-sm absolute sticky top-0 right-0 z-[70] flex h-full w-full max-w-sm flex-col justify-between border-l border-mineshaft-500 bg-bunker shadow-xl">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="h-min w-full overflow-y-auto">
|
|
||||||
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
|
|
||||||
<p className="text-lg font-semibold text-bunker-200">{t("dashboard.sidebar.secret")}</p>
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="p-1"
|
|
||||||
onClick={() => toggleSidebar("None")}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="pointer-events-none mt-4 px-4">
|
|
||||||
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.key")}</p>
|
|
||||||
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
|
|
||||||
<DashboardInputField
|
|
||||||
onChangeHandler={modifyKey}
|
|
||||||
type="varName"
|
|
||||||
id={data[0]?.id}
|
|
||||||
value={data[0]?.key}
|
|
||||||
isDuplicate={false}
|
|
||||||
blurred={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{data[0]?.value || data[0]?.value === "" ? (
|
|
||||||
<div
|
|
||||||
className={`relative mt-2 px-4 ${
|
|
||||||
overrideEnabled && "pointer-events-none opacity-40"
|
|
||||||
} duration-200`}
|
|
||||||
>
|
|
||||||
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.value")}</p>
|
|
||||||
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
|
|
||||||
<DashboardInputField
|
|
||||||
onChangeHandler={modifyValue}
|
|
||||||
type="value"
|
|
||||||
id={data[0].id}
|
|
||||||
value={data[0]?.value}
|
|
||||||
isDuplicate={false}
|
|
||||||
blurred
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="absolute right-[1.07rem] top-[1.6rem] z-50 bg-bunker-800">
|
|
||||||
<GenerateSecretMenu modifyValue={modifyValue} id={data[0]?.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="px-4 pt-4 text-sm text-bunker-300">
|
|
||||||
<span className="mr-1 rounded-md bg-primary-200/10 py-0.5 px-1">
|
|
||||||
{t("common.note")}:
|
|
||||||
</span>
|
|
||||||
{t("dashboard.sidebar.personal-explanation")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mt-4 px-4">
|
|
||||||
{(data[0]?.value || data[0]?.value === "") && (
|
|
||||||
<div className="my-2 flex flex-row items-center justify-between pl-1 pr-2">
|
|
||||||
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.override")}</p>
|
|
||||||
<Toggle
|
|
||||||
enabled={overrideEnabled}
|
|
||||||
setEnabled={setOverrideEnabled}
|
|
||||||
addOverride={modifyValueOverride}
|
|
||||||
id={data[0]?.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`relative ${
|
|
||||||
!overrideEnabled && "pointer-events-none opacity-40"
|
|
||||||
} duration-200`}
|
|
||||||
>
|
|
||||||
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
|
|
||||||
<DashboardInputField
|
|
||||||
onChangeHandler={modifyValueOverride}
|
|
||||||
type="value"
|
|
||||||
id={data[0]?.id}
|
|
||||||
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
|
|
||||||
isDuplicate={false}
|
|
||||||
blurred
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
|
|
||||||
<GenerateSecretMenu modifyValue={modifyValueOverride} id={data[0]?.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SecretVersionList secretId={data[0]?.id} />
|
|
||||||
<CommentField comment={data[0]?.comment} modifyComment={modifyComment} id={data[0]?.id} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mt-full mt-4 mb-4 flex w-96 max-w-sm flex-col justify-start space-y-2 px-4">
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
text="Compare secret across environments"
|
|
||||||
color="mineshaft"
|
|
||||||
size="md"
|
|
||||||
onButtonPressed={() => setCompareModal(true)}
|
|
||||||
/>
|
|
||||||
<CompareSecretsModal
|
|
||||||
compareModal={compareModal}
|
|
||||||
setCompareModal={setCompareModal}
|
|
||||||
currentSecret={{ key: data[0]?.key, value: data[0]?.value ?? "" }}
|
|
||||||
workspaceEnvs={workspaceEnvs}
|
|
||||||
selectedEnv={selectedEnv}
|
|
||||||
workspaceId={workspaceId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Button
|
|
||||||
text={String(t("common.save-changes"))}
|
|
||||||
onButtonPressed={savePush}
|
|
||||||
color="primary"
|
|
||||||
size="md"
|
|
||||||
active={buttonReady}
|
|
||||||
textDisabled="Saved"
|
|
||||||
/>
|
|
||||||
<DeleteActionButton
|
|
||||||
onSubmit={() =>
|
|
||||||
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SideBar;
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
import { Tag } from "public/data/frequentInterfaces";
|
|
||||||
|
|
||||||
import getSecrets from "@app/pages/api/files/GetSecrets";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, decryptSymmetric } from "../cryptography/crypto";
|
|
||||||
|
|
||||||
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";
|
|
||||||
tags: Tag[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SecretProps {
|
|
||||||
key: string;
|
|
||||||
value: string | undefined;
|
|
||||||
type: "personal" | "shared";
|
|
||||||
comment: string;
|
|
||||||
id: string;
|
|
||||||
tags: Tag[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FunctionProps {
|
|
||||||
env: string;
|
|
||||||
setIsKeyAvailable?: any;
|
|
||||||
setData?: any;
|
|
||||||
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 ({
|
|
||||||
env,
|
|
||||||
setIsKeyAvailable,
|
|
||||||
setData,
|
|
||||||
workspaceId
|
|
||||||
}: FunctionProps) => {
|
|
||||||
try {
|
|
||||||
let encryptedSecrets;
|
|
||||||
try {
|
|
||||||
encryptedSecrets = await getSecrets(workspaceId, env);
|
|
||||||
} catch (error) {
|
|
||||||
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
|
|
||||||
if (typeof setIsKeyAvailable === "function") {
|
|
||||||
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
|
||||||
|
|
||||||
const tempDecryptedSecrets: SecretProps[] = [];
|
|
||||||
if (latestKey) {
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
|
||||||
const key = decryptAssymmetric({
|
|
||||||
ciphertext: latestKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestKey.latestKey.nonce,
|
|
||||||
publicKey: latestKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
// decrypt secret keys, values, and comments
|
|
||||||
encryptedSecrets.forEach((secret: EncryptedSecretProps) => {
|
|
||||||
const plainTextKey = decryptSymmetric({
|
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
|
||||||
iv: secret.secretKeyIV,
|
|
||||||
tag: secret.secretKeyTag,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
let plainTextValue;
|
|
||||||
if (secret.secretValueCiphertext !== undefined) {
|
|
||||||
plainTextValue = decryptSymmetric({
|
|
||||||
ciphertext: secret.secretValueCiphertext,
|
|
||||||
iv: secret.secretValueIV,
|
|
||||||
tag: secret.secretValueTag,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
plainTextValue = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let plainTextComment;
|
|
||||||
if (secret.secretCommentCiphertext) {
|
|
||||||
plainTextComment = decryptSymmetric({
|
|
||||||
ciphertext: secret.secretCommentCiphertext,
|
|
||||||
iv: secret.secretCommentIV,
|
|
||||||
tag: secret.secretCommentTag,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
plainTextComment = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDecryptedSecrets.push({
|
|
||||||
id: secret._id,
|
|
||||||
key: plainTextKey,
|
|
||||||
value: plainTextValue,
|
|
||||||
type: secret.type,
|
|
||||||
comment: plainTextComment,
|
|
||||||
tags: secret.tags
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const secretKeys = [...new Set(tempDecryptedSecrets.map((secret) => secret.key))];
|
|
||||||
|
|
||||||
const result = secretKeys.map((key, index) => ({
|
|
||||||
id: tempDecryptedSecrets.filter((secret) => secret.key === key && secret.type === "shared")[0]
|
|
||||||
?.id,
|
|
||||||
idOverride: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0]?.id,
|
|
||||||
pos: index,
|
|
||||||
key,
|
|
||||||
value: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.value,
|
|
||||||
valueOverride: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0]?.value,
|
|
||||||
comment: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.comment,
|
|
||||||
tags: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.tags
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (typeof setData === "function") {
|
|
||||||
setData(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Something went wrong during accessing or decripting secrets.");
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getSecretsForProject;
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
import { createContext, ReactNode, useContext, useMemo } from "react";
|
||||||
|
|
||||||
import { useGetOrganization } from "@app/hooks/api";
|
import { useGetOrganizations } from "@app/hooks/api";
|
||||||
import { Organization } from "@app/hooks/api/types";
|
import { Organization } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const OrgProvider = ({ children }: Props): JSX.Element => {
|
export const OrgProvider = ({ children }: Props): JSX.Element => {
|
||||||
const { data: userOrgs, isLoading } = useGetOrganization();
|
const { data: userOrgs, isLoading } = useGetOrganizations();
|
||||||
|
|
||||||
// const currentWsOrgID = currentWorkspace?.organization;
|
// const currentWsOrgID = currentWorkspace?.organization;
|
||||||
const currentWsOrgID = localStorage.getItem("orgData.id");
|
const currentWsOrgID = localStorage.getItem("orgData.id");
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||||
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
|
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
|
||||||
import addSecrets from "@app/pages/api/files/AddSecrets";
|
import { createSecret } from "@app/hooks/api/secrets/queries";
|
||||||
|
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
||||||
import getUser from "@app/pages/api/user/getUser";
|
import getUser from "@app/pages/api/user/getUser";
|
||||||
import createWorkspace from "@app/pages/api/workspace/createWorkspace";
|
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
||||||
|
|
||||||
const secretsToBeAdded = [
|
const secretsToBeAdded = [
|
||||||
@@ -95,10 +95,12 @@ const initProjectHelper = async ({
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
// create new project
|
// create new project
|
||||||
project = await createWorkspace({
|
const { data: { workspace } } = await createWorkspace({
|
||||||
workspaceName: projectName,
|
workspaceName: projectName,
|
||||||
organizationId
|
organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
project = workspace;
|
||||||
|
|
||||||
// create and upload new (encrypted) project key
|
// create and upload new (encrypted) project key
|
||||||
const randomBytes = crypto.randomBytes(16).toString("hex");
|
const randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
@@ -116,19 +118,33 @@ const initProjectHelper = async ({
|
|||||||
|
|
||||||
await uploadKeys(project._id, user._id, ciphertext, nonce);
|
await uploadKeys(project._id, user._id, ciphertext, nonce);
|
||||||
|
|
||||||
|
const workspaceId = project._id;
|
||||||
|
|
||||||
// encrypt and upload secrets to new project
|
// encrypt and upload secrets to new project
|
||||||
const secrets = await encryptSecrets({
|
const secrets = await encryptSecrets({
|
||||||
secretsToEncrypt: secretsToBeAdded,
|
secretsToEncrypt: secretsToBeAdded,
|
||||||
workspaceId: project._id,
|
workspaceId,
|
||||||
env: "dev"
|
env: "dev"
|
||||||
});
|
});
|
||||||
|
|
||||||
await addSecrets({
|
secrets?.forEach((secret) => {
|
||||||
secrets: secrets ?? [],
|
createSecret({
|
||||||
env: "dev",
|
workspaceId,
|
||||||
workspaceId: project._id
|
environment: secret.environment,
|
||||||
|
type: secret.type,
|
||||||
|
secretKey: secret.secretName,
|
||||||
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: secret.secretKeyIV,
|
||||||
|
secretKeyTag: secret.secretKeyTag,
|
||||||
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
|
secretValueIV: secret.secretValueIV,
|
||||||
|
secretValueTag: secret.secretValueTag,
|
||||||
|
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: secret.secretCommentIV,
|
||||||
|
secretCommentTag: secret.secretCommentTag,
|
||||||
|
secretPath: "/"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to init project in organization", err);
|
console.error("Failed to init project in organization", err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,29 +8,26 @@ const queryKeys = {
|
|||||||
getBot: (workspaceId: string) => [{ workspaceId }, "bot"] as const
|
getBot: (workspaceId: string) => [{ workspaceId }, "bot"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWorkspaceBot = async (workspaceId: string) => {
|
|
||||||
const { data } = await apiRequest.get<{ bot: TBot }>(`/api/v1/bot/${workspaceId}`);
|
|
||||||
return data.bot;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetWorkspaceBot = (workspaceId: string) =>
|
export const useGetWorkspaceBot = (workspaceId: string) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: queryKeys.getBot(workspaceId),
|
queryKey: queryKeys.getBot(workspaceId),
|
||||||
queryFn: () => fetchWorkspaceBot(workspaceId),
|
queryFn: async () => {
|
||||||
|
const { data: { bot } } = await apiRequest.get<{ bot: TBot }>(`/api/v1/bot/${workspaceId}`);
|
||||||
|
return bot;
|
||||||
|
},
|
||||||
enabled: Boolean(workspaceId)
|
enabled: Boolean(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// mutation
|
|
||||||
|
|
||||||
export const useUpdateBotActiveStatus = () => {
|
export const useUpdateBotActiveStatus = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, TSetBotActiveStatusDto>({
|
return useMutation<{}, {}, TSetBotActiveStatusDto>({
|
||||||
mutationFn: ({ botId, isActive, botKey }) =>
|
mutationFn: ({ botId, isActive, botKey }) => {
|
||||||
apiRequest.patch(`/api/v1/bot/${botId}/active`, {
|
return apiRequest.patch(`/api/v1/bot/${botId}/active`, {
|
||||||
isActive,
|
isActive,
|
||||||
botKey
|
botKey
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: (_, { workspaceId }) => {
|
onSuccess: (_, { workspaceId }) => {
|
||||||
queryClient.invalidateQueries(queryKeys.getBot(workspaceId));
|
queryClient.invalidateQueries(queryKeys.getBot(workspaceId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export {
|
|||||||
useCreateCustomerPortalSession,
|
useCreateCustomerPortalSession,
|
||||||
useDeleteOrgPmtMethod,
|
useDeleteOrgPmtMethod,
|
||||||
useDeleteOrgTaxId,
|
useDeleteOrgTaxId,
|
||||||
useGetOrganization,
|
useGetOrganizations,
|
||||||
useGetOrgBillingDetails,
|
useGetOrgBillingDetails,
|
||||||
useGetOrgInvoices,
|
useGetOrgInvoices,
|
||||||
useGetOrgLicenses,
|
useGetOrgLicenses,
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import {
|
|||||||
PmtMethod,
|
PmtMethod,
|
||||||
ProductsTable,
|
ProductsTable,
|
||||||
RenameOrgDTO,
|
RenameOrgDTO,
|
||||||
TaxID} from "./types";
|
TaxID
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
const organizationKeys = {
|
const organizationKeys = {
|
||||||
getUserOrganization: ["organization"] as const,
|
getUserOrganizations: ["organization"] as const,
|
||||||
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
|
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
|
||||||
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
|
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
|
||||||
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const,
|
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const,
|
||||||
@@ -26,13 +27,12 @@ const organizationKeys = {
|
|||||||
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetOrganization = () => {
|
export const useGetOrganizations = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: organizationKeys.getUserOrganization,
|
queryKey: organizationKeys.getUserOrganizations,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
||||||
|
return organizations;
|
||||||
return data.organizations;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ export const useRenameOrg = () => {
|
|||||||
mutationFn: ({ newOrgName, orgId }) =>
|
mutationFn: ({ newOrgName, orgId }) =>
|
||||||
apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }),
|
apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(organizationKeys.getUserOrganization);
|
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { secretSnapshotKeys } from "../secretSnapshots/queries";
|
import { secretSnapshotKeys } from "../secretSnapshots/queries";
|
||||||
import {
|
import {
|
||||||
BatchSecretDTO,
|
BatchSecretDTO,
|
||||||
|
CreateSecretDTO,
|
||||||
DecryptedSecret,
|
DecryptedSecret,
|
||||||
EncryptedSecret,
|
EncryptedSecret,
|
||||||
EncryptedSecretVersion,
|
EncryptedSecretVersion,
|
||||||
GetProjectSecretsDTO,
|
GetProjectSecretsDTO,
|
||||||
GetSecretVersionsDTO,
|
GetSecretVersionsDTO,
|
||||||
TGetProjectSecretsAllEnvDTO
|
TGetProjectSecretsAllEnvDTO} from "./types";
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export const secretKeys = {
|
export const secretKeys = {
|
||||||
// this is also used in secretSnapshot part
|
// this is also used in secretSnapshot part
|
||||||
@@ -324,3 +324,24 @@ export const useBatchSecretsOp = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSecret = async (dto: CreateSecretDTO) => {
|
||||||
|
const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateSecret = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, CreateSecretDTO>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const data = createSecret(dto);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, dto) => {
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
secretKeys.getProjectSecret(dto.workspaceId, dto.environment)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -147,3 +147,22 @@ export type TDeleteSecretsV3DTO = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretName: string;
|
secretName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- v3
|
||||||
|
|
||||||
|
export type CreateSecretDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
type: "shared" | "personal";
|
||||||
|
secretKey: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretPath: string;
|
||||||
|
}
|
||||||
@@ -23,12 +23,13 @@ const fetchWorkspaceServiceTokens = async (workspaceID: string) => {
|
|||||||
|
|
||||||
type UseGetWorkspaceServiceTokensProps = { workspaceID: string };
|
type UseGetWorkspaceServiceTokensProps = { workspaceID: string };
|
||||||
|
|
||||||
export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) =>
|
export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) => {
|
||||||
useQuery({
|
return useQuery({
|
||||||
queryKey: serviceTokenKeys.getAllWorkspaceServiceToken(workspaceID),
|
queryKey: serviceTokenKeys.getAllWorkspaceServiceToken(workspaceID),
|
||||||
queryFn: () => fetchWorkspaceServiceTokens(workspaceID),
|
queryFn: () => fetchWorkspaceServiceTokens(workspaceID),
|
||||||
enabled: Boolean(workspaceID)
|
enabled: Boolean(workspaceID)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// mutation
|
// mutation
|
||||||
export const useCreateServiceToken = () => {
|
export const useCreateServiceToken = () => {
|
||||||
@@ -36,6 +37,7 @@ export const useCreateServiceToken = () => {
|
|||||||
|
|
||||||
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
|
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
|
||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
|
console.log("useCreateServiceToken");
|
||||||
const { data } = await apiRequest.post("/api/v2/service-token/", body);
|
const { data } = await apiRequest.post("/api/v2/service-token/", body);
|
||||||
data.serviceToken += `.${body.randomBytes}`;
|
data.serviceToken += `.${body.randomBytes}`;
|
||||||
return data;
|
return data;
|
||||||
@@ -51,6 +53,7 @@ export const useDeleteServiceToken = () => {
|
|||||||
|
|
||||||
return useMutation<DeleteServiceTokenRes, {}, string>({
|
return useMutation<DeleteServiceTokenRes, {}, string>({
|
||||||
mutationFn: async (serviceTokenId) => {
|
mutationFn: async (serviceTokenId) => {
|
||||||
|
console.log("useDeleteServiceToken");
|
||||||
const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`);
|
const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -166,13 +166,21 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
|
|||||||
enabled: Boolean(workspaceId)
|
enabled: Boolean(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// mutation
|
export const createWorkspace = ({
|
||||||
|
organizationId,
|
||||||
|
workspaceName
|
||||||
|
}: CreateWorkspaceDTO): Promise<{ data: { workspace: Workspace } }> => {
|
||||||
|
return apiRequest.post("/api/v1/workspace", { workspaceName, organizationId });
|
||||||
|
}
|
||||||
|
|
||||||
export const useCreateWorkspace = () => {
|
export const useCreateWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{ data: { workspace: Workspace } }, {}, CreateWorkspaceDTO>({
|
return useMutation<{ data: { workspace: Workspace } }, {}, CreateWorkspaceDTO>({
|
||||||
mutationFn: async ({ organizationId, workspaceName }) =>
|
mutationFn: async ({ organizationId, workspaceName }) => createWorkspace({
|
||||||
apiRequest.post("/api/v1/workspace", { workspaceName, organizationId }),
|
organizationId,
|
||||||
|
workspaceName
|
||||||
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -183,8 +191,9 @@ export const useRenameWorkspace = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, RenameWorkspaceDTO>({
|
return useMutation<{}, {}, RenameWorkspaceDTO>({
|
||||||
mutationFn: ({ workspaceID, newWorkspaceName }) =>
|
mutationFn: ({ workspaceID, newWorkspaceName }) => {
|
||||||
apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName }),
|
return apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName });
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -220,11 +229,12 @@ export const useCreateWsEnvironment = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, CreateEnvironmentDTO>({
|
return useMutation<{}, {}, CreateEnvironmentDTO>({
|
||||||
mutationFn: ({ workspaceID, environmentName, environmentSlug }) =>
|
mutationFn: ({ workspaceID, environmentName, environmentSlug }) => {
|
||||||
apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, {
|
return apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, {
|
||||||
environmentName,
|
environmentName,
|
||||||
environmentSlug
|
environmentSlug
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -235,12 +245,13 @@ export const useUpdateWsEnvironment = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateEnvironmentDTO>({
|
return useMutation<{}, {}, UpdateEnvironmentDTO>({
|
||||||
mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) =>
|
mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) => {
|
||||||
apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, {
|
return apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, {
|
||||||
environmentName,
|
environmentName,
|
||||||
environmentSlug,
|
environmentSlug,
|
||||||
oldEnvironmentSlug
|
oldEnvironmentSlug
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -251,10 +262,11 @@ export const useDeleteWsEnvironment = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeleteEnvironmentDTO>({
|
return useMutation<{}, {}, DeleteEnvironmentDTO>({
|
||||||
mutationFn: ({ workspaceID, environmentSlug }) =>
|
mutationFn: ({ workspaceID, environmentSlug }) => {
|
||||||
apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, {
|
return apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, {
|
||||||
data: { environmentSlug }
|
data: { environmentSlug }
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function fetches the bot for a project
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getBot = async ({ workspaceId }: Props) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v1/bot/${workspaceId}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return (await res.json()).bot;
|
|
||||||
}
|
|
||||||
console.log("Failed to get bot for project");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getBot;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface BotKey {
|
|
||||||
encryptedKey: string;
|
|
||||||
nonce: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
botId: string;
|
|
||||||
isActive: boolean;
|
|
||||||
botKey?: BotKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function sets the active status of a bot and shares a copy of
|
|
||||||
* the project key (encrypted under the bot's public key) with the
|
|
||||||
* project's bot
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.botId
|
|
||||||
* @param {String} obj.isActive
|
|
||||||
* @param {Object} obj.botKey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const setBotActiveStatus = async ({ botId, isActive, botKey }: Props) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v1/bot/${botId}/active`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
isActive,
|
|
||||||
botKey
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to get bot for project");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default setBotActiveStatus;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
type NewEnvironmentInfo = {
|
|
||||||
environmentSlug: string;
|
|
||||||
environmentName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route deletes a specified workspace.
|
|
||||||
* @param {*} workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const createEnvironment = (workspaceId: string, newEnv: NewEnvironmentInfo) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(newEnv)
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to create environment");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default createEnvironment;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
/**
|
|
||||||
* This route deletes a specified env.
|
|
||||||
* @param {*} workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const deleteEnvironment = (workspaceId: string, environmentSlug: string) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ environmentSlug })
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to delete environment");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default deleteEnvironment;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
type EnvironmentInfo = {
|
|
||||||
oldEnvironmentSlug: string;
|
|
||||||
environmentSlug: string;
|
|
||||||
environmentName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route updates a specified environment.
|
|
||||||
* @param {*} workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const updateEnvironment = (workspaceId: string, env: EnvironmentInfo) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(env)
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to update environment");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default updateEnvironment;
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/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;
|
|
||||||
}) =>
|
|
||||||
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 res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to add certain project secrets");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default addSecrets;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/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[] }) =>
|
|
||||||
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 res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to delete certain project secrets");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default deleteSecrets;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} env - environment of a project for which a user is trying ot get secrets
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getSecrets = async (workspaceId: string, env: string) =>
|
|
||||||
SecurityClient.fetchCall(
|
|
||||||
`/api/v2/secrets?${new URLSearchParams({
|
|
||||||
environment: env,
|
|
||||||
workspaceId
|
|
||||||
})}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return (await res.json()).secrets;
|
|
||||||
}
|
|
||||||
console.log("Failed to get project secrets");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getSecrets;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/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[] }) =>
|
|
||||||
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 res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to update certain project secrets");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default updateSecrets;
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId: string;
|
|
||||||
secrets: any;
|
|
||||||
keys: string;
|
|
||||||
environment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function uploads the encrypted .env file
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.workspaceId
|
|
||||||
* @param {} obj.secrets
|
|
||||||
* @param {} obj.keys
|
|
||||||
* @param {string} obj.environment
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }: Props) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/secrets`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
secrets,
|
|
||||||
keys,
|
|
||||||
environment,
|
|
||||||
channel: "web"
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to push secrets");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default uploadSecrets;
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
|
|
||||||
interface RequestType {
|
|
||||||
method: string;
|
|
||||||
secret: {
|
|
||||||
type: string;
|
|
||||||
secretKeyCiphertext: string;
|
|
||||||
secretKeyIV: string;
|
|
||||||
secretKeyTag: string;
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
secretCommentCiphertext: string;
|
|
||||||
secretCommentIV: string;
|
|
||||||
secretCommentTag: string;
|
|
||||||
tags: string[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const batchSecrets = async ({
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
requests
|
|
||||||
}: {
|
|
||||||
workspaceId: string;
|
|
||||||
environment: string;
|
|
||||||
requests: RequestType[];
|
|
||||||
}) => {
|
|
||||||
const { data } = await apiRequest.post("/api/v2/secrets/batch", {
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
requests
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default batchSecrets;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route lets us get the current subscription of an org.
|
|
||||||
* @param {*} req
|
|
||||||
* @param {*} res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getOrganizationSubscriptions = (req: { orgId: string }) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v1/organization/${req.orgId}/subscriptions`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return (await res.json()).subscriptions;
|
|
||||||
}
|
|
||||||
console.log("Failed to get org subscriptions");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getOrganizationSubscriptions;
|
|
||||||
@@ -4,18 +4,20 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
|
|||||||
* This route lets us get the all the orgs of a certain user.
|
* This route lets us get the all the orgs of a certain user.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganizations = () =>
|
const getOrganizations = () => {
|
||||||
SecurityClient.fetchCall("/api/v1/organization", {
|
return SecurityClient.fetchCall("/api/v1/organization", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res?.status === 200) {
|
if (res?.status === 200) {
|
||||||
return (await res.json()).organizations;
|
const {organizations} = await res.json();
|
||||||
|
return organizations;
|
||||||
}
|
}
|
||||||
console.log("Failed to get orgs of a user");
|
console.log("Failed to get orgs of a user");
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default getOrganizations;
|
export default getOrganizations;
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
name: string;
|
|
||||||
workspaceId: string;
|
|
||||||
environment: string;
|
|
||||||
expiresIn: number;
|
|
||||||
encryptedKey: string;
|
|
||||||
iv: string;
|
|
||||||
tag: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route adds a service token for a specific user in a project
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.name - name of the service token
|
|
||||||
* @param {string} obj.workspaceId - workspace for which we are issuing the token
|
|
||||||
* @param {string} obj.environment - environment for which we are issuing the token
|
|
||||||
* @param {string} obj.expiresIn - how soon the service token expires in ms
|
|
||||||
* @param {string} obj.encryptedKey - encrypted project key through random symmetric encryption
|
|
||||||
* @param {string} obj.iv - obtained through symmetric encryption
|
|
||||||
* @param {string} obj.tag - obtained through symmetric encryption
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const addServiceToken = ({
|
|
||||||
name,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
expiresIn,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
}: Props) =>
|
|
||||||
SecurityClient.fetchCall("/api/v2/service-token/", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
expiresIn,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to add service tokens");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default addServiceToken;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
serviceTokenId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route revokes a specific service token
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.serviceTokenId - id of a cervice token that we want to delete
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const deleteServiceToken = ({ serviceTokenId }: Props) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/service-token/${serviceTokenId}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to delete a service token");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default deleteServiceToken;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route gets service tokens for a specific user in a project
|
|
||||||
* @param {*} param0
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getServiceTokens = ({ workspaceId }: { workspaceId: string }) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/service-token-data`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return (await res.json()).serviceTokenData;
|
|
||||||
}
|
|
||||||
console.log("Failed to get service tokens");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getServiceTokens;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route creates a new workspace for a user within a certain organization.
|
|
||||||
* @param {string} workspaceName - project Name
|
|
||||||
* @param {string} organizationId - org ID
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const createWorkspace = ({
|
|
||||||
workspaceName,
|
|
||||||
organizationId
|
|
||||||
}: {
|
|
||||||
workspaceName: string;
|
|
||||||
organizationId: string;
|
|
||||||
}) =>
|
|
||||||
SecurityClient.fetchCall("/api/v1/workspace", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
workspaceName,
|
|
||||||
organizationId
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res?.status === 200) {
|
|
||||||
return (await res.json()).workspace;
|
|
||||||
}
|
|
||||||
console.log("Failed to create a project");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default createWorkspace;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route lets us rename a certain workspace.
|
|
||||||
* @param {*} req
|
|
||||||
* @param {*} res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const renameWorkspace = (workspaceId: string, newWorkspaceName: string) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/name`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: newWorkspaceName
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to rename a project");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default renameWorkspace;
|
|
||||||
+11
-26
@@ -1,30 +1,17 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric
|
||||||
} from "@app/components/utilities/cryptography/crypto";
|
} from "@app/components/utilities/cryptography/crypto";
|
||||||
import { Checkbox } from "@app/components/v2";
|
import { Checkbox } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api";
|
||||||
|
|
||||||
import getBot from "../../../../../pages/api/bot/getBot";
|
|
||||||
import setBotActiveStatus from "../../../../../pages/api/bot/setBotActiveStatus";
|
|
||||||
import getLatestFileKey from "../../../../../pages/api/workspace/getLatestFileKey";
|
import getLatestFileKey from "../../../../../pages/api/workspace/getLatestFileKey";
|
||||||
|
|
||||||
export const E2EESection = () => {
|
export const E2EESection = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const [bot, setBot] = useState<any>(null);
|
const { data: bot } = useGetWorkspaceBot(currentWorkspace?._id ?? "");
|
||||||
|
const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus();
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
if (currentWorkspace) {
|
|
||||||
// get project bot
|
|
||||||
setBot(await getBot({
|
|
||||||
workspaceId: currentWorkspace._id
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [currentWorkspace]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate bot for project by performing the following steps:
|
* Activate bot for project by performing the following steps:
|
||||||
@@ -69,22 +56,20 @@ export const E2EESection = () => {
|
|||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
nonce
|
nonce
|
||||||
};
|
};
|
||||||
|
|
||||||
const botx = await setBotActiveStatus({
|
await updateBotActiveStatus({
|
||||||
botId: bot._id,
|
workspaceId: currentWorkspace._id,
|
||||||
|
botKey,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
botKey
|
botId: bot._id
|
||||||
});
|
});
|
||||||
|
|
||||||
setBot(botx.bot);
|
|
||||||
} else {
|
} else {
|
||||||
// bot is active -> deactivate bot
|
// bot is active -> deactivate bot
|
||||||
const botx = await setBotActiveStatus({
|
await updateBotActiveStatus({
|
||||||
|
isActive: false,
|
||||||
botId: bot._id,
|
botId: bot._id,
|
||||||
isActive: false
|
workspaceId: currentWorkspace._id
|
||||||
});
|
});
|
||||||
|
|
||||||
setBot(botx.bot);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user