Merge pull request #701 from Infisical/main

Update branch
This commit is contained in:
vmatsiiako
2023-06-30 16:54:26 -07:00
committed by GitHub
176 changed files with 11767 additions and 7719 deletions
@@ -1,208 +0,0 @@
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 addAPIKey from "@app/pages/api/apiKey/addAPIKey";
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;
// TODO: These never and any will be filled by single folder that contains types and hooks about the API
apiKeys: any[];
setApiKeys: (arg: any[]) => void;
};
// TODO: convert to TS
const AddApiKeyDialog = ({ isOpen, closeModal, apiKeys, setApiKeys }: Props) => {
const [apiKey, setApiKey] = useState("");
const [apiKeyName, setApiKeyName] = useState("");
const [apiKeyExpiresIn, setApiKeyExpiresIn] = useState("1 day");
const [apiKeyCopied, setApiKeyCopied] = useState(false);
const { t } = useTranslation();
const generateAPIKey = async () => {
const newApiKey = await addAPIKey({
name: apiKeyName,
expiresIn: expiryMapping[apiKeyExpiresIn as keyof typeof expiryMapping]
});
setApiKeys([...apiKeys, newApiKey.apiKeyData]);
setApiKey(newApiKey.apiKey);
};
function copyToClipboard() {
// Get the text field
const copyText = document.getElementById("apiKey") 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);
setApiKeyCopied(true);
setTimeout(() => setApiKeyCopied(false), 2000);
// Alert the copied text
// alert("Copied the text: " + copyText.value);
}
const closeAddApiKeyModal = () => {
closeModal();
setApiKeyName("");
setApiKey("");
};
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"
>
{apiKey === "" ? (
<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.api-key.add-dialog.title")}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
{t("section.api-key.add-dialog.description")}
</p>
</div>
</div>
<div className="mb-2 max-h-28">
<InputField
label={t("section.api-key.add-dialog.name")}
onChangeHandler={setApiKeyName}
type="varName"
value={apiKeyName}
placeholder=""
isRequired
/>
</div>
<div className="max-h-28">
<ListBox
isSelected={apiKeyExpiresIn}
onChange={setApiKeyExpiresIn}
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={() => generateAPIKey()}
color="mineshaft"
text={t("section.api-key.add-dialog.add") as string}
textDisabled={t("section.api-key.add-dialog.add") as string}
size="md"
active={apiKeyName !== ""}
/>
</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.api-key.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.api-key.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={apiKey}
disabled
id="apiKey"
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">
{apiKey}
</div>
<div className="group relative inline-block h-full font-normal text-gray-400 underline duration-200 hover:text-primary">
<button
type="button"
onClick={copyToClipboard}
className="h-full border-l border-white/20 py-2 pl-3.5 pr-4 duration-200 hover:bg-white/[0.12]"
>
{apiKeyCopied ? (
<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={() => closeAddApiKeyModal()}
color="mineshaft"
text="Close"
size="md"
/>
</div>
</Dialog.Panel>
)}
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddApiKeyDialog;
@@ -1,85 +0,0 @@
import { faX } from "@fortawesome/free-solid-svg-icons";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import deleteAPIKey from "../../../pages/api/apiKey/deleteAPIKey";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
interface TokenProps {
_id: string;
name: string;
expiresAt: string;
}
interface ServiceTokensProps {
data: TokenProps[];
setApiKeys: (value: TokenProps[]) => void;
}
/**
* This is the component that we utilize for the api key table
* @param {object} obj
* @param {any[]} obj.data - current state of the api key table
* @param {function} obj.setApiKeys - updating the state of the api key table
* @returns
*/
const ApiKeyTable = ({ data, setApiKeys }: 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">API KEY NAME</th>
<th className="text-left pl-6 pt-2.5 pb-2">VALID 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">
{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={() => {
deleteAPIKey({ apiKeyId: row._id });
setApiKeys(data.filter((token) => token._id !== row._id));
createNotification({
text: `'${row.name}' API key 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 API keys yet
</td>
</tr>
)}
</tbody>
</table>
</div>
);
};
export default ApiKeyTable;
@@ -1,12 +1,11 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faEye, faEyeSlash, faPenToSquare, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
import { plans } from "public/data/frequentConstants";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Select, SelectItem } from "@app/components/v2";
import { useSubscription } from "@app/context";
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
import getOrganizationSubscriptions from "@app/pages/api/organization/GetOrgSubscription";
import changeUserRoleInWorkspace from "@app/pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
@@ -40,13 +39,12 @@ type EnvironmentProps = {
* @returns
*/
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
const { subscription } = useSubscription();
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const host = window.location.origin;
const router = useRouter();
const [myRole, setMyRole] = useState("member");
const [currentPlan, setCurrentPlan] = useState("");
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
const { createNotification } = useNotificationContext();
@@ -128,7 +126,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
denials = [];
}
if (currentPlan !== plans.professional && host === "https://app.infisical.com" && workspaceId !== "63ea8121b6e2b0543ba79616") {
if (subscription?.rbac === false) {
setIsUpgradeModalOpen(true);
} else {
const allDenials = userData[index].deniedPermissions
@@ -167,14 +165,6 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
(async () => {
const result = await getProjectInfo({ projectId: workspaceId });
setWorkspaceEnvs(result.environments);
const orgId = localStorage.getItem("orgData.id") as string;
const subscriptions = await getOrganizationSubscriptions({
orgId
});
if (subscriptions) {
setCurrentPlan(subscriptions.data[0].plan.product);
}
})();
}, [userData, myUser]);
@@ -208,11 +198,13 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
return (
<div className="table-container relative mb-6 mt-1 min-w-max rounded-md border border-mineshaft-600 bg-bunker">
<div className="absolute h-[3.1rem] w-full rounded-t-md bg-white/5" />
<UpgradePlanModal
isOpen={isUpgradeModalOpen}
onClose={closeUpgradeModal}
text="You can change user permissions if you switch to Infisical's Professional plan."
/>
{subscription && (
<UpgradePlanModal
isOpen={isUpgradeModalOpen}
onClose={closeUpgradeModal}
text={subscription.slug === null ? "You can use RBAC under an Enterprise license" : "You can use RBAC if you switch to Infisical's Team Plan."}
/>
)}
<table className="my-0.5 w-full">
<thead className="text-xs font-light text-gray-400 bg-mineshaft-800">
<tr>
-86
View File
@@ -1,86 +0,0 @@
import React from "react";
import StripeRedirect from "@app/pages/api/organization/StripeRedirect";
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
interface Props {
plan: {
name: string;
price: string;
priceExplanation?: string;
text: string;
subtext?: string;
buttonTextMain: string;
buttonTextSecondary: string;
current: boolean;
};
}
export default function Plan({ plan }: Props) {
return (
<div
className={`relative flex flex-col justify-between border min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
plan.name !== "Starter" && plan.current === true ? "border-2 border-primary" : "border-mineshaft-600"
}
`}
>
<div className="flex flex-col">
<div className="flex flex-row justify-between items-center relative z-10">
<p className="px-6 py-4 text-3xl font-semibold text-gray-400">{plan.name}</p>
</div>
<div className="flex flwx-row items-end justify-start mb-4">
<p className="pl-6 text-3xl font-semibold text-primary">{plan.price}</p>
<p className="pl-3 mb-1 text-lg text-gray-400">{plan.priceExplanation}</p>
</div>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">{plan.text}</p>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">{plan.subtext}</p>
</div>
<div className="flex flex-row items-center">
{plan.current === false ? (
<>
{plan.buttonTextMain === "Schedule a Demo" ? (
<a href="/scheduledemo" target='_blank rel="noopener"'>
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-mineshaft-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
{plan.buttonTextMain}
</div>
</a>
) : (
<div
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-mineshaft-600 text-gray-400 font-semibold ${
plan.buttonTextMain === "Downgrade"
? "hover:bg-red hover:text-white hover:border-red"
: "hover:bg-primary hover:text-black hover:border-primary"
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
>
<button
type="button"
onClick={() =>
StripeRedirect({
orgId: tempLocalStorage("orgData.id")
})
}
>
{plan.buttonTextMain}
</button>
</div>
)}
<a href="https://infisical.com/pricing" target='_blank rel="noopener"'>
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
{plan.buttonTextSecondary}
</div>
</a>
</>
) : (
<div
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
plan.name !== "Starter" && plan.current === true ? "bg-primary" : "bg-mineshaft-400"
}`}
>
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>
</div>
)}
</div>
</div>
);
}
@@ -97,7 +97,7 @@ export default function InitialLoginStep({
setIsLoading(false);
}
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
{/* <div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
@@ -57,7 +57,7 @@ export default function NavHeader({
);
return (
<div className="ml-6 flex flex-row items-center pt-6">
<div className="flex flex-row items-center pt-6">
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{currentOrg?.name?.charAt(0)}
</div>
@@ -0,0 +1,108 @@
/* eslint-disable new-cap */
import crypto from "crypto";
import jsrp from "jsrp";
import changePassword2 from "@app/pages/api/auth/ChangePassword2";
import SRP1 from "@app/pages/api/auth/SRP1";
import Aes256Gcm from "./cryptography/aes-256-gcm";
import { deriveArgonKey } from "./cryptography/crypto";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client();
type Params = {
email: string;
currentPassword: string;
newPassword: string;
}
const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => {
return new Promise((resolve, reject) => {
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
let serverPublicKey; let salt;
try {
const clientPublicKey = clientOldPassword.getPublicKey();
const res = await SRP1({ clientPublicKey });
serverPublicKey = res.serverPublicKey;
salt = res.salt;
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof();
clientNewPassword.init({ username: email, password: newPassword }, async () => {
clientNewPassword.createVerifier(async (err, result) => {
try {
const derivedKey = await deriveArgonKey({
password: newPassword,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.randomBytes(32);
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: localStorage.getItem("PRIVATE_KEY") as string,
secret: key
});
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
await changePassword2({
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier
});
saveTokenToLocalStorage({
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
});
resolve();
} catch (err2) {
console.error(err2);
reject(err2);
}
});
});
} catch (err) {
console.error(err);
reject(err);
}
});
});
}
export default attemptChangePassword;
@@ -13,6 +13,7 @@ type Props = {
onChange?: (isOpen: boolean) => void;
deleteKey: string;
title: string;
subTitle?: string;
onDeleteApproved: () => Promise<void>;
};
@@ -22,7 +23,8 @@ export const DeleteActionModal = ({
onChange,
deleteKey,
onDeleteApproved,
title
title,
subTitle = "This action is irreversible!"
}: Props): JSX.Element => {
const [inputData, setInputData] = useState("");
const [isLoading, setIsLoading] = useToggle();
@@ -52,7 +54,7 @@ export const DeleteActionModal = ({
>
<ModalContent
title={title}
subTitle="This action is irreversible!"
subTitle={subTitle}
footerContent={
<div className="flex items-center">
<Button
@@ -11,7 +11,7 @@ export type FormLabelProps = {
};
export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => (
<Label.Root className="mb-1 ml-0.5 block text-sm font-medium text-mineshaft-300" htmlFor={id}>
<Label.Root className="mb-0.5 ml-1 block text-sm font-normal text-mineshaft-400" htmlFor={id}>
{label}
{isRequired && <span className="ml-1 text-red">*</span>}
</Label.Root>
@@ -1,5 +1,7 @@
import Link from "next/link";
import { useSubscription } from "@app/context";
import { Button } from "../Button";
import { Modal, ModalClose, ModalContent } from "../Modal";
@@ -9,28 +11,35 @@ type Props = {
text: string;
};
export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Unleash Infisical's Full Power"
footerContent={[
<Link
href={`/settings/billing/${localStorage.getItem("projectData.id") as string}`}
key="upgrade-plan"
>
<Button className="mr-4 ml-2 mb-2">Upgrade Plan</Button>
</Link>,
<ModalClose asChild key="upgrade-plan-cancel">
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
]}
>
<p className="mb-2 text-bunker-300">{text}</p>
<p className="text-bunker-300">
Upgrade and get access to this, as well as to other powerful enhancements.
</p>
</ModalContent>
</Modal>
);
export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => {
const { subscription } = useSubscription();
const link = (subscription && subscription.slug !== null)
? `/settings/billing/${localStorage.getItem("projectData.id") as string}`
: "https://infisical.com/scheduledemo";
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Unleash Infisical's Full Power"
footerContent={[
<Link
href={link}
key="upgrade-plan"
>
<Button className="mr-4 ml-2 mb-2">Upgrade Plan</Button>
</Link>,
<ModalClose asChild key="upgrade-plan-cancel">
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
]}
>
<p className="mb-2 text-bunker-300">{text}</p>
<p className="text-bunker-300">
Upgrade and get access to this, as well as to other powerful enhancements.
</p>
</ModalContent>
</Modal>
)
}