From ef234a270f795d7ffd904b1d25624c9074988d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=95=EC=A4=80?= Date: Sun, 27 Nov 2022 16:17:21 +0900 Subject: [PATCH] lint(frontend): apply two space instead of tab --- frontend/.prettierrc | 4 +- frontend/components/RouteGuard.js | 3 +- frontend/components/analytics/posthog.js | 23 +- frontend/components/basic/Error.js | 24 +- frontend/components/basic/InputField.js | 192 +- frontend/components/basic/Listbox.js | 189 +- frontend/components/basic/buttons/Button.js | 180 +- .../basic/dialog/AddIncidentContactDialog.js | 165 +- .../basic/dialog/AddProjectMemberDialog.js | 261 +- .../basic/dialog/AddServiceTokenDialog.js | 468 ++- .../components/basic/dialog/AddUserDialog.js | 206 +- .../basic/dialog/AddWorkspaceDialog.js | 169 +- .../basic/dialog/DeleteUserDialog.js | 137 +- frontend/components/basic/layout.js | 570 ++- .../basic/popups/BottomRightPopup.js | 88 +- frontend/components/basic/table/Checkbox.js | 46 +- .../basic/table/ServiceTokenTable.js | 125 +- frontend/components/basic/table/UserTable.js | 429 +-- frontend/components/billing/Plan.js | 163 +- .../dashboard/DashboardInputField.js | 216 +- frontend/components/dashboard/DropZone.js | 323 +- .../components/navigation/NavBarDashboard.js | 568 ++- frontend/components/navigation/NavHeader.js | 84 +- .../components/utilities/SecurityClient.js | 3 +- frontend/components/utilities/attemptLogin.js | 308 +- .../utilities/checks/PasswordCheck.js | 104 +- frontend/components/utilities/config/index.ts | 20 +- .../utilities/cryptography/aes-256-gcm.js | 80 +- .../utilities/cryptography/changePassword.js | 185 +- .../utilities/cryptography/crypto.js | 92 +- .../utilities/cryptography/issueBackupKey.js | 136 +- frontend/components/utilities/file.js | 52 +- .../components/utilities/generateBackupPDF.js | 201 +- frontend/components/utilities/randomId.js | 34 +- .../utilities/secrets/getSecretsForProject.js | 162 +- .../components/utilities/secrets/pushKeys.js | 161 +- .../utilities/secrets/pushKeysIntegration.js | 104 +- frontend/const.js | 32 +- frontend/next.config.js | 92 +- frontend/package.json | 126 +- frontend/pages/_app.js | 3 +- frontend/pages/api/auth/ChangePassword2.js | 3 +- frontend/pages/api/auth/CheckAuth.js | 3 +- .../pages/api/auth/IssueBackupPrivateKey.js | 3 +- frontend/pages/api/auth/Logout.js | 3 +- frontend/pages/api/auth/SRP1.js | 3 +- frontend/pages/api/files/GetSecrets.js | 3 +- frontend/pages/api/files/UploadSecrets.js | 3 +- .../integrations/ChangeHerokuConfigVars.js | 3 +- .../api/integrations/DeleteIntegration.js | 3 +- .../api/integrations/DeleteIntegrationAuth.js | 3 +- .../api/integrations/GetIntegrationApps.js | 3 +- .../pages/api/integrations/GetIntegrations.js | 3 +- .../api/integrations/StartIntegration.js | 3 +- .../api/integrations/authorizeIntegration.js | 3 +- .../getWorkspaceAuthorizations.js | 3 +- .../integrations/getWorkspaceIntegrations.js | 3 +- frontend/pages/api/organization/GetOrg.js | 3 +- .../pages/api/organization/GetOrgProjects.js | 3 +- .../api/organization/GetOrgSubscription.js | 3 +- .../api/organization/GetOrgUserProjects.js | 3 +- .../pages/api/organization/GetOrgUsers.js | 3 +- .../pages/api/organization/StripeRedirect.js | 3 +- .../api/organization/addIncidentContact.js | 3 +- .../pages/api/organization/addUserToOrg.js | 3 +- .../api/organization/deleteIncidentContact.js | 3 +- .../deleteUserFromOrganization.js | 3 +- .../api/organization/getIncidentContacts.js | 3 +- frontend/pages/api/organization/getOrgs.js | 3 +- frontend/pages/api/organization/renameOrg.js | 3 +- .../pages/api/serviceToken/addServiceToken.js | 3 +- .../api/serviceToken/getServiceTokens.js | 3 +- frontend/pages/api/user/getUser.js | 3 +- .../pages/api/userActions/checkUserAction.js | 3 +- .../api/userActions/registerUserAction.js | 3 +- .../pages/api/workspace/addUserToWorkspace.js | 3 +- .../workspace/changeUserRoleInWorkspace.js | 3 +- .../pages/api/workspace/createWorkspace.js | 3 +- .../api/workspace/deleteUserFromWorkspace.js | 3 +- .../pages/api/workspace/deleteWorkspace.js | 3 +- .../pages/api/workspace/getLatestFileKey.js | 3 +- .../pages/api/workspace/getWorkspaceInfo.js | 3 +- .../pages/api/workspace/getWorkspaceKeys.js | 3 +- .../pages/api/workspace/getWorkspaceUsers.js | 3 +- frontend/pages/api/workspace/getWorkspaces.js | 3 +- .../pages/api/workspace/renameWorkspace.js | 3 +- frontend/pages/api/workspace/uploadKeys.js | 3 +- frontend/pages/dashboard.js | 44 +- frontend/pages/dashboard/[id].js | 1446 ++++---- frontend/pages/heroku.js | 50 +- frontend/pages/index.js | 12 +- frontend/pages/integrations/[id].js | 717 ++-- frontend/pages/login.js | 238 +- frontend/pages/noprojects.js | 28 +- frontend/pages/requestnewinvite.js | 34 +- frontend/pages/settings/billing/[id].js | 181 +- frontend/pages/settings/org/[id].js | 623 ++-- frontend/pages/settings/personal/[id].js | 531 ++- frontend/pages/settings/project/[id].js | 448 ++- frontend/pages/signup.js | 988 +++-- frontend/pages/signupinvite.js | 681 ++-- frontend/pages/users/[id].js | 325 +- frontend/postcss.config.js | 2 +- frontend/tailwind.config.js | 3295 ++++++++--------- 104 files changed, 7883 insertions(+), 8426 deletions(-) diff --git a/frontend/.prettierrc b/frontend/.prettierrc index d6765438e..222861c34 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,4 +1,4 @@ { - "tabWidth": 2, - "useTabs": false + "tabWidth": 2, + "useTabs": false } diff --git a/frontend/components/RouteGuard.js b/frontend/components/RouteGuard.js index 3a7b89ec6..a21c0c86a 100644 --- a/frontend/components/RouteGuard.js +++ b/frontend/components/RouteGuard.js @@ -2,9 +2,8 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; -import checkAuth from "~/pages/api/auth/CheckAuth"; - import { publicPaths } from "~/const"; +import checkAuth from "~/pages/api/auth/CheckAuth"; // #TODO: finish spinner only when the data loads fully // #TODO: Redirect somewhere if the page does not exist diff --git a/frontend/components/analytics/posthog.js b/frontend/components/analytics/posthog.js index c480c4e6b..c8d51ad1b 100644 --- a/frontend/components/analytics/posthog.js +++ b/frontend/components/analytics/posthog.js @@ -1,15 +1,20 @@ import posthog from "posthog-js"; -import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config"; +import { + ENV, + POSTHOG_API_KEY, + POSTHOG_HOST, + TELEMETRY_ENABLED, +} from "../utilities/config"; export const initPostHog = () => { - if (typeof window !== "undefined") { - if (ENV == "production" && TELEMETRY_ENABLED) { - posthog.init(POSTHOG_API_KEY, { - api_host: POSTHOG_HOST, - }); - } - } + if (typeof window !== "undefined") { + if (ENV == "production" && TELEMETRY_ENABLED) { + posthog.init(POSTHOG_API_KEY, { + api_host: POSTHOG_HOST, + }); + } + } - return posthog; + return posthog; }; diff --git a/frontend/components/basic/Error.js b/frontend/components/basic/Error.js index 11b9c6dd3..78a181016 100644 --- a/frontend/components/basic/Error.js +++ b/frontend/components/basic/Error.js @@ -3,17 +3,15 @@ import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function Error({ text }) { - return ( -
- - {text && ( -

- {text} -

- )} -
- ); + return ( +
+ + {text && ( +

{text}

+ )} +
+ ); } diff --git a/frontend/components/basic/InputField.js b/frontend/components/basic/InputField.js index a339b139a..8368d9797 100644 --- a/frontend/components/basic/InputField.js +++ b/frontend/components/basic/InputField.js @@ -2,11 +2,11 @@ import React from "react"; import { useState } from "react"; import { useRouter } from "next/router"; import { - faCircle, - faCircleExclamation, - faE, - faEye, - faEyeSlash, + faCircle, + faCircleExclamation, + faE, + faEye, + faEyeSlash, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -14,36 +14,36 @@ import guidGenerator from "../utilities/randomId"; import Error from "./Error"; const InputField = (props) => { - const [passwordVisible, setPasswordVisible] = useState(false); - const router = useRouter(); + const [passwordVisible, setPasswordVisible] = useState(false); + const router = useRouter(); - if (props.static === true) { - return ( -
-

- {props.label} -

- {props.text && ( -

{props.text}

- )} - props.onChangeHandler(e.target.value)} - type={props.type} - placeholder={props.placeholder} - value={props.value} - required={props.isRequired} - className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none" - name={props.name} - readOnly - /> -
- ); - } else { - return ( -
-
-

{props.label}

- {/* {props.label == "Password" && router.asPath != "/login" && ( + if (props.static === true) { + return ( +
+

+ {props.label} +

+ {props.text && ( +

{props.text}

+ )} + props.onChangeHandler(e.target.value)} + type={props.type} + placeholder={props.placeholder} + value={props.value} + required={props.isRequired} + className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none" + name={props.name} + readOnly + /> +
+ ); + } else { + return ( +
+
+

{props.label}

+ {/* {props.label == "Password" && router.asPath != "/login" && (
{
)} */} -
-
- props.onChangeHandler(e.target.value)} - type={passwordVisible == false ? props.type : "text"} - placeholder={props.placeholder} - value={props.value} - required={props.isRequired} - className={`${ - props.blurred - ? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400" - : "" - } ${ - props.error - ? "focus:ring-red/50" - : "focus:ring-primary/50" - } relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`} - name={props.name} - spellCheck="false" - /> - {props.label?.includes("Password") && ( - - )} - {props.blurred && ( -
-

- {props.value - .split("") - .slice(0, 54) - .map(() => ( - - ))} -
- )} - {/* {props.error && ( +
+
+ props.onChangeHandler(e.target.value)} + type={passwordVisible == false ? props.type : "text"} + placeholder={props.placeholder} + value={props.value} + required={props.isRequired} + className={`${ + props.blurred + ? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400" + : "" + } ${ + props.error ? "focus:ring-red/50" : "focus:ring-primary/50" + } relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`} + name={props.name} + spellCheck="false" + /> + {props.label?.includes("Password") && ( + + )} + {props.blurred && ( +
+

+ {props.value + .split("") + .slice(0, 54) + .map(() => ( + + ))} +
+ )} + {/* {props.error && (
)} */} -
- {props.error && ( -

- {props.errorText} -

- )} -
- ); - } +
+ {props.error && ( +

+ {props.errorText} +

+ )} +
+ ); + } }; export default React.memo(InputField); diff --git a/frontend/components/basic/Listbox.js b/frontend/components/basic/Listbox.js index eeb444aa5..9949123f4 100644 --- a/frontend/components/basic/Listbox.js +++ b/frontend/components/basic/Listbox.js @@ -1,7 +1,11 @@ import React from "react"; import { Fragment } from "react"; import { useRouter } from "next/router"; -import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { + faAngleDown, + faCheck, + faPlus, +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Listbox, Transition } from "@headlessui/react"; @@ -11,98 +15,97 @@ import { Listbox, Transition } from "@headlessui/react"; * @returns */ export default function ListBox({ - selected, - onChange, - data, - text, - buttonAction, - width, - workspaceMapping = [], + selected, + onChange, + data, + text, + buttonAction, + width, + workspaceMapping = [], }) { - const router = useRouter(); + const router = useRouter(); - return ( - -
- -
- {text} - - {" "} - {selected} - -
- {data && ( -
- -
- )} -
- {data && ( - - - {data.map((person, personIdx) => ( - - `my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${ - selected - ? "bg-white/10 text-gray-400 font-bold" - : "" - } ${ - active & !selected - ? "bg-white/5 text-mineshaft-200 cursor-pointer" - : "text-gray-400" - } ` - } - value={person} - > - {({ selected }) => ( - <> - - {person} - - {selected ? ( - - - - ) : null} - - )} - - ))} - {buttonAction && ( - - )} - - - )} -
-
- ); + return ( + +
+ +
+ {text} + + {" "} + {selected} + +
+ {data && ( +
+ +
+ )} +
+ {data && ( + + + {data.map((person, personIdx) => ( + + `my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${ + selected ? "bg-white/10 text-gray-400 font-bold" : "" + } ${ + active & !selected + ? "bg-white/5 text-mineshaft-200 cursor-pointer" + : "text-gray-400" + } ` + } + value={person} + > + {({ selected }) => ( + <> + + {person} + + {selected ? ( + + + + ) : null} + + )} + + ))} + {buttonAction && ( + + )} + + + )} +
+
+ ); } diff --git a/frontend/components/basic/buttons/Button.js b/frontend/components/basic/buttons/Button.js index c66a9b990..92613788e 100644 --- a/frontend/components/basic/buttons/Button.js +++ b/frontend/components/basic/buttons/Button.js @@ -7,105 +7,105 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; var classNames = require("classnames"); export default function Button({ - text, - onButtonPressed, - link, - loading, - color, - size, - icon, - active = true, - iconDisabled, - textDisabled + text, + onButtonPressed, + link, + loading, + color, + size, + icon, + active = true, + iconDisabled, + textDisabled, }) { - let styleButton = classNames( - "group m-auto md:m-0 inline-block rounded-md duration-200", + let styleButton = classNames( + "group m-auto md:m-0 inline-block rounded-md duration-200", - // Setting background colors and hover modes - color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary", - color == "mineshaft" && !active && "bg-mineshaft", - (color == "primary" || !color) && active && "bg-primary hover:opacity-80", - (color == "primary" || !color) && !active && "bg-primary", - color == "red" && "bg-red", - - // Changing the opacity when active vs when not - active ? "opacity-100 cursor-pointer" : "opacity-40", + // Setting background colors and hover modes + color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary", + color == "mineshaft" && !active && "bg-mineshaft", + (color == "primary" || !color) && active && "bg-primary hover:opacity-80", + (color == "primary" || !color) && !active && "bg-primary", + color == "red" && "bg-red", - // Setting the button sizes - size == "md" && "h-10 w-full px-2 md:px-4", - size == "lg" && "h-12 w-full px-2 md:px-8", - !size && "md:py-1 px-3 md:px-8", - size == "icon-md" && "h-10 w-10 flex items-center justify-center", - size == "icon-sm" && "h-9 w-9 flex items-center justify-center", - ); + // Changing the opacity when active vs when not + active ? "opacity-100 cursor-pointer" : "opacity-40", - let styleMainDiv = classNames( - "relative font-medium flex items-center", + // Setting the button sizes + size == "md" && "h-10 w-full px-2 md:px-4", + size == "lg" && "h-12 w-full px-2 md:px-8", + !size && "md:py-1 px-3 md:px-8", + size == "icon-md" && "h-10 w-10 flex items-center justify-center", + size == "icon-sm" && "h-9 w-9 flex items-center justify-center" + ); - // Setting the text color for the text and icon - color == "mineshaft" && "text-gray-400", - color != "mineshaft" && color != "red" && "text-black", - color == "red" && "text-gray-200", - active && color != "red" ? "group-hover:text-black" : "", + let styleMainDiv = classNames( + "relative font-medium flex items-center", - size == "icon" && "flex items-center justify-center", - ); + // Setting the text color for the text and icon + color == "mineshaft" && "text-gray-400", + color != "mineshaft" && color != "red" && "text-black", + color == "red" && "text-gray-200", + active && color != "red" ? "group-hover:text-black" : "", - let textStyle = classNames( - "relative duration-200", + size == "icon" && "flex items-center justify-center" + ); - // Show the loading sign if the loading indicator is on - loading == true ? "opacity-0" : "opacity-100", - size == "md" && "text-sm", - size == "lg" && "text-lg" - ); + let textStyle = classNames( + "relative duration-200", - const button = ( - - ); + // Show the loading sign if the loading indicator is on + loading == true ? "opacity-0" : "opacity-100", + size == "md" && "text-sm", + size == "lg" && "text-lg" + ); - if (link) { - return {button}; - } + const button = ( + + ); - return button; + if (link) { + return {button}; + } + + return button; } diff --git a/frontend/components/basic/dialog/AddIncidentContactDialog.js b/frontend/components/basic/dialog/AddIncidentContactDialog.js index 4e5004db8..fad13cdee 100644 --- a/frontend/components/basic/dialog/AddIncidentContactDialog.js +++ b/frontend/components/basic/dialog/AddIncidentContactDialog.js @@ -7,91 +7,92 @@ import Button from "../buttons/Button"; import InputField from "../InputField"; const AddIncidentContactDialog = ({ - isOpen, - closeModal, - workspaceId, - incidentContacts, - setIncidentContacts, + isOpen, + closeModal, + workspaceId, + incidentContacts, + setIncidentContacts, }) => { - let [incidentContactEmail, setIncidentContactEmail] = useState(""); + let [incidentContactEmail, setIncidentContactEmail] = useState(""); - const submit = () => { - setIncidentContacts( - incidentContacts?.length > 0 - ? incidentContacts.concat([incidentContactEmail]) - : [incidentContactEmail] - ); - addIncidentContact(localStorage.getItem("orgData.id"), incidentContactEmail); - closeModal(); - }; - return ( -
- - - -
- + const submit = () => { + setIncidentContacts( + incidentContacts?.length > 0 + ? incidentContacts.concat([incidentContactEmail]) + : [incidentContactEmail] + ); + addIncidentContact( + localStorage.getItem("orgData.id"), + incidentContactEmail + ); + closeModal(); + }; + return ( +
+ + + +
+ -
-
- - - - Add an Incident Contact - -
-

- This contact will be notified in the - unlikely event of a severe incident. -

-
-
- -
-
-
-
-
-
-
-
-
-
- ); +
+
+ + + + Add an Incident Contact + +
+

+ This contact will be notified in the unlikely event of a + severe incident. +

+
+
+ +
+
+
+
+
+
+
+
+
+
+ ); }; export default AddIncidentContactDialog; diff --git a/frontend/components/basic/dialog/AddProjectMemberDialog.js b/frontend/components/basic/dialog/AddProjectMemberDialog.js index a170a4d49..0d18bc3ca 100644 --- a/frontend/components/basic/dialog/AddProjectMemberDialog.js +++ b/frontend/components/basic/dialog/AddProjectMemberDialog.js @@ -6,146 +6,133 @@ import Button from "../buttons/Button"; import ListBox from "../Listbox"; const AddProjectMemberDialog = ({ - isOpen, - closeModal, - submitModal, - data, - email, - workspaceId, - setEmail, + isOpen, + closeModal, + submitModal, + data, + email, + workspaceId, + setEmail, }) => { - const router = useRouter(); + const router = useRouter(); - return ( -
- - - -
- + return ( +
+ + + +
+ -
-
- - - {data?.length > 0 ? ( - - Add a member to your project - - ) : ( - - All the users in your organization - are already invited. - - )} -
- {data?.length > 0 ? ( -
-

- The user will receive an email - with the instructions. -

-
- - -
-
- ) : ( -

- Add more users to the - organization first. -

- )} -
-
- {data?.length > 0 && ( - - )} -
-
- {data?.length > 0 ? ( -
-
- ) : ( -
-
-
-
-
-
-
-
- ); +
+
+ + + {data?.length > 0 ? ( + + Add a member to your project + + ) : ( + + All the users in your organization are already invited. + + )} +
+ {data?.length > 0 ? ( +
+

+ The user will receive an email with the instructions. +

+
+ + +
+
+ ) : ( +

+ Add more users to the organization first. +

+ )} +
+
+ {data?.length > 0 && ( + + )} +
+
+ {data?.length > 0 ? ( +
+
+ ) : ( +
+
+
+
+
+
+
+
+ ); }; export default AddProjectMemberDialog; diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js index 8f5419502..f8300358f 100644 --- a/frontend/components/basic/dialog/AddServiceTokenDialog.js +++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js @@ -8,276 +8,254 @@ import nacl from "tweetnacl"; import addServiceToken from "~/pages/api/serviceToken/addServiceToken"; import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; -import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto"; +import { + decryptAssymmetric, + encryptAssymmetric, +} from "../../utilities/cryptography/crypto"; import Button from "../buttons/Button"; import InputField from "../InputField"; import ListBox from "../Listbox"; const envMapping = { - Development: "dev", - Staging: "staging", - Production: "prod", - Testing: "test", + Development: "dev", + Staging: "staging", + Production: "prod", + Testing: "test", }; const expiryMapping = { - "1 day": 86400, - "7 days": 604800, - "1 month": 2592000, + "1 day": 86400, + "7 days": 604800, + "1 month": 2592000, }; const AddServiceTokenDialog = ({ - isOpen, - closeModal, - workspaceId, - workspaceName, + isOpen, + closeModal, + workspaceId, + workspaceName, }) => { - const router = useRouter(); - const [serviceToken, setServiceToken] = useState(""); - const [serviceTokenName, setServiceTokenName] = useState(""); - const [serviceTokenEnv, setServiceTokenEnv] = useState("Development"); - const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day"); - const [serviceTokenCopied, setServiceTokenCopied] = useState(false); + const router = useRouter(); + const [serviceToken, setServiceToken] = useState(""); + const [serviceTokenName, setServiceTokenName] = useState(""); + const [serviceTokenEnv, setServiceTokenEnv] = useState("Development"); + const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day"); + const [serviceTokenCopied, setServiceTokenCopied] = useState(false); - const generateServiceToken = async () => { - const latestFileKey = await getLatestFileKey(workspaceId); + 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"), - }); + const key = decryptAssymmetric({ + ciphertext: latestFileKey.latestKey.encryptedKey, + nonce: latestFileKey.latestKey.nonce, + publicKey: latestFileKey.latestKey.sender.publicKey, + privateKey: localStorage.getItem("PRIVATE_KEY"), + }); - // generate new public/private key pair - const pair = nacl.box.keyPair(); - const publicKey = nacl.util.encodeBase64(pair.publicKey); - const privateKey = nacl.util.encodeBase64(pair.secretKey); + // generate new public/private key pair + const pair = nacl.box.keyPair(); + const publicKey = nacl.util.encodeBase64(pair.publicKey); + const privateKey = nacl.util.encodeBase64(pair.secretKey); - // encrypt workspace key under newly-generated public key - const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({ - plaintext: key, - publicKey, - privateKey, - }); + // encrypt workspace key under newly-generated public key + const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({ + plaintext: key, + publicKey, + privateKey, + }); - let newServiceToken = await addServiceToken({ - name: serviceTokenName, - workspaceId, - environment: envMapping[serviceTokenEnv], - expiresIn: expiryMapping[serviceTokenExpiresIn], - publicKey, - encryptedKey, - nonce, - }); + let newServiceToken = await addServiceToken({ + name: serviceTokenName, + workspaceId, + environment: envMapping[serviceTokenEnv], + expiresIn: expiryMapping[serviceTokenExpiresIn], + publicKey, + encryptedKey, + nonce, + }); - const serviceToken = newServiceToken + "," + privateKey; - setServiceToken(serviceToken); - }; + const serviceToken = newServiceToken + "," + privateKey; + setServiceToken(serviceToken); + }; - function copyToClipboard() { - // Get the text field - var copyText = document.getElementById("serviceToken"); + function copyToClipboard() { + // Get the text field + var copyText = document.getElementById("serviceToken"); - // Select the text field - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices + // 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); + // 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); - } + setServiceTokenCopied(true); + setTimeout(() => setServiceTokenCopied(false), 2000); + // Alert the copied text + // alert("Copied the text: " + copyText.value); + } - const closeAddServiceTokenModal = () => { - closeModal(); - setServiceTokenName(""); - setServiceToken(""); - }; + const closeAddServiceTokenModal = () => { + closeModal(); + setServiceTokenName(""); + setServiceToken(""); + }; - return ( -
- - - -
- + return ( +
+ + + +
+ -
-
- - {serviceToken == "" ? ( - - - Add a service token for{" "} - {workspaceName} - -
-
-

- Specify the name, - environment, and expiry - period. When a token is - generated, you will only be - able to see it once before - it disappears. Make sure to - save it somewhere. -

-
-
-
- -
-
- -
-
- -
-
-
-
-
-
- ) : ( - - - Copy your service token - -
-
-

- Once you close this popup, - you will never see your - service token again -

-
-
-
-
- -
- {serviceToken} -
-
- - - Click to Copy - -
-
-
-
-
-
- )} -
-
-
-
-
-
- ); +
+
+ + {serviceToken == "" ? ( + + + Add a service token for {workspaceName} + +
+
+

+ Specify the name, environment, and expiry period. When + a token is generated, you will only be able to see it + once before it disappears. Make sure to save it + somewhere. +

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ ) : ( + + + Copy your service token + +
+
+

+ Once you close this popup, you will never see your + service token again +

+
+
+
+
+ +
+ {serviceToken} +
+
+ + + Click to Copy + +
+
+
+
+
+
+ )} +
+
+
+
+
+
+ ); }; export default AddServiceTokenDialog; diff --git a/frontend/components/basic/dialog/AddUserDialog.js b/frontend/components/basic/dialog/AddUserDialog.js index 59cc2ec58..a8dc34adb 100644 --- a/frontend/components/basic/dialog/AddUserDialog.js +++ b/frontend/components/basic/dialog/AddUserDialog.js @@ -7,107 +7,103 @@ import Button from "../buttons/Button"; import InputField from "../InputField"; const AddUserDialog = ({ - isOpen, - closeModal, - submitModal, - email, - workspaceId, - setEmail, - currentPlan, - orgName + isOpen, + closeModal, + submitModal, + email, + workspaceId, + setEmail, + currentPlan, + orgName, }) => { - const submit = () => { - submitModal(email); - }; - const router = useRouter(); + const submit = () => { + submitModal(email); + }; + const router = useRouter(); - return ( -
- - - -
- + return ( +
+ + + +
+ -
-
- - - - Invite others to {orgName} - -
-

- An invite is specific to an email address - and expires after 1 day. For security reasons, - you will need to separately add members to projects. -

-
-
- -
- {currentPlan == STRIPE_PRODUCT_STARTER &&
- - -
} -
-
-
- {/* +
+
+ + + + Invite others to {orgName} + +
+

+ An invite is specific to an email address and expires + after 1 day. For security reasons, you will need to + separately add members to projects. +

+
+
+ +
+ {currentPlan == STRIPE_PRODUCT_STARTER && ( +
+ + +
+ )} +
+
+
+ {/*
*/} - -
-
-
-
-
- ); + +
+
+ + + + ); }; export default AddUserDialog; diff --git a/frontend/components/basic/dialog/AddWorkspaceDialog.js b/frontend/components/basic/dialog/AddWorkspaceDialog.js index 361aa3f8b..32d706959 100644 --- a/frontend/components/basic/dialog/AddWorkspaceDialog.js +++ b/frontend/components/basic/dialog/AddWorkspaceDialog.js @@ -12,94 +12,93 @@ import { Checkbox } from "../table/Checkbox"; * @returns */ const AddWorkspaceDialog = ({ - isOpen, - closeModal, - submitModal, - workspaceName, - setWorkspaceName, - error, - loading, + isOpen, + closeModal, + submitModal, + workspaceName, + setWorkspaceName, + error, + loading, }) => { - const [addAllUsers, setAddAllUsers] = useState(true); - const submit = () => { - submitModal(workspaceName, addAllUsers); - }; + const [addAllUsers, setAddAllUsers] = useState(true); + const submit = () => { + submitModal(workspaceName, addAllUsers); + }; - return ( -
- - - -
- + return ( +
+ + + +
+ -
-
- - - - Create a new project - -
-

- This project will contain your - environmental variables. -

-
-
- 0} - errorText={error} - /> -
-
- -
-
-
-
-
-
-
-
-
-
- ); +
+
+ + + + Create a new project + +
+

+ This project will contain your environmental variables. +

+
+
+ 0} + errorText={error} + /> +
+
+ +
+
+
+
+
+
+
+
+
+
+ ); }; export default AddWorkspaceDialog; diff --git a/frontend/components/basic/dialog/DeleteUserDialog.js b/frontend/components/basic/dialog/DeleteUserDialog.js index 2d97b0ff0..437a0c06b 100644 --- a/frontend/components/basic/dialog/DeleteUserDialog.js +++ b/frontend/components/basic/dialog/DeleteUserDialog.js @@ -1,78 +1,83 @@ -import { Fragment, useState } from 'react' -import { Dialog, Transition } from '@headlessui/react' +import { Fragment, useState } from "react"; +import { Dialog, Transition } from "@headlessui/react"; -import InputField from '../InputField'; +import InputField from "../InputField"; // #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state. +const DeleteUserDialog = ({ + isOpen, + closeModal, + submitModal, + userIdToBeDeleted, +}) => { + const submit = () => { + submitModal(userIdToBeDeleted); + }; + return ( +
+ + + +
+ -const DeleteUserDialog = ({isOpen, closeModal, submitModal, userIdToBeDeleted}) => { - const submit = () => { - submitModal(userIdToBeDeleted); - } - return ( -
- - - +
+ -
- - -
-
- - - + + + Are you sure you want to remove this user from the + workspace? + +
+

+ This action is irrevertible. +

+
+
+ - -
-
-
-
+ Delete + + +
+ +
-
-
-
- ); +
+
+
+
+ ); }; -export default DeleteUserDialog; \ No newline at end of file +export default DeleteUserDialog; diff --git a/frontend/components/basic/layout.js b/frontend/components/basic/layout.js index edac0dc9b..a56b0fe17 100644 --- a/frontend/components/basic/layout.js +++ b/frontend/components/basic/layout.js @@ -3,11 +3,11 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { - faGear, - faHouse, - faLink, - faMobile, - faUser, + faGear, + faHouse, + faLink, + faMobile, + faUser, } from "@fortawesome/free-solid-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -20,319 +20,285 @@ import createWorkspace from "~/pages/api/workspace/createWorkspace"; import getWorkspaces from "~/pages/api/workspace/getWorkspaces"; import NavBarDashboard from "../navigation/NavBarDashboard"; -import { decryptAssymmetric, encryptAssymmetric } from "../utilities/cryptography/crypto"; +import { + decryptAssymmetric, + encryptAssymmetric, +} from "../utilities/cryptography/crypto"; import Button from "./buttons/Button"; import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog"; import Listbox from "./Listbox"; export default function Layout({ children }) { - const router = useRouter(); - const [workspaceList, setWorkspaceList] = useState([]); - const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]); - const [workspaceSelected, setWorkspaceSelected] = useState("∞"); - let [newWorkspaceName, setNewWorkspaceName] = useState(""); - let [isOpen, setIsOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(false); + const router = useRouter(); + const [workspaceList, setWorkspaceList] = useState([]); + const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]); + const [workspaceSelected, setWorkspaceSelected] = useState("∞"); + let [newWorkspaceName, setNewWorkspaceName] = useState(""); + let [isOpen, setIsOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); - function closeModal() { - setIsOpen(false); - } + function closeModal() { + setIsOpen(false); + } - // TODO: what to do about the fact that 2ids can have the same name + // TODO: what to do about the fact that 2ids can have the same name - /** - * When a user creates a new workspace, redirect them to the page of the new workspace. - * @param {*} workspaceName - */ - async function submitModal(workspaceName, addAllUsers) { - setLoading(true); - setTimeout(() => setLoading(false), 1500); - const workspaces = await getWorkspaces(); - const currentWorkspaces = workspaces.map((workspace) => workspace.name); - if (!currentWorkspaces.includes(workspaceName)) { - const newWorkspace = await createWorkspace( - workspaceName, - localStorage.getItem("orgData.id") - ); - let newWorkspaceId; - try { - newWorkspaceId = newWorkspace._id; - } catch (error) { - console.log(error); - } - if (addAllUsers) { - let orgUsers = await getOrganizationUsers({ - orgId: localStorage.getItem("orgData.id"), - }); - orgUsers.map(async (user) => { - if (user.status == "accepted") { - let result = await addUserToWorkspace( - user.user.email, - newWorkspaceId - ); - if (result?.invitee && result?.latestKey) { - const PRIVATE_KEY = - localStorage.getItem("PRIVATE_KEY"); + /** + * When a user creates a new workspace, redirect them to the page of the new workspace. + * @param {*} workspaceName + */ + async function submitModal(workspaceName, addAllUsers) { + setLoading(true); + setTimeout(() => setLoading(false), 1500); + const workspaces = await getWorkspaces(); + const currentWorkspaces = workspaces.map((workspace) => workspace.name); + if (!currentWorkspaces.includes(workspaceName)) { + const newWorkspace = await createWorkspace( + workspaceName, + localStorage.getItem("orgData.id") + ); + let newWorkspaceId; + try { + newWorkspaceId = newWorkspace._id; + } catch (error) { + console.log(error); + } + if (addAllUsers) { + let orgUsers = await getOrganizationUsers({ + orgId: localStorage.getItem("orgData.id"), + }); + orgUsers.map(async (user) => { + if (user.status == "accepted") { + let result = await addUserToWorkspace( + user.user.email, + newWorkspaceId + ); + if (result?.invitee && result?.latestKey) { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - // 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, - }); + // 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: result.invitee.publicKey, - privateKey: PRIVATE_KEY, - }); + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: key, + publicKey: result.invitee.publicKey, + privateKey: PRIVATE_KEY, + }); - uploadKeys( - newWorkspaceId, - result.invitee._id, - ciphertext, - nonce - ); - } - } - }); - } - router.push("/dashboard/" + newWorkspaceId + "?Development"); - setIsOpen(false); - setNewWorkspaceName(""); - } else { - setError("A project with this name already exists."); - setLoading(false); - } - } + uploadKeys(newWorkspaceId, result.invitee._id, ciphertext, nonce); + } + } + }); + } + router.push("/dashboard/" + newWorkspaceId + "?Development"); + setIsOpen(false); + setNewWorkspaceName(""); + } else { + setError("A project with this name already exists."); + setLoading(false); + } + } - function openModal() { - setIsOpen(true); - } + function openModal() { + setIsOpen(true); + } - const menuItems = [ - { - href: - "/dashboard/" + - workspaceMapping[workspaceSelected] + - "?Development", - title: "Secrets", - emoji: , - }, - { - href: "/users/" + workspaceMapping[workspaceSelected], - title: "Members", - emoji: , - }, - { - href: "/integrations/" + workspaceMapping[workspaceSelected], - title: "Integrations", - emoji: , - }, - { - href: "/settings/project/" + workspaceMapping[workspaceSelected], - title: "Project Settings", - emoji: , - }, - ]; + const menuItems = [ + { + href: + "/dashboard/" + workspaceMapping[workspaceSelected] + "?Development", + title: "Secrets", + emoji: , + }, + { + href: "/users/" + workspaceMapping[workspaceSelected], + title: "Members", + emoji: , + }, + { + href: "/integrations/" + workspaceMapping[workspaceSelected], + title: "Integrations", + emoji: , + }, + { + href: "/settings/project/" + workspaceMapping[workspaceSelected], + title: "Project Settings", + emoji: , + }, + ]; - useEffect(async () => { - // Put a user in a workspace if they're not in one yet - if ( - localStorage.getItem("orgData.id") == null || - localStorage.getItem("orgData.id") == "" - ) { - const userOrgs = await getOrganizations(); - localStorage.setItem("orgData.id", userOrgs[0]._id); - } + useEffect(async () => { + // Put a user in a workspace if they're not in one yet + if ( + localStorage.getItem("orgData.id") == null || + localStorage.getItem("orgData.id") == "" + ) { + const userOrgs = await getOrganizations(); + localStorage.setItem("orgData.id", userOrgs[0]._id); + } - let orgUserProjects = await getOrganizationUserProjects({ - orgId: localStorage.getItem("orgData.id"), - }); - let userWorkspaces = orgUserProjects; - if ( - userWorkspaces.length == 0 && - router.asPath != "/noprojects" && - !router.asPath.includes("settings") - ) { - router.push("/noprojects"); - } else if (router.asPath != "/noprojects") { - const intendedWorkspaceId = router.asPath - .split("/") - [router.asPath.split("/").length - 1].split("?")[0]; + let orgUserProjects = await getOrganizationUserProjects({ + orgId: localStorage.getItem("orgData.id"), + }); + let userWorkspaces = orgUserProjects; + if ( + userWorkspaces.length == 0 && + router.asPath != "/noprojects" && + !router.asPath.includes("settings") + ) { + router.push("/noprojects"); + } else if (router.asPath != "/noprojects") { + const intendedWorkspaceId = router.asPath + .split("/") + [router.asPath.split("/").length - 1].split("?")[0]; - // If a user is not a member of a workspace they are trying to access, just push them to one of theirs - if ( - intendedWorkspaceId != "heroku" && - !userWorkspaces - .map((workspace) => workspace._id) - .includes(intendedWorkspaceId) - ) { - router.push( - "/dashboard/" + userWorkspaces[0]._id + "?Development" - ); - } else { - setWorkspaceList( - userWorkspaces.map((workspace) => workspace.name) - ); - setWorkspaceMapping( - Object.fromEntries( - userWorkspaces.map((workspace) => [ - workspace.name, - workspace._id, - ]) - ) - ); - setWorkspaceSelected( - Object.fromEntries( - userWorkspaces.map((workspace) => [ - workspace._id, - workspace.name, - ]) - )[ - router.asPath - .split("/") - [router.asPath.split("/").length - 1].split("?")[0] - ] - ); - } - } - }, []); + // If a user is not a member of a workspace they are trying to access, just push them to one of theirs + if ( + intendedWorkspaceId != "heroku" && + !userWorkspaces + .map((workspace) => workspace._id) + .includes(intendedWorkspaceId) + ) { + router.push("/dashboard/" + userWorkspaces[0]._id + "?Development"); + } else { + setWorkspaceList(userWorkspaces.map((workspace) => workspace.name)); + setWorkspaceMapping( + Object.fromEntries( + userWorkspaces.map((workspace) => [workspace.name, workspace._id]) + ) + ); + setWorkspaceSelected( + Object.fromEntries( + userWorkspaces.map((workspace) => [workspace._id, workspace.name]) + )[ + router.asPath + .split("/") + [router.asPath.split("/").length - 1].split("?")[0] + ] + ); + } + } + }, []); - useEffect(() => { - try { - if ( - workspaceMapping[workspaceSelected] && - workspaceMapping[workspaceSelected] !== - router.asPath - .split("/") - [router.asPath.split("/").length - 1].split("?")[0] - ) { - router.push( - "/dashboard/" + - workspaceMapping[workspaceSelected] + - "?Development" - ); - localStorage.setItem( - "projectData.id", - workspaceMapping[workspaceSelected] - ); - } - } catch (error) { - console.log(error); - } - }, [workspaceSelected]); + useEffect(() => { + try { + if ( + workspaceMapping[workspaceSelected] && + workspaceMapping[workspaceSelected] !== + router.asPath + .split("/") + [router.asPath.split("/").length - 1].split("?")[0] + ) { + router.push( + "/dashboard/" + workspaceMapping[workspaceSelected] + "?Development" + ); + localStorage.setItem( + "projectData.id", + workspaceMapping[workspaceSelected] + ); + } + } catch (error) { + console.log(error); + } + }, [workspaceSelected]); - return ( - <> -
- -
- - -
{children}
-
-
-
- -

- {" "} - To use Infisical, please log in through a device with larger - dimensions.{" "} -

-
- - ); + return ( + <> +
+ +
+ + +
{children}
+
+
+
+ +

+ {" "} + To use Infisical, please log in through a device with larger + dimensions.{" "} +

+
+ + ); } diff --git a/frontend/components/basic/popups/BottomRightPopup.js b/frontend/components/basic/popups/BottomRightPopup.js index d75699290..adfefb0ee 100644 --- a/frontend/components/basic/popups/BottomRightPopup.js +++ b/frontend/components/basic/popups/BottomRightPopup.js @@ -3,49 +3,49 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function BottonRightPopup({ - buttonText, - buttonLink, - titleText, - emoji, - textLine1, - textLine2, - setCheckDocsPopUpVisible, + buttonText, + buttonLink, + titleText, + emoji, + textLine1, + textLine2, + setCheckDocsPopUpVisible, }) { - return ( - - ); + return ( + + ); } diff --git a/frontend/components/basic/table/Checkbox.js b/frontend/components/basic/table/Checkbox.js index 6aa9a53e7..2ef6c2de8 100644 --- a/frontend/components/basic/table/Checkbox.js +++ b/frontend/components/basic/table/Checkbox.js @@ -1,28 +1,28 @@ import React from "react"; export const Checkbox = ({ addAllUsers, setAddAllUsers }) => { - return ( - <> -
- {addAllUsers == true ? ( - setAddAllUsers(!addAllUsers)} - /> - ) : ( -
setAddAllUsers(!addAllUsers)} - >
- )} + return ( + <> +
+ {addAllUsers == true ? ( + setAddAllUsers(!addAllUsers)} + /> + ) : ( +
setAddAllUsers(!addAllUsers)} + >
+ )} - -
- - ); + +
+ + ); }; diff --git a/frontend/components/basic/table/ServiceTokenTable.js b/frontend/components/basic/table/ServiceTokenTable.js index acf4aa576..8018d3e1c 100644 --- a/frontend/components/basic/table/ServiceTokenTable.js +++ b/frontend/components/basic/table/ServiceTokenTable.js @@ -8,11 +8,11 @@ import Button from "../buttons/Button"; const roles = ["admin", "user"]; const reverseEnvMapping = { - "dev": "Development", - "staging": "Staging", - "prod": "Production", - "test": "Testing" -} + dev: "Development", + staging: "Staging", + prod: "Production", + test: "Testing", +}; /** * This is the component that we utilize for the user table - in future, can reuse it for some other purposes too. @@ -20,63 +20,66 @@ const reverseEnvMapping = { * @param {*} props * @returns */ -const ServiceTokenTable = ({ - data, - workspaceName -}) => { - const router = useRouter(); +const ServiceTokenTable = ({ data, workspaceName }) => { + const router = useRouter(); - return ( -
-
- - - - - - - - - - - - {data?.length > 0 - ? data - .map((row, index) => { - return ( - - - - - - - - ); - }) - : - - } - -
Token nameProjectEnvironmentValid until
- {row.name} - - {workspaceName} - - {reverseEnvMapping[row.environment]} - - {new Date(row.expiresAt).toUTCString()} - -
-
-
No service tokens yet
-
- ); + return ( +
+
+ + + + + + + + + + + + {data?.length > 0 ? ( + data.map((row, index) => { + return ( + + + + + + + + ); + }) + ) : ( + + + + )} + +
Token nameProjectEnvironmentValid until
+ {row.name} + + {workspaceName} + + {reverseEnvMapping[row.environment]} + + {new Date(row.expiresAt).toUTCString()} + +
+
+
+ No service tokens yet +
+
+ ); }; export default ServiceTokenTable; diff --git a/frontend/components/basic/table/UserTable.js b/frontend/components/basic/table/UserTable.js index 4d364b4a5..3f5468288 100644 --- a/frontend/components/basic/table/UserTable.js +++ b/frontend/components/basic/table/UserTable.js @@ -13,8 +13,8 @@ import Button from "../buttons/Button"; import Listbox from "../Listbox"; const { - decryptAssymmetric, - encryptAssymmetric, + decryptAssymmetric, + encryptAssymmetric, } = require("../../utilities/cryptography/crypto"); const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); @@ -28,245 +28,214 @@ const roles = ["admin", "user"]; * @returns */ const UserTable = ({ - userData, - changeData, - myUser, - filter, - resendInvite, - isOrg, - onClick, - deleteUser, - setUserIdToBeDeleted, + userData, + changeData, + myUser, + filter, + resendInvite, + isOrg, + onClick, + deleteUser, + setUserIdToBeDeleted, }) => { - const [roleSelected, setRoleSelected] = useState( - Array(userData?.length).fill(userData.map((user) => user.role)) - ); - const router = useRouter(); - const [myRole, setMyRole] = useState("member"); + const [roleSelected, setRoleSelected] = useState( + Array(userData?.length).fill(userData.map((user) => user.role)) + ); + const router = useRouter(); + const [myRole, setMyRole] = useState("member"); - // 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, index, e) => { - // 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), - ]); - }; + // 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, index, e) => { + // 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 rold of a certain user - const handleRoleUpdate = (index, e) => { - changeUserRoleInWorkspace(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), - ]); - }; + // Update the rold of a certain user + const handleRoleUpdate = (index, e) => { + changeUserRoleInWorkspace(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); - }, [userData, myUser]); + useEffect(() => { + setMyRole(userData.filter((user) => user.email == myUser)[0]?.role); + }, [userData, myUser]); - const grantAccess = async (id, publicKey) => { - let result = await getLatestFileKey(router.query.id); + const grantAccess = async (id, publicKey) => { + let result = await getLatestFileKey(router.query.id); - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - // 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, - }); + // 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: publicKey, - privateKey: PRIVATE_KEY, - }); + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: key, + publicKey: publicKey, + privateKey: PRIVATE_KEY, + }); - uploadKeys(router.query.id, id, ciphertext, nonce); - router.reload(); - }; + uploadKeys(router.query.id, id, ciphertext, nonce); + router.reload(); + }; - const deleteMembershipAndResendInvite = (email, membershipId) => { - // deleteUserFromWorkspace(membershipId); - resendInvite(email); - }; + const deleteMembershipAndResendInvite = (email, membershipId) => { + // deleteUserFromWorkspace(membershipId); + resendInvite(email); + }; - return ( -
-
- - - - - - - - - - - {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) => { - return ( - - - - - - - ); - })} - -
First NameLast NameEmail
- {row.firstName} - - {row.lastName} - - {row.email} - -
- {row.status == "granted" && - ((myRole == "admin" && - row.role != "owner") || - myRole == "owner") && - myUser !== row.email ? ( - - handleRoleUpdate( - index, - e - ) - } - data={ - myRole == "owner" - ? [ - "owner", - "admin", - "member", - ] - : [ - "admin", - "member", - ] - } - text="Role: " - membershipId={ - row.membershipId - } - /> - ) : ( - row.status != "invited" && - row.status != - "verified" && ( - - ) - )} - {(row.status == "invited" || - row.status == - "verified") && ( -
-
- )} - {row.status == "completed" && - myUser !== row.email && ( -
-
- )} -
- {myUser !== row.email && - // row.role != "admin" && - myRole != "member" ? ( -
-
- ) : ( -
- )} -
-
- ); + return ( +
+
+ + + + + + + + + + + {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) => { + return ( + + + + + + + ); + })} + +
First NameLast NameEmail
+ {row.firstName} + + {row.lastName} + + {row.email} + +
+ {row.status == "granted" && + ((myRole == "admin" && row.role != "owner") || + myRole == "owner") && + myUser !== row.email ? ( + handleRoleUpdate(index, e)} + data={ + myRole == "owner" + ? ["owner", "admin", "member"] + : ["admin", "member"] + } + text="Role: " + membershipId={row.membershipId} + /> + ) : ( + row.status != "invited" && + row.status != "verified" && ( + + ) + )} + {(row.status == "invited" || + row.status == "verified") && ( +
+
+ )} + {row.status == "completed" && myUser !== row.email && ( +
+
+ )} +
+ {myUser !== row.email && + // row.role != "admin" && + myRole != "member" ? ( +
+
+ ) : ( +
+ )} +
+
+ ); }; export default UserTable; diff --git a/frontend/components/billing/Plan.js b/frontend/components/billing/Plan.js index 2dcf3bc1c..f156cf76d 100644 --- a/frontend/components/billing/Plan.js +++ b/frontend/components/billing/Plan.js @@ -3,91 +3,82 @@ import React from "react"; import StripeRedirect from "~/pages/api/organization/StripeRedirect"; export default function Plan({ plan }) { - return ( -
-
-
-

- {plan.name} -

-
-
-

- {plan.price} -

-

- {plan.priceExplanation} -

-
-

- {plan.text} -

-

- {plan.subtext} -

-
-
- {plan.current == false ? ( - <> - {plan.buttonTextMain == "Schedule a Demo" ? ( - -
- {plan.buttonTextMain} -
-
- ) : ( -
- -
- )} - -
- {plan.buttonTextSecondary} -
-
- - ) : ( -
-

- CURRENT PLAN -

-
- )} -
-
- ); + > +
+
+

+ {plan.name} +

+
+
+

+ {plan.price} +

+

+ {plan.priceExplanation} +

+
+

+ {plan.text} +

+

+ {plan.subtext} +

+
+
+ {plan.current == false ? ( + <> + {plan.buttonTextMain == "Schedule a Demo" ? ( + +
+ {plan.buttonTextMain} +
+
+ ) : ( +
+ +
+ )} + +
+ {plan.buttonTextSecondary} +
+
+ + ) : ( +
+

CURRENT PLAN

+
+ )} +
+ + ); } diff --git a/frontend/components/dashboard/DashboardInputField.js b/frontend/components/dashboard/DashboardInputField.js index 4a81054d5..53733c38b 100644 --- a/frontend/components/dashboard/DashboardInputField.js +++ b/frontend/components/dashboard/DashboardInputField.js @@ -10,17 +10,17 @@ import guidGenerator from "../utilities/randomId"; * @returns */ const findReferences = (text) => { - var splitText = text.split("${"); - let textArray = [splitText[0]]; - for (var i = 1; i < splitText.length; i++) { - let insideBrackets = "${" + splitText[i].split("}")[0]; - if (splitText[i].includes("}")) { - insideBrackets += "}"; - } - textArray.push(insideBrackets); - textArray.push(splitText[i].split("}")[1]); - } - return textArray; + var splitText = text.split("${"); + let textArray = [splitText[0]]; + for (var i = 1; i < splitText.length; i++) { + let insideBrackets = "${" + splitText[i].split("}")[0]; + if (splitText[i].includes("}")) { + insideBrackets += "}"; + } + textArray.push(insideBrackets); + textArray.push(splitText[i].split("}")[1]); + } + return textArray; }; /** @@ -34,110 +34,98 @@ const findReferences = (text) => { * @returns */ const DashboardInputField = ({ - index, - onChangeHandler, - type, - value, - blurred, + index, + onChangeHandler, + type, + value, + blurred, }) => { - if (type === "varName") { - return ( -
-
- onChangeHandler(e.target.value, index)} - type={type} - value={value} - className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200" - spellCheck="false" - /> -
-
- ); - } else if (type === "value") { - return ( -
-
- onChangeHandler(e.target.value, index)} - type={type} - value={value} - className={`${ - blurred - ? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent" - : "" - } asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`} - spellCheck="false" - /> -
- {findReferences(value).map((texts, id) => { - if (id % 2 == 0 || texts.length <= 2) { - return ( - - {texts} - - ); - } - return ( - - {texts.slice(0, 2)} - - {texts.slice(2, texts.length - 1)} - - {texts.slice( - texts.length - 1, - texts.length - ) == "}" ? ( - - {texts.slice( - texts.length - 1, - texts.length - )}{" "} - - ) : ( - - {texts.slice( - texts.length - 1, - texts.length - )}{" "} - - )} - - ); - })} -
- {blurred && ( -
-

- {value - .split("") - .slice(0, 42) - .map(() => ( - - ))} -
- )} -
-
- ); - } + if (type === "varName") { + return ( +
+
+ onChangeHandler(e.target.value, index)} + type={type} + value={value} + className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200" + spellCheck="false" + /> +
+
+ ); + } else if (type === "value") { + return ( +
+
+ onChangeHandler(e.target.value, index)} + type={type} + value={value} + className={`${ + blurred + ? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent" + : "" + } asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`} + spellCheck="false" + /> +
+ {findReferences(value).map((texts, id) => { + if (id % 2 == 0 || texts.length <= 2) { + return ( + + {texts} + + ); + } + return ( + + {texts.slice(0, 2)} + + {texts.slice(2, texts.length - 1)} + + {texts.slice(texts.length - 1, texts.length) == "}" ? ( + + {texts.slice(texts.length - 1, texts.length)}{" "} + + ) : ( + + {texts.slice(texts.length - 1, texts.length)}{" "} + + )} + + ); + })} +
+ {blurred && ( +
+

+ {value + .split("") + .slice(0, 42) + .map(() => ( + + ))} +
+ )} +
+
+ ); + } }; export default React.memo(DashboardInputField); diff --git a/frontend/components/dashboard/DropZone.js b/frontend/components/dashboard/DropZone.js index 762371c4b..6171e682f 100644 --- a/frontend/components/dashboard/DropZone.js +++ b/frontend/components/dashboard/DropZone.js @@ -9,178 +9,177 @@ import parse from "../utilities/file"; import guidGenerator from "../utilities/randomId"; const DropZone = ({ - setData, - setErrorDragAndDrop, - createNewFile, - errorDragAndDrop, - addPresetRow, - setButtonReady, - keysExist, - numCurrentRows + setData, + setErrorDragAndDrop, + createNewFile, + errorDragAndDrop, + addPresetRow, + setButtonReady, + keysExist, + numCurrentRows, }) => { - const handleDragEnter = (e) => { - e.preventDefault(); - e.stopPropagation(); - }; + const handleDragEnter = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; - const handleDragLeave = (e) => { - e.preventDefault(); - e.stopPropagation(); - }; + const handleDragLeave = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; - const handleDragOver = (e) => { - e.preventDefault(); - e.stopPropagation(); + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); - // set dropEffect to copy i.e copy of the source item - e.dataTransfer.dropEffect = "copy"; - }; + // set dropEffect to copy i.e copy of the source item + e.dataTransfer.dropEffect = "copy"; + }; - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - // This function function immediately parses the file after it is dropped - const handleDrop = async (e) => { - setLoading(true); - setTimeout(() => setLoading(false), 5000); - e.preventDefault(); - e.stopPropagation(); - e.dataTransfer.dropEffect = "copy"; + // This function function immediately parses the file after it is dropped + const handleDrop = async (e) => { + setLoading(true); + setTimeout(() => setLoading(false), 5000); + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = "copy"; - var file = e.dataTransfer.files[0], - reader = new FileReader(); - reader.onload = function (event) { - const keyPairs = parse(event.target.result) - const newData = Object.keys(keyPairs) - .map((key, index) => [ - guidGenerator(), - numCurrentRows + index, - key, - keyPairs[key], - "shared", - ]); - setData(newData); - setButtonReady(true); - }; + var file = e.dataTransfer.files[0], + reader = new FileReader(); + reader.onload = function (event) { + const keyPairs = parse(event.target.result); + const newData = Object.keys(keyPairs).map((key, index) => [ + guidGenerator(), + numCurrentRows + index, + key, + keyPairs[key], + "shared", + ]); + setData(newData); + setButtonReady(true); + }; - // If something is wrong show an error - try { - reader.readAsText(file); - setLoading(false); - } catch (error) { - setErrorDragAndDrop(true); - setLoading(false); - } - }; + // If something is wrong show an error + try { + reader.readAsText(file); + setLoading(false); + } catch (error) { + setErrorDragAndDrop(true); + setLoading(false); + } + }; - // This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop) - const handleFileSelect = (e) => { - setLoading(true); - setTimeout(() => setLoading(false), 5000); - var file = e.target.files[0], - reader = new FileReader(); - reader.onload = function (event) { - const newData = event.target.result - .split("\n") - .map((line, index) => [ - guidGenerator(), - numCurrentRows + index, - line.split("=")[0], - line.split("=").slice(1, line.split("=").length).join("="), - "shared", - ]); - setData(newData); - setButtonReady(true); - }; - reader.readAsText(file); - }; + // This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop) + const handleFileSelect = (e) => { + setLoading(true); + setTimeout(() => setLoading(false), 5000); + var file = e.target.files[0], + reader = new FileReader(); + reader.onload = function (event) { + const newData = event.target.result + .split("\n") + .map((line, index) => [ + guidGenerator(), + numCurrentRows + index, + line.split("=")[0], + line.split("=").slice(1, line.split("=").length).join("="), + "shared", + ]); + setData(newData); + setButtonReady(true); + }; + reader.readAsText(file); + }; - return loading ? ( -
- google logo -
- ) : keysExist ? ( -
handleDragEnter(e)} - onDragOver={(e) => handleDragOver(e)} - onDragLeave={(e) => handleDragLeave(e)} - onDrop={(e) => handleDrop(e)} - > - handleFileSelect(e)} - /> - {errorDragAndDrop ? ( -
- ) : ( -
- )} -
- -

- Drag and drop your .env file here to add more keys. -

-
- {errorDragAndDrop ? ( -
- -
- ) : ( - <> - )} -
- ) : ( -
handleDragEnter(e)} - onDragOver={(e) => handleDragOver(e)} - onDragLeave={(e) => handleDragLeave(e)} - onDrop={(e) => handleDrop(e)} - > - -

Drag and drop your .env file here.

- handleFileSelect(e)} - /> -
-
-

OR

-
-
-
-
- {errorDragAndDrop ? ( -
- -
- ) : ( -
- {/*

If you are expecting to see a file here, contact your administrator for permission.

*/} -
- )} -
- ); + return loading ? ( +
+ google logo +
+ ) : keysExist ? ( +
handleDragEnter(e)} + onDragOver={(e) => handleDragOver(e)} + onDragLeave={(e) => handleDragLeave(e)} + onDrop={(e) => handleDrop(e)} + > + handleFileSelect(e)} + /> + {errorDragAndDrop ? ( +
+ ) : ( +
+ )} +
+ +

+ Drag and drop your .env file here to add more keys. +

+
+ {errorDragAndDrop ? ( +
+ +
+ ) : ( + <> + )} +
+ ) : ( +
handleDragEnter(e)} + onDragOver={(e) => handleDragOver(e)} + onDragLeave={(e) => handleDragLeave(e)} + onDrop={(e) => handleDrop(e)} + > + +

Drag and drop your .env file here.

+ handleFileSelect(e)} + /> +
+
+

OR

+
+
+
+
+ {errorDragAndDrop ? ( +
+ +
+ ) : ( +
+ {/*

If you are expecting to see a file here, contact your administrator for permission.

*/} +
+ )} +
+ ); }; export default DropZone; diff --git a/frontend/components/navigation/NavBarDashboard.js b/frontend/components/navigation/NavBarDashboard.js index d4f0c704a..80537dc1a 100644 --- a/frontend/components/navigation/NavBarDashboard.js +++ b/frontend/components/navigation/NavBarDashboard.js @@ -1,18 +1,18 @@ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react/jsx-key */ -import React, { Fragment, useEffect,useState } from "react"; +import React, { Fragment, useEffect, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; -import { faGithub,faSlack } from "@fortawesome/free-brands-svg-icons"; +import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons"; import { - faAngleDown, - faBook, - faCoins, - faEnvelope, - faGear, - faPlus, - faRightFromBracket, + faAngleDown, + faBook, + faCoins, + faEnvelope, + faGear, + faPlus, + faRightFromBracket, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Menu, Transition } from "@headlessui/react"; @@ -25,301 +25,269 @@ import getUser from "~/pages/api/user/getUser"; import guidGenerator from "../utilities/randomId"; const supportOptions = [ - [ - , - "Join Slack Forum", - "https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g", - ], - [ - , - "Read Docs", - "https://infisical.com/docs/getting-started/introduction", - ], - [ - , - "Open a GitHub Issue", - "https://github.com/Infisical/infisical-cli/issues", - ], - [ - , - "Send us an Email", - "mailto:support@infisical.com", - ], + [ + , + "Join Slack Forum", + "https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g", + ], + [ + , + "Read Docs", + "https://infisical.com/docs/getting-started/introduction", + ], + [ + , + "Open a GitHub Issue", + "https://github.com/Infisical/infisical-cli/issues", + ], + [ + , + "Send us an Email", + "mailto:support@infisical.com", + ], ]; export default function Navbar({ onButtonPressed }) { - const router = useRouter(); - const [user, setUser] = useState({}); - const [orgs, setOrgs] = useState([]); - const [currentOrg, setCurrentOrg] = useState([]); + const router = useRouter(); + const [user, setUser] = useState({}); + const [orgs, setOrgs] = useState([]); + const [currentOrg, setCurrentOrg] = useState([]); - useEffect(async () => { - const userData = await getUser(); - setUser(userData); - const orgsData = await getOrganizations(); - setOrgs(orgsData); - const currentOrg = await getOrganization({ - orgId: localStorage.getItem("orgData.id"), - }); - setCurrentOrg(currentOrg); - }, []); + useEffect(async () => { + const userData = await getUser(); + setUser(userData); + const orgsData = await getOrganizations(); + setOrgs(orgsData); + const currentOrg = await getOrganization({ + orgId: localStorage.getItem("orgData.id"), + }); + setCurrentOrg(currentOrg); + }, []); - const closeApp = async () => { - console.log("Logging out..."); - await logout(); - router.push("/"); - }; + const closeApp = async () => { + console.log("Logging out..."); + await logout(); + router.push("/"); + }; - return ( -
-
-
-
- logo -
- - Infisical - -
-
-
- -
- - - -
- - - {supportOptions.map((option) => ( - // eslint-disable-next-line react/jsx-no-target-blank - -
- {option[0]} -
- {option[1]} -
-
-
- ))} -
-
-
- -
- - {user?.firstName} {user?.lastName} - - -
- - -
-
- SIGNED IN AS -
-
- router.push( - "/settings/personal/" + - router.query.id - ) - } - className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md" - > -
- {user?.firstName?.charAt(0)} -
-
-
-

- {" "} - {user?.firstName}{" "} - {user?.lastName} -

-

- {" "} - {user?.email} -

-
- -
-
-
-
-
- CURRENT ORGANIZATION -
-
- router.push( - "/settings/org/" + router.query.id - ) - } - className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md" - > -
- {currentOrg?.name?.charAt(0)} -
-
-

- {currentOrg?.name} -

- -
-
- - -
- {orgs?.length > 1 && ( -
-
- OTHER ORGANIZATIONS -
-
- {orgs - .filter( - (org) => - org._id != - localStorage.getItem( - "orgData.id" - ) - ) - .map((org) => ( -
{ - localStorage.setItem( - "orgData.id", - org._id - ); - router.reload(); - }} - className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md" - > -
- {org.name.charAt(0)} -
-
-

- {org.name} -

-
-
- ))} -
-
- )} -
- - {({ active }) => ( - - )} - -
-
-
-
-
-
- ); + return ( +
+
+
+
+ logo +
+ Infisical +
+
+
+ +
+ + + +
+ + + {supportOptions.map((option) => ( + // eslint-disable-next-line react/jsx-no-target-blank + +
+ {option[0]} +
{option[1]}
+
+
+ ))} +
+
+
+ +
+ + {user?.firstName} {user?.lastName} + + +
+ + +
+
+ SIGNED IN AS +
+
+ router.push("/settings/personal/" + router.query.id) + } + className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md" + > +
+ {user?.firstName?.charAt(0)} +
+
+
+

+ {" "} + {user?.firstName} {user?.lastName} +

+

+ {" "} + {user?.email} +

+
+ +
+
+
+
+
+ CURRENT ORGANIZATION +
+
+ router.push("/settings/org/" + router.query.id) + } + className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md" + > +
+ {currentOrg?.name?.charAt(0)} +
+
+

+ {currentOrg?.name} +

+ +
+
+ + +
+ {orgs?.length > 1 && ( +
+
+ OTHER ORGANIZATIONS +
+
+ {orgs + .filter( + (org) => org._id != localStorage.getItem("orgData.id") + ) + .map((org) => ( +
{ + localStorage.setItem("orgData.id", org._id); + router.reload(); + }} + className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md" + > +
+ {org.name.charAt(0)} +
+
+

+ {org.name} +

+
+
+ ))} +
+
+ )} +
+ + {({ active }) => ( + + )} + +
+
+
+
+
+
+ ); } diff --git a/frontend/components/navigation/NavHeader.js b/frontend/components/navigation/NavHeader.js index 58527aa56..3f65a5aa6 100644 --- a/frontend/components/navigation/NavHeader.js +++ b/frontend/components/navigation/NavHeader.js @@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons"; import { - faAngleRight, - faQuestionCircle, + faAngleRight, + faQuestionCircle, } from "@fortawesome/free-solid-svg-icons"; import { faCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -12,46 +12,46 @@ import getOrganization from "~/pages/api/organization/GetOrg"; import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo"; export default function NavHeader({ pageName, isProjectRelated }) { - const [orgName, setOrgName] = useState(""); - const [workspaceName, setWorkspaceName] = useState(""); - const router = useRouter(); + const [orgName, setOrgName] = useState(""); + const [workspaceName, setWorkspaceName] = useState(""); + const router = useRouter(); - useEffect(() => { - (async () => { - let org = await getOrganization({ - orgId: localStorage.getItem("orgData.id"), - }); - setOrgName(org.name); - let workspace = await getWorkspaceInfo({ - workspaceId: router.query.id, - }); - setWorkspaceName(workspace.name); - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(() => { + (async () => { + let org = await getOrganization({ + orgId: localStorage.getItem("orgData.id"), + }); + setOrgName(org.name); + let workspace = await getWorkspaceInfo({ + workspaceId: router.query.id, + }); + setWorkspaceName(workspace.name); + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return ( -
-
- {orgName?.charAt(0)} -
-
{orgName}
- {isProjectRelated && ( - <> - -
- {workspaceName} -
- - )} - -
{pageName}
-
- ); + return ( +
+
+ {orgName?.charAt(0)} +
+
{orgName}
+ {isProjectRelated && ( + <> + +
+ {workspaceName} +
+ + )} + +
{pageName}
+
+ ); } diff --git a/frontend/components/utilities/SecurityClient.js b/frontend/components/utilities/SecurityClient.js index 587b43517..645ecbb67 100644 --- a/frontend/components/utilities/SecurityClient.js +++ b/frontend/components/utilities/SecurityClient.js @@ -1,6 +1,5 @@ -import token from "~/pages/api/auth/Token"; - import { PATH } from "~/const"; +import token from "~/pages/api/auth/Token"; export default class SecurityClient { static authOrigins = [PATH]; diff --git a/frontend/components/utilities/attemptLogin.js b/frontend/components/utilities/attemptLogin.js index 53c56789b..4e74a40ab 100644 --- a/frontend/components/utilities/attemptLogin.js +++ b/frontend/components/utilities/attemptLogin.js @@ -5,8 +5,8 @@ import getOrganizations from "~/pages/api/organization/getOrgs"; import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects"; import { initPostHog } from "../analytics/posthog"; -import { ENV } from "./config"; import pushKeys from "./secrets/pushKeys"; +import { ENV } from "./config"; import SecurityClient from "./SecurityClient"; const nacl = require("tweetnacl"); @@ -24,184 +24,158 @@ const client = new jsrp.client(); * @returns */ const attemptLogin = async ( - email, - password, - setErrorLogin, - router, - isSignUp, - isLogin + email, + password, + setErrorLogin, + router, + isSignUp, + isLogin ) => { - try { - let userWorkspace, userOrg; - client.init( - { - username: email, - password: password, - }, - async () => { - const clientPublicKey = client.getPublicKey(); + try { + let userWorkspace, userOrg; + client.init( + { + username: email, + password: password, + }, + async () => { + const clientPublicKey = client.getPublicKey(); - let serverPublicKey, salt; - try { - const res = await login1(email, clientPublicKey); - res = await res.json(); - serverPublicKey = res.serverPublicKey; - salt = res.salt; - } catch (err) { - setErrorLogin(true); - console.log("Wrong password", err); - } + let serverPublicKey, salt; + try { + const res = await login1(email, clientPublicKey); + res = await res.json(); + serverPublicKey = res.serverPublicKey; + salt = res.salt; + } catch (err) { + setErrorLogin(true); + console.log("Wrong password", err); + } - let response; - try { - client.setSalt(salt); - client.setServerPublicKey(serverPublicKey); - const clientProof = client.getProof(); // called M1 - response = await login2(email, clientProof); - } catch (err) { - setErrorLogin(true); - console.log("Password verification failed"); - } + let response; + try { + client.setSalt(salt); + client.setServerPublicKey(serverPublicKey); + const clientProof = client.getProof(); // called M1 + response = await login2(email, clientProof); + } catch (err) { + setErrorLogin(true); + console.log("Password verification failed"); + } - // if everything works, go the main dashboard page. - try { - if (response.status == "200") { - response = await response.json(); - SecurityClient.setToken(response["token"]); - const publicKey = response["publicKey"]; - const encryptedPrivateKey = - response["encryptedPrivateKey"]; - const iv = response["iv"]; - const tag = response["tag"]; + // if everything works, go the main dashboard page. + try { + if (response.status == "200") { + response = await response.json(); + SecurityClient.setToken(response["token"]); + const publicKey = response["publicKey"]; + const encryptedPrivateKey = response["encryptedPrivateKey"]; + const iv = response["iv"]; + const tag = response["tag"]; - const PRIVATE_KEY = Aes256Gcm.decrypt( - encryptedPrivateKey, - iv, - tag, - password - .slice(0, 32) - .padStart( - 32 + - (password.slice(0, 32).length - - new Blob([password]).size), - "0" - ) - ); + const PRIVATE_KEY = Aes256Gcm.decrypt( + encryptedPrivateKey, + iv, + tag, + password + .slice(0, 32) + .padStart( + 32 + + (password.slice(0, 32).length - new Blob([password]).size), + "0" + ) + ); - try { - localStorage.setItem("publicKey", publicKey); - localStorage.setItem( - "encryptedPrivateKey", - encryptedPrivateKey - ); - localStorage.setItem("iv", iv); - localStorage.setItem("tag", tag); - localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); - } catch (err) { - setErrorLogin(true); - console.error( - "Unable to send the tokens in local storage:" + - err.message - ); - } - } else { - setErrorLogin(true); - } + try { + localStorage.setItem("publicKey", publicKey); + localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey); + localStorage.setItem("iv", iv); + localStorage.setItem("tag", tag); + localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); + } catch (err) { + setErrorLogin(true); + console.error( + "Unable to send the tokens in local storage:" + err.message + ); + } + } else { + setErrorLogin(true); + } - const userOrgs = await getOrganizations(); - const userOrgsData = userOrgs.map((org) => org._id); + const userOrgs = await getOrganizations(); + const userOrgsData = userOrgs.map((org) => org._id); - let orgToLogin; - if ( - userOrgsData.includes( - localStorage.getItem("orgData.id") - ) - ) { - orgToLogin = localStorage.getItem("orgData.id"); - } else { - orgToLogin = userOrgsData[0]; - localStorage.setItem("orgData.id", orgToLogin); - } + let orgToLogin; + if (userOrgsData.includes(localStorage.getItem("orgData.id"))) { + orgToLogin = localStorage.getItem("orgData.id"); + } else { + orgToLogin = userOrgsData[0]; + localStorage.setItem("orgData.id", orgToLogin); + } - let orgUserProjects = await getOrganizationUserProjects({ - orgId: orgToLogin, - }); + let orgUserProjects = await getOrganizationUserProjects({ + orgId: orgToLogin, + }); - orgUserProjects = orgUserProjects?.map( - (project) => project._id - ); - let projectToLogin; - if ( - orgUserProjects.includes( - localStorage.getItem("projectData.id") - ) - ) { - projectToLogin = localStorage.getItem("projectData.id"); - } else { - try { - projectToLogin = orgUserProjects[0]; - localStorage.setItem( - "projectData.id", - projectToLogin - ); - } catch (error) { - console.log( - "ERROR: User likely has no projects. ", - error - ); - } - } + orgUserProjects = orgUserProjects?.map((project) => project._id); + let projectToLogin; + if ( + orgUserProjects.includes(localStorage.getItem("projectData.id")) + ) { + projectToLogin = localStorage.getItem("projectData.id"); + } else { + try { + projectToLogin = orgUserProjects[0]; + localStorage.setItem("projectData.id", projectToLogin); + } catch (error) { + console.log("ERROR: User likely has no projects. ", error); + } + } - // If user is logging in for the first time, add the example keys - if (isSignUp) { - await pushKeys( - { - DATABASE_URL: [ - "mongodb+srv://this_is:an_example@mongodb.net", - "personal", - ], - TWILIO_AUTH_TOKEN: [ - "hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG", - "shared", - ], - WEBSITE_URL: [ - "http://localhost:3000", - "shared", - ], - STRIPE_SECRET_KEY: [ - "sk_test_7348oyho4hfq398HIUOH78", - "shared", - ], - }, - projectToLogin, - "Development" - ); - } - try { - if (email) { - if (ENV == "production") { - const posthog = initPostHog(); - posthog.identify(email); - posthog.capture("User Logged In"); - } - } - } catch (error) { - console.log("posthog", error); - } + // If user is logging in for the first time, add the example keys + if (isSignUp) { + await pushKeys( + { + DATABASE_URL: [ + "mongodb+srv://this_is:an_example@mongodb.net", + "personal", + ], + TWILIO_AUTH_TOKEN: [ + "hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG", + "shared", + ], + WEBSITE_URL: ["http://localhost:3000", "shared"], + STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"], + }, + projectToLogin, + "Development" + ); + } + try { + if (email) { + if (ENV == "production") { + const posthog = initPostHog(); + posthog.identify(email); + posthog.capture("User Logged In"); + } + } + } catch (error) { + console.log("posthog", error); + } - if (isLogin) { - router.push("/dashboard/"); - } - } catch (error) { - setErrorLogin(true); - console.log("Login response not available"); - } - } - ); - } catch (error) { - console.log("Something went wrong during authentication"); - } - return true; + if (isLogin) { + router.push("/dashboard/"); + } + } catch (error) { + setErrorLogin(true); + console.log("Login response not available"); + } + } + ); + } catch (error) { + console.log("Something went wrong during authentication"); + } + return true; }; export default attemptLogin; diff --git a/frontend/components/utilities/checks/PasswordCheck.js b/frontend/components/utilities/checks/PasswordCheck.js index 8c5d02dac..448c2942e 100644 --- a/frontend/components/utilities/checks/PasswordCheck.js +++ b/frontend/components/utilities/checks/PasswordCheck.js @@ -7,59 +7,59 @@ * @returns */ const passwordCheck = ( - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - currentErrorCheck + password, + setPasswordErrorLength, + setPasswordErrorNumber, + setPasswordErrorLowerCase, + currentErrorCheck ) => { - let errorCheck = currentErrorCheck; - if (!password || password.length < 14) { - setPasswordErrorLength(true); - errorCheck = true; - } else { - setPasswordErrorLength(false); - } - - if (!/\d/.test(password)) { - setPasswordErrorNumber(true); - errorCheck = true; - } else { - setPasswordErrorNumber(false); - } - - if (!/[a-z]/.test(password)) { - setPasswordErrorLowerCase(true); - errorCheck = true; - // } else if (/(.)(?:(?!\1).){1,2}/.test(password)) { - // console.log(111) - // setPasswordError(true); - // setPasswordErrorMessage("Password should not contain repeating characters."); - // errorCheck = true; - // } else if (RegExp(`[${email}]`).test(password)) { - // console.log(222) - // setPasswordError(true); - // setPasswordErrorMessage("Password should not contain your email."); - // errorCheck = true; - } else { - setPasswordErrorLowerCase(false); - } - - // if (!/[A-Z]/.test(password)) { - // setPasswordErrorUpperCase(true); - // errorCheck = true; - // } else { - // setPasswordErrorUpperCase(false); - // } - - // if (!/(?=.*[!@#$%^&*])/.test(password)) { - // setPasswordErrorSpecialChar(true); - // // "Please add at least 1 special character (*, !, #, %)." - // errorCheck = true; - // } else { - // setPasswordErrorSpecialChar(false); - // } - return errorCheck; + let errorCheck = currentErrorCheck; + if (!password || password.length < 14) { + setPasswordErrorLength(true); + errorCheck = true; + } else { + setPasswordErrorLength(false); + } + + if (!/\d/.test(password)) { + setPasswordErrorNumber(true); + errorCheck = true; + } else { + setPasswordErrorNumber(false); + } + + if (!/[a-z]/.test(password)) { + setPasswordErrorLowerCase(true); + errorCheck = true; + // } else if (/(.)(?:(?!\1).){1,2}/.test(password)) { + // console.log(111) + // setPasswordError(true); + // setPasswordErrorMessage("Password should not contain repeating characters."); + // errorCheck = true; + // } else if (RegExp(`[${email}]`).test(password)) { + // console.log(222) + // setPasswordError(true); + // setPasswordErrorMessage("Password should not contain your email."); + // errorCheck = true; + } else { + setPasswordErrorLowerCase(false); + } + + // if (!/[A-Z]/.test(password)) { + // setPasswordErrorUpperCase(true); + // errorCheck = true; + // } else { + // setPasswordErrorUpperCase(false); + // } + + // if (!/(?=.*[!@#$%^&*])/.test(password)) { + // setPasswordErrorSpecialChar(true); + // // "Please add at least 1 special character (*, !, #, %)." + // errorCheck = true; + // } else { + // setPasswordErrorSpecialChar(false); + // } + return errorCheck; }; export default passwordCheck; diff --git a/frontend/components/utilities/config/index.ts b/frontend/components/utilities/config/index.ts index 95c53db4b..62549725c 100644 --- a/frontend/components/utilities/config/index.ts +++ b/frontend/components/utilities/config/index.ts @@ -1,15 +1,17 @@ -const ENV = process.env.NEXT_PUBLIC_ENV! || 'development'; // investigate +const ENV = process.env.NEXT_PUBLIC_ENV! || "development"; // investigate const POSTHOG_API_KEY = process.env.NEXT_PUBLIC_POSTHOG_API_KEY!; -const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST! || 'https://app.posthog.com'; +const POSTHOG_HOST = + process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com"; const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!; const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!; -const TELEMETRY_ENABLED = (process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== 'false'); +const TELEMETRY_ENABLED = + process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== "false"; export { - ENV, - POSTHOG_API_KEY, - POSTHOG_HOST, - STRIPE_PRODUCT_PRO, - STRIPE_PRODUCT_STARTER, - TELEMETRY_ENABLED + ENV, + POSTHOG_API_KEY, + POSTHOG_HOST, + STRIPE_PRODUCT_PRO, + STRIPE_PRODUCT_STARTER, + TELEMETRY_ENABLED, }; diff --git a/frontend/components/utilities/cryptography/aes-256-gcm.js b/frontend/components/utilities/cryptography/aes-256-gcm.js index e5c447fd5..0616813df 100644 --- a/frontend/components/utilities/cryptography/aes-256-gcm.js +++ b/frontend/components/utilities/cryptography/aes-256-gcm.js @@ -13,51 +13,51 @@ const BLOCK_SIZE_BYTES = 16; // 128 bit * Provides easy encryption/decryption methods using AES 256 GCM. */ class Aes256Gcm { - /** - * No need to run the constructor. The class only has static methods. - */ - constructor() {} + /** + * No need to run the constructor. The class only has static methods. + */ + constructor() {} - /** - * Encrypts text with AES 256 GCM. - * @param {string} text - Cleartext to encode. - * @param {string} secret - Shared secret key, must be 32 bytes. - * @returns {object} - */ - static encrypt(text, secret) { - const iv = crypto.randomBytes(BLOCK_SIZE_BYTES); - const cipher = crypto.createCipheriv(ALGORITHM, secret, iv); + /** + * Encrypts text with AES 256 GCM. + * @param {string} text - Cleartext to encode. + * @param {string} secret - Shared secret key, must be 32 bytes. + * @returns {object} + */ + static encrypt(text, secret) { + const iv = crypto.randomBytes(BLOCK_SIZE_BYTES); + const cipher = crypto.createCipheriv(ALGORITHM, secret, iv); - let ciphertext = cipher.update(text, "utf8", "base64"); - ciphertext += cipher.final("base64"); - return { - ciphertext, - iv: iv.toString("base64"), - tag: cipher.getAuthTag().toString("base64"), - }; - } + let ciphertext = cipher.update(text, "utf8", "base64"); + ciphertext += cipher.final("base64"); + return { + ciphertext, + iv: iv.toString("base64"), + tag: cipher.getAuthTag().toString("base64"), + }; + } - /** - * Decrypts AES 256 CGM encrypted text. - * @param {string} ciphertext - Base64-encoded ciphertext. - * @param {string} iv - The base64-encoded initialization vector. - * @param {string} tag - The base64-encoded authentication tag generated by getAuthTag(). - * @param {string} secret - Shared secret key, must be 32 bytes. - * @returns {string} - */ - static decrypt(ciphertext, iv, tag, secret) { - const decipher = crypto.createDecipheriv( - ALGORITHM, - secret, - Buffer.from(iv, "base64") - ); - decipher.setAuthTag(Buffer.from(tag, "base64")); + /** + * Decrypts AES 256 CGM encrypted text. + * @param {string} ciphertext - Base64-encoded ciphertext. + * @param {string} iv - The base64-encoded initialization vector. + * @param {string} tag - The base64-encoded authentication tag generated by getAuthTag(). + * @param {string} secret - Shared secret key, must be 32 bytes. + * @returns {string} + */ + static decrypt(ciphertext, iv, tag, secret) { + const decipher = crypto.createDecipheriv( + ALGORITHM, + secret, + Buffer.from(iv, "base64") + ); + decipher.setAuthTag(Buffer.from(tag, "base64")); - let cleartext = decipher.update(ciphertext, "base64", "utf8"); - cleartext += decipher.final("utf8"); + let cleartext = decipher.update(ciphertext, "base64", "utf8"); + cleartext += decipher.final("utf8"); - return cleartext; - } + return cleartext; + } } module.exports = Aes256Gcm; diff --git a/frontend/components/utilities/cryptography/changePassword.js b/frontend/components/utilities/cryptography/changePassword.js index 1951d5885..8bfaad674 100644 --- a/frontend/components/utilities/cryptography/changePassword.js +++ b/frontend/components/utilities/cryptography/changePassword.js @@ -19,109 +19,102 @@ const clientNewPassword = new jsrp.client(); * @returns */ const changePassword = async ( - email, - currentPassword, - newPassword, - setCurrentPasswordError, - setPasswordChanged, - setCurrentPassword, - setNewPassword + email, + currentPassword, + newPassword, + setCurrentPasswordError, + setPasswordChanged, + setCurrentPassword, + setNewPassword ) => { - try { - setPasswordChanged(false); - setCurrentPasswordError(false); + try { + setPasswordChanged(false); + setCurrentPasswordError(false); - clientOldPassword.init( - { - username: email, - password: currentPassword, - }, - async () => { - const clientPublicKey = clientOldPassword.getPublicKey(); + clientOldPassword.init( + { + username: email, + password: currentPassword, + }, + async () => { + const clientPublicKey = clientOldPassword.getPublicKey(); - let serverPublicKey, salt; - try { - const res = await SRP1({ - clientPublicKey: clientPublicKey, - }); - serverPublicKey = res.serverPublicKey; - salt = res.salt; - } catch (err) { - setCurrentPasswordError(true); - console.log("Wrong current password", err, 1); - } + let serverPublicKey, salt; + try { + const res = await SRP1({ + clientPublicKey: clientPublicKey, + }); + serverPublicKey = res.serverPublicKey; + salt = res.salt; + } catch (err) { + setCurrentPasswordError(true); + console.log("Wrong current password", err, 1); + } - clientOldPassword.setSalt(salt); - clientOldPassword.setServerPublicKey(serverPublicKey); - const clientProof = clientOldPassword.getProof(); // called M1 + clientOldPassword.setSalt(salt); + clientOldPassword.setServerPublicKey(serverPublicKey); + const clientProof = clientOldPassword.getProof(); // called M1 - clientNewPassword.init( - { - username: email, - password: newPassword, - }, - async () => { - clientNewPassword.createVerifier( - async (err, result) => { - // The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø) - let { ciphertext, iv, tag } = Aes256Gcm.encrypt( - localStorage.getItem("PRIVATE_KEY"), - newPassword - .slice(0, 32) - .padStart( - 32 + - (newPassword.slice(0, 32) - .length - - new Blob([newPassword]) - .size), - "0" - ) - ); + clientNewPassword.init( + { + username: email, + password: newPassword, + }, + async () => { + clientNewPassword.createVerifier(async (err, result) => { + // The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø) + let { ciphertext, iv, tag } = Aes256Gcm.encrypt( + localStorage.getItem("PRIVATE_KEY"), + newPassword + .slice(0, 32) + .padStart( + 32 + + (newPassword.slice(0, 32).length - + new Blob([newPassword]).size), + "0" + ) + ); - if (ciphertext) { - localStorage.setItem( - "encryptedPrivateKey", - ciphertext - ); - localStorage.setItem("iv", iv); - localStorage.setItem("tag", tag); + if (ciphertext) { + localStorage.setItem("encryptedPrivateKey", ciphertext); + localStorage.setItem("iv", iv); + localStorage.setItem("tag", tag); - let res; - try { - res = await changePassword2({ - encryptedPrivateKey: ciphertext, - iv, - tag, - salt: result.salt, - verifier: result.verifier, - clientProof, - }); - if (res.status == 400) { - setCurrentPasswordError(true); - } else if (res.status == 200) { - setPasswordChanged(true); - setCurrentPassword(""); - setNewPassword(""); - } - } catch (err) { - setCurrentPasswordError(true); - console.log(err); - } - } - } - ); - } - ); - } - ); - } catch (error) { - console.log( - "Something went wrong during changing the password", - slat, - serverPublicKey - ); - } - return true; + let res; + try { + res = await changePassword2({ + encryptedPrivateKey: ciphertext, + iv, + tag, + salt: result.salt, + verifier: result.verifier, + clientProof, + }); + if (res.status == 400) { + setCurrentPasswordError(true); + } else if (res.status == 200) { + setPasswordChanged(true); + setCurrentPassword(""); + setNewPassword(""); + } + } catch (err) { + setCurrentPasswordError(true); + console.log(err); + } + } + }); + } + ); + } + ); + } catch (error) { + console.log( + "Something went wrong during changing the password", + slat, + serverPublicKey + ); + } + return true; }; export default changePassword; diff --git a/frontend/components/utilities/cryptography/crypto.js b/frontend/components/utilities/cryptography/crypto.js index f7c0388fa..842911174 100644 --- a/frontend/components/utilities/cryptography/crypto.js +++ b/frontend/components/utilities/cryptography/crypto.js @@ -14,18 +14,18 @@ const aes = require("./aes-256-gcm"); * @returns {String} nonce - base64-encoded nonce */ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => { - const nonce = nacl.randomBytes(24); - const ciphertext = nacl.box( - nacl.util.decodeUTF8(plaintext), - nonce, - nacl.util.decodeBase64(publicKey), - nacl.util.decodeBase64(privateKey) - ); + const nonce = nacl.randomBytes(24); + const ciphertext = nacl.box( + nacl.util.decodeUTF8(plaintext), + nonce, + nacl.util.decodeBase64(publicKey), + nacl.util.decodeBase64(privateKey) + ); - return { - ciphertext: nacl.util.encodeBase64(ciphertext), - nonce: nacl.util.encodeBase64(nonce), - }; + return { + ciphertext: nacl.util.encodeBase64(ciphertext), + nonce: nacl.util.encodeBase64(nonce), + }; }; /** @@ -39,14 +39,14 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => { * @param {String} plaintext - UTF8 plaintext */ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => { - const plaintext = nacl.box.open( - nacl.util.decodeBase64(ciphertext), - nacl.util.decodeBase64(nonce), - nacl.util.decodeBase64(publicKey), - nacl.util.decodeBase64(privateKey) - ); + const plaintext = nacl.box.open( + nacl.util.decodeBase64(ciphertext), + nacl.util.decodeBase64(nonce), + nacl.util.decodeBase64(publicKey), + nacl.util.decodeBase64(privateKey) + ); - return nacl.util.encodeUTF8(plaintext); + return nacl.util.encodeUTF8(plaintext); }; /** @@ -56,23 +56,23 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => { * @param {String} obj.key - 16-byte hex key */ const encryptSymmetric = ({ plaintext, key }) => { - let ciphertext, iv, tag; - try { - const obj = aes.encrypt(plaintext, key); - ciphertext = obj.ciphertext; - iv = obj.iv; - tag = obj.tag; - } catch (err) { - console.log("Failed to perform encryption"); - console.log(err); - process.exit(1); - } + let ciphertext, iv, tag; + try { + const obj = aes.encrypt(plaintext, key); + ciphertext = obj.ciphertext; + iv = obj.iv; + tag = obj.tag; + } catch (err) { + console.log("Failed to perform encryption"); + console.log(err); + process.exit(1); + } - return { - ciphertext, - iv, - tag, - }; + return { + ciphertext, + iv, + tag, + }; }; /** @@ -86,20 +86,20 @@ const encryptSymmetric = ({ plaintext, key }) => { * */ const decryptSymmetric = ({ ciphertext, iv, tag, key }) => { - let plaintext; - try { - plaintext = aes.decrypt(ciphertext, iv, tag, key); - } catch (err) { - console.log("Failed to perform decryption"); - process.exit(1); - } + let plaintext; + try { + plaintext = aes.decrypt(ciphertext, iv, tag, key); + } catch (err) { + console.log("Failed to perform decryption"); + process.exit(1); + } - return plaintext; + return plaintext; }; module.exports = { - encryptAssymmetric, - decryptAssymmetric, - encryptSymmetric, - decryptSymmetric, + encryptAssymmetric, + decryptAssymmetric, + encryptSymmetric, + decryptSymmetric, }; diff --git a/frontend/components/utilities/cryptography/issueBackupKey.js b/frontend/components/utilities/cryptography/issueBackupKey.js index 570c8bc1a..19d7ebefa 100644 --- a/frontend/components/utilities/cryptography/issueBackupKey.js +++ b/frontend/components/utilities/cryptography/issueBackupKey.js @@ -1,8 +1,8 @@ import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey"; import SRP1 from "~/pages/api/auth/SRP1"; -import Aes256Gcm from "./aes-256-gcm"; import generateBackupPDF from "../generateBackupPDF"; +import Aes256Gcm from "./aes-256-gcm"; const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); @@ -21,82 +21,78 @@ const crypto = require("crypto"); * @returns */ const issueBackupKey = async ({ - email, - password, - personalName, - setBackupKeyError, - setBackupKeyIssued, + email, + password, + personalName, + setBackupKeyError, + setBackupKeyIssued, }) => { - try { - setBackupKeyError(false); - setBackupKeyIssued(false); - clientPassword.init( - { - username: email, - password: password, - }, - async () => { - const clientPublicKey = clientPassword.getPublicKey(); + try { + setBackupKeyError(false); + setBackupKeyIssued(false); + clientPassword.init( + { + username: email, + password: password, + }, + async () => { + const clientPublicKey = clientPassword.getPublicKey(); - let serverPublicKey, salt; - try { - const res = await SRP1({ - clientPublicKey: clientPublicKey, - }); - serverPublicKey = res.serverPublicKey; - salt = res.salt; - } catch (err) { - setBackupKeyError(true); - console.log("Wrong current password", err, 1); - } + let serverPublicKey, salt; + try { + const res = await SRP1({ + clientPublicKey: clientPublicKey, + }); + serverPublicKey = res.serverPublicKey; + salt = res.salt; + } catch (err) { + setBackupKeyError(true); + console.log("Wrong current password", err, 1); + } - clientPassword.setSalt(salt); - clientPassword.setServerPublicKey(serverPublicKey); - const clientProof = clientPassword.getProof(); // called M1 + clientPassword.setSalt(salt); + clientPassword.setServerPublicKey(serverPublicKey); + const clientProof = clientPassword.getProof(); // called M1 - const generatedKey = crypto.randomBytes(16).toString("hex"); + const generatedKey = crypto.randomBytes(16).toString("hex"); - clientKey.init( - { - username: email, - password: generatedKey, - }, - async () => { - clientKey.createVerifier(async (err, result) => { - let { ciphertext, iv, tag } = Aes256Gcm.encrypt( - localStorage.getItem("PRIVATE_KEY"), - generatedKey - ); + clientKey.init( + { + username: email, + password: generatedKey, + }, + async () => { + clientKey.createVerifier(async (err, result) => { + let { ciphertext, iv, tag } = Aes256Gcm.encrypt( + localStorage.getItem("PRIVATE_KEY"), + generatedKey + ); - const res = await issueBackupPrivateKey({ - encryptedPrivateKey: ciphertext, - iv, - tag, - salt: result.salt, - verifier: result.verifier, - clientProof, - }); + const res = await issueBackupPrivateKey({ + encryptedPrivateKey: ciphertext, + iv, + tag, + salt: result.salt, + verifier: result.verifier, + clientProof, + }); - if (res.status == 400) { - setBackupKeyError(true); - } else if (res.status == 200) { - generateBackupPDF( - personalName, - email, - generatedKey - ); - setBackupKeyIssued(true); - } - }); - } - ); - } - ); - } catch (error) { - setBackupKeyError(true); - console.log("Failed to issue a backup key"); - } - return true; + if (res.status == 400) { + setBackupKeyError(true); + } else if (res.status == 200) { + generateBackupPDF(personalName, email, generatedKey); + setBackupKeyIssued(true); + } + }); + } + ); + } + ); + } catch (error) { + setBackupKeyError(true); + console.log("Failed to issue a backup key"); + } + return true; }; export default issueBackupKey; diff --git a/frontend/components/utilities/file.js b/frontend/components/utilities/file.js index c15f54a4a..96d1820eb 100644 --- a/frontend/components/utilities/file.js +++ b/frontend/components/utilities/file.js @@ -1,5 +1,5 @@ const LINE = - /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm; + /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm; /** * Return text that is the buffer parsed @@ -7,41 +7,41 @@ const LINE = * @returns {String} text - text of buffer */ function parse(src) { - const obj = {}; + const obj = {}; - // Convert buffer to string - let lines = src.toString(); + // Convert buffer to string + let lines = src.toString(); - // Convert line breaks to same format - lines = lines.replace(/\r\n?/gm, "\n"); + // Convert line breaks to same format + lines = lines.replace(/\r\n?/gm, "\n"); - let match; - while ((match = LINE.exec(lines)) != null) { - const key = match[1]; + let match; + while ((match = LINE.exec(lines)) != null) { + const key = match[1]; - // Default undefined or null to empty string - let value = match[2] || ""; + // Default undefined or null to empty string + let value = match[2] || ""; - // Remove whitespace - value = value.trim(); + // Remove whitespace + value = value.trim(); - // Check if double quoted - const maybeQuote = value[0]; + // Check if double quoted + const maybeQuote = value[0]; - // Remove surrounding quotes - value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2"); + // Remove surrounding quotes + value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2"); - // Expand newlines if double quoted - if (maybeQuote === '"') { - value = value.replace(/\\n/g, "\n"); - value = value.replace(/\\r/g, "\r"); - } + // Expand newlines if double quoted + if (maybeQuote === '"') { + value = value.replace(/\\n/g, "\n"); + value = value.replace(/\\r/g, "\r"); + } - // Add to object - obj[key] = value; - } + // Add to object + obj[key] = value; + } - return obj; + return obj; } export default parse; diff --git a/frontend/components/utilities/generateBackupPDF.js b/frontend/components/utilities/generateBackupPDF.js index 788d80ad5..58e0b6667 100644 --- a/frontend/components/utilities/generateBackupPDF.js +++ b/frontend/components/utilities/generateBackupPDF.js @@ -4,132 +4,83 @@ import { jsPDF } from "jspdf"; * This function generate a pdf with a secret key for a user. */ function generateBackupPDF(personalName, personalEmail, generatedKey) { - const imgData = - "data:image/png;base64,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"; - - // new jsPDF('p', 'mm', [297, 210]); - var today = new Date(); - var dd = String(today.getDate()).padStart(2, '0'); - var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0! - var yyyy = today.getFullYear(); + const imgData = + "data:image/png;base64,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"; - today = mm + '/' + dd + '/' + yyyy; + // new jsPDF('p', 'mm', [297, 210]); + var today = new Date(); + var dd = String(today.getDate()).padStart(2, "0"); + var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0! + var yyyy = today.getFullYear(); - var doc = new jsPDF("p", "pt"); - doc.setFillColor(13, 17, 23); - doc.rect(0, 0, 600, 900, "F"); - doc.setTextColor(227, 227, 227); - doc.addImage(imgData, "png", 30, 35, 535, 72); - doc.addFont("helvetica", "normal"); - doc.setFontSize(12); - doc.text( - 290, - 130, - "Created for " + personalName + " on " + today + ".", - "center" - ); - doc.setFontSize(14); - doc.text( - 32, - 180, - "In case you get locked out of you Infisical account, you`ll need these account details" - ); - doc.text( - 32, - 200, - "to sign in —" - ); - doc.setFont(undefined, "bold"); - doc.text( - 110, - 200, - "including your Secret Key, which we absolutely cannot access or" - ); - doc.text( - 32, - 220, - "recover for you. " - ); - doc.setFont(undefined, "normal"); - doc.text( - 32, - 250, - "Recommendations:" - ); - doc.text( - 32, - 280, - "1. We recommend to get your Emergency Kit off your computer and print a copy." - ); - doc.text( - 32, - 310, - "2. Store it somewhere safe (such as with your birth certificate, your will, or on your" - ); - doc.text( - 32, - 330, - "personal cloud storage)." - ); - doc.setFillColor(206, 217, 111); - doc.roundedRect(32, 350, 530, 190, 5, 5, 'F'); - doc.setDrawColor(228, 255, 0) - doc.setLineWidth(1) - doc.roundedRect(32, 350, 530, 190, 5, 5, 'S'); - doc.setTextColor(43, 43, 43); - doc.setFont(undefined, "bold"); - doc.setFontSize(15); - doc.text( - 290, - 375, - "Infisical Account Details", - "center" - ); - doc.setFont(undefined, "normal"); - doc.setFontSize(12); - doc.text( - 50, - 420, - "SIGN-IN URL" - ); - doc.text( - 50, - 465, - "EMAIL ADDRESS" - ); - doc.text( - 50, - 510, - "SECRET KEY" - ); - doc.setFillColor(23, 27, 33); - doc.roundedRect(170, 398, 375, 35, 5, 5, 'F'); - doc.roundedRect(170, 443, 375, 35, 5, 5, 'F'); - doc.roundedRect(170, 488, 375, 35, 5, 5, 'F'); - doc.setTextColor(227, 227, 227); - doc.setFontSize(14); - doc.text( - 180, - 420, - "https://app.infisical.com/login" - ); - doc.text( - 180, - 465, - personalEmail - ); - doc.text( - 180, - 510, - generatedKey - ); - doc.text( - 32, - 575, - "Need help? Contact us at support@infisical.com" - ); - - doc.save("Infisical Emergency Kit.pdf"); -}; + today = mm + "/" + dd + "/" + yyyy; + + var doc = new jsPDF("p", "pt"); + doc.setFillColor(13, 17, 23); + doc.rect(0, 0, 600, 900, "F"); + doc.setTextColor(227, 227, 227); + doc.addImage(imgData, "png", 30, 35, 535, 72); + doc.addFont("helvetica", "normal"); + doc.setFontSize(12); + doc.text( + 290, + 130, + "Created for " + personalName + " on " + today + ".", + "center" + ); + doc.setFontSize(14); + doc.text( + 32, + 180, + "In case you get locked out of you Infisical account, you`ll need these account details" + ); + doc.text(32, 200, "to sign in —"); + doc.setFont(undefined, "bold"); + doc.text( + 110, + 200, + "including your Secret Key, which we absolutely cannot access or" + ); + doc.text(32, 220, "recover for you. "); + doc.setFont(undefined, "normal"); + doc.text(32, 250, "Recommendations:"); + doc.text( + 32, + 280, + "1. We recommend to get your Emergency Kit off your computer and print a copy." + ); + doc.text( + 32, + 310, + "2. Store it somewhere safe (such as with your birth certificate, your will, or on your" + ); + doc.text(32, 330, "personal cloud storage)."); + doc.setFillColor(206, 217, 111); + doc.roundedRect(32, 350, 530, 190, 5, 5, "F"); + doc.setDrawColor(228, 255, 0); + doc.setLineWidth(1); + doc.roundedRect(32, 350, 530, 190, 5, 5, "S"); + doc.setTextColor(43, 43, 43); + doc.setFont(undefined, "bold"); + doc.setFontSize(15); + doc.text(290, 375, "Infisical Account Details", "center"); + doc.setFont(undefined, "normal"); + doc.setFontSize(12); + doc.text(50, 420, "SIGN-IN URL"); + doc.text(50, 465, "EMAIL ADDRESS"); + doc.text(50, 510, "SECRET KEY"); + doc.setFillColor(23, 27, 33); + doc.roundedRect(170, 398, 375, 35, 5, 5, "F"); + doc.roundedRect(170, 443, 375, 35, 5, 5, "F"); + doc.roundedRect(170, 488, 375, 35, 5, 5, "F"); + doc.setTextColor(227, 227, 227); + doc.setFontSize(14); + doc.text(180, 420, "https://app.infisical.com/login"); + doc.text(180, 465, personalEmail); + doc.text(180, 510, generatedKey); + doc.text(32, 575, "Need help? Contact us at support@infisical.com"); + + doc.save("Infisical Emergency Kit.pdf"); +} export default generateBackupPDF; diff --git a/frontend/components/utilities/randomId.js b/frontend/components/utilities/randomId.js index 3f8c8817d..7d1c0859b 100644 --- a/frontend/components/utilities/randomId.js +++ b/frontend/components/utilities/randomId.js @@ -3,23 +3,23 @@ * @returns */ const guidGenerator = () => { - var S4 = function () { - return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); - }; - return ( - S4() + - S4() + - "-" + - S4() + - "-" + - S4() + - "-" + - S4() + - "-" + - S4() + - S4() + - S4() - ); + var S4 = function () { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + }; + return ( + S4() + + S4() + + "-" + + S4() + + "-" + + S4() + + "-" + + S4() + + "-" + + S4() + + S4() + + S4() + ); }; export default guidGenerator; diff --git a/frontend/components/utilities/secrets/getSecretsForProject.js b/frontend/components/utilities/secrets/getSecretsForProject.js index 4087e72d0..ba502ccfd 100644 --- a/frontend/components/utilities/secrets/getSecretsForProject.js +++ b/frontend/components/utilities/secrets/getSecretsForProject.js @@ -3,103 +3,101 @@ import getSecrets from "~/pages/api/files/GetSecrets"; import guidGenerator from "../randomId"; const { - decryptAssymmetric, - decryptSymmetric, + decryptAssymmetric, + decryptSymmetric, } = require("../cryptography/crypto"); const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); const envMapping = { - Development: "dev", - Staging: "staging", - Production: "prod", - Testing: "test", + Development: "dev", + Staging: "staging", + Production: "prod", + Testing: "test", }; const getSecretsForProject = async ({ - env, - setFileState, - setIsKeyAvailable, - setData, - workspaceId, + env, + setFileState, + setIsKeyAvailable, + setData, + workspaceId, }) => { - try { - let file; - try { - file = await getSecrets(workspaceId, envMapping[env]); + try { + let file; + try { + file = await getSecrets(workspaceId, envMapping[env]); - setFileState(file); - } catch (error) { - console.log("ERROR: Not able to access the latest file"); - } - // This is called isKeyAvilable but what it really means is if a person is able to create new key pairs - setIsKeyAvailable( - !file.key ? (file.secrets.length == 0 ? true : false) : true - ); + setFileState(file); + } catch (error) { + console.log("ERROR: Not able to access the latest file"); + } + // This is called isKeyAvilable but what it really means is if a person is able to create new key pairs + setIsKeyAvailable( + !file.key ? (file.secrets.length == 0 ? true : false) : true + ); - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - let tempFileState = []; - if (file.key) { - // assymmetrically decrypt symmetric key with local private key - const key = decryptAssymmetric({ - ciphertext: file.key.encryptedKey, - nonce: file.key.nonce, - publicKey: file.key.sender.publicKey, - privateKey: PRIVATE_KEY, - }); + let tempFileState = []; + if (file.key) { + // assymmetrically decrypt symmetric key with local private key + const key = decryptAssymmetric({ + ciphertext: file.key.encryptedKey, + nonce: file.key.nonce, + publicKey: file.key.sender.publicKey, + privateKey: PRIVATE_KEY, + }); - file.secrets.map((secretPair) => { - // decrypt .env file with symmetric key - const plainTextKey = decryptSymmetric({ - ciphertext: secretPair.secretKey.ciphertext, - iv: secretPair.secretKey.iv, - tag: secretPair.secretKey.tag, - key, - }); + file.secrets.map((secretPair) => { + // decrypt .env file with symmetric key + const plainTextKey = decryptSymmetric({ + ciphertext: secretPair.secretKey.ciphertext, + iv: secretPair.secretKey.iv, + tag: secretPair.secretKey.tag, + key, + }); - const plainTextValue = decryptSymmetric({ - ciphertext: secretPair.secretValue.ciphertext, - iv: secretPair.secretValue.iv, - tag: secretPair.secretValue.tag, - key, - }); - tempFileState.push({ - key: plainTextKey, - value: plainTextValue, - type: secretPair.type, - }); - }); - } - setFileState(tempFileState); + const plainTextValue = decryptSymmetric({ + ciphertext: secretPair.secretValue.ciphertext, + iv: secretPair.secretValue.iv, + tag: secretPair.secretValue.tag, + key, + }); + tempFileState.push({ + key: plainTextKey, + value: plainTextValue, + type: secretPair.type, + }); + }); + } + setFileState(tempFileState); - setData( - tempFileState.map((line, index) => [ - guidGenerator(), - index, - line["key"], - line["value"], - line["type"], - ]) - // .sort((a, b) => - // sortMethod == "alphabetical" - // ? a[2].localeCompare(b[2]) - // : b[2].localeCompare(a[2]) - // ) - ); - return tempFileState.map((line, index) => [ - guidGenerator(), - index, - line["key"], - line["value"], - line["type"], - ]); - } catch (error) { - console.log( - "Something went wrong during accessing or decripting secrets." - ); - } - return true; + setData( + tempFileState.map((line, index) => [ + guidGenerator(), + index, + line["key"], + line["value"], + line["type"], + ]) + // .sort((a, b) => + // sortMethod == "alphabetical" + // ? a[2].localeCompare(b[2]) + // : b[2].localeCompare(a[2]) + // ) + ); + return tempFileState.map((line, index) => [ + guidGenerator(), + index, + line["key"], + line["value"], + line["type"], + ]); + } catch (error) { + console.log("Something went wrong during accessing or decripting secrets."); + } + return true; }; export default getSecretsForProject; diff --git a/frontend/components/utilities/secrets/pushKeys.js b/frontend/components/utilities/secrets/pushKeys.js index 16b095632..97ec31c06 100644 --- a/frontend/components/utilities/secrets/pushKeys.js +++ b/frontend/components/utilities/secrets/pushKeys.js @@ -4,106 +4,103 @@ import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys"; const crypto = require("crypto"); const { - decryptAssymmetric, - decryptSymmetric, - encryptSymmetric, - encryptAssymmetric, + decryptAssymmetric, + decryptSymmetric, + encryptSymmetric, + encryptAssymmetric, } = require("../cryptography/crypto"); const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); const envMapping = { - Development: "dev", - Staging: "staging", - Production: "prod", - Testing: "test", + Development: "dev", + Staging: "staging", + Production: "prod", + Testing: "test", }; const pushKeys = async (obj, workspaceId, env) => { - let sharedKey = await getLatestFileKey(workspaceId); + let sharedKey = await getLatestFileKey(workspaceId); - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - let randomBytes; - if (Object.keys(sharedKey).length > 0) { - // case: a (shared) key exists for the workspace - randomBytes = decryptAssymmetric({ - ciphertext: sharedKey.latestKey.encryptedKey, - nonce: sharedKey.latestKey.nonce, - publicKey: sharedKey.latestKey.sender.publicKey, - privateKey: PRIVATE_KEY, - }); - } else { - // case: a (shared) key does not exist for the workspace - randomBytes = crypto.randomBytes(16).toString("hex"); - } + let randomBytes; + if (Object.keys(sharedKey).length > 0) { + // case: a (shared) key exists for the workspace + randomBytes = decryptAssymmetric({ + ciphertext: sharedKey.latestKey.encryptedKey, + nonce: sharedKey.latestKey.nonce, + publicKey: sharedKey.latestKey.sender.publicKey, + privateKey: PRIVATE_KEY, + }); + } else { + // case: a (shared) key does not exist for the workspace + randomBytes = crypto.randomBytes(16).toString("hex"); + } - const secrets = Object.keys(obj).map((key) => { - // encrypt key - const { - ciphertext: ciphertextKey, - iv: ivKey, - tag: tagKey, - } = encryptSymmetric({ - plaintext: key, - key: randomBytes, - }); + const secrets = Object.keys(obj).map((key) => { + // encrypt key + const { + ciphertext: ciphertextKey, + iv: ivKey, + tag: tagKey, + } = encryptSymmetric({ + plaintext: key, + key: randomBytes, + }); - // encrypt value - const { - ciphertext: ciphertextValue, - iv: ivValue, - tag: tagValue, - } = encryptSymmetric({ - plaintext: obj[key][0], - key: randomBytes, - }); + // encrypt value + const { + ciphertext: ciphertextValue, + iv: ivValue, + tag: tagValue, + } = encryptSymmetric({ + plaintext: obj[key][0], + key: randomBytes, + }); - const visibility = obj[key][1] != null ? obj[key][1] : "personal"; + const visibility = obj[key][1] != null ? obj[key][1] : "personal"; - return { - ciphertextKey, - ivKey, - tagKey, - hashKey: crypto.createHash("sha256").update(key).digest("hex"), - ciphertextValue, - ivValue, - tagValue, - hashValue: crypto - .createHash("sha256") - .update(obj[key][0]) - .digest("hex"), - type: visibility, - }; - }); + return { + ciphertextKey, + ivKey, + tagKey, + hashKey: crypto.createHash("sha256").update(key).digest("hex"), + ciphertextValue, + ivValue, + tagValue, + hashValue: crypto.createHash("sha256").update(obj[key][0]).digest("hex"), + type: visibility, + }; + }); - // obtain public keys of all receivers (i.e. members in workspace) - const publicKeys = await getWorkspaceKeys({ - workspaceId, - }); + // obtain public keys of all receivers (i.e. members in workspace) + const publicKeys = await getWorkspaceKeys({ + workspaceId, + }); - // assymmetrically encrypt key with each receiver public keys - const keys = publicKeys.map((k) => { - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: randomBytes, - publicKey: k.publicKey, - privateKey: PRIVATE_KEY, - }); + // assymmetrically encrypt key with each receiver public keys + const keys = publicKeys.map((k) => { + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: randomBytes, + publicKey: k.publicKey, + privateKey: PRIVATE_KEY, + }); - return { - encryptedKey: ciphertext, - nonce, - userId: k.userId, - }; - }); + return { + encryptedKey: ciphertext, + nonce, + userId: k.userId, + }; + }); - // send payload - await uploadSecrets({ - workspaceId, - secrets, - keys, - environment: envMapping[env], - }); + // send payload + await uploadSecrets({ + workspaceId, + secrets, + keys, + environment: envMapping[env], + }); }; export default pushKeys; diff --git a/frontend/components/utilities/secrets/pushKeysIntegration.js b/frontend/components/utilities/secrets/pushKeysIntegration.js index 3f1bc6ee4..5b08748e7 100644 --- a/frontend/components/utilities/secrets/pushKeysIntegration.js +++ b/frontend/components/utilities/secrets/pushKeysIntegration.js @@ -2,73 +2,73 @@ import publicKeyInfical from "~/pages/api/auth/publicKeyInfisical"; import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars"; const crypto = require("crypto"); -const { encryptSymmetric, encryptAssymmetric } = require("../cryptography/crypto"); +const { + encryptSymmetric, + encryptAssymmetric, +} = require("../cryptography/crypto"); const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); const pushKeysIntegration = async ({ obj, integrationId }) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - let randomBytes = crypto.randomBytes(16).toString("hex"); + let randomBytes = crypto.randomBytes(16).toString("hex"); - const secrets = Object.keys(obj).map((key) => { - // encrypt key - const { - ciphertext: ciphertextKey, - iv: ivKey, - tag: tagKey, - } = encryptSymmetric({ - plaintext: key, - key: randomBytes, - }); + const secrets = Object.keys(obj).map((key) => { + // encrypt key + const { + ciphertext: ciphertextKey, + iv: ivKey, + tag: tagKey, + } = encryptSymmetric({ + plaintext: key, + key: randomBytes, + }); - // encrypt value - const { - ciphertext: ciphertextValue, - iv: ivValue, - tag: tagValue, - } = encryptSymmetric({ - plaintext: obj[key], - key: randomBytes, - }); + // encrypt value + const { + ciphertext: ciphertextValue, + iv: ivValue, + tag: tagValue, + } = encryptSymmetric({ + plaintext: obj[key], + key: randomBytes, + }); - const visibility = "shared"; + const visibility = "shared"; - return { - ciphertextKey, - ivKey, - tagKey, - hashKey: crypto.createHash("sha256").update(key).digest("hex"), - ciphertextValue, - ivValue, - tagValue, - hashValue: crypto - .createHash("sha256") - .update(obj[key]) - .digest("hex"), - type: visibility, - }; - }); + return { + ciphertextKey, + ivKey, + tagKey, + hashKey: crypto.createHash("sha256").update(key).digest("hex"), + ciphertextValue, + ivValue, + tagValue, + hashValue: crypto.createHash("sha256").update(obj[key]).digest("hex"), + type: visibility, + }; + }); - // obtain public keys of all receivers (i.e. members in workspace) - let publicKeyInfisical = await publicKeyInfical(); + // obtain public keys of all receivers (i.e. members in workspace) + let publicKeyInfisical = await publicKeyInfical(); - publicKeyInfisical = (await publicKeyInfisical.json()).publicKey; + publicKeyInfisical = (await publicKeyInfisical.json()).publicKey; - // assymmetrically encrypt key with each receiver public keys + // assymmetrically encrypt key with each receiver public keys - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: randomBytes, - publicKey: publicKeyInfisical, - privateKey: PRIVATE_KEY, - }); + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: randomBytes, + publicKey: publicKeyInfisical, + privateKey: PRIVATE_KEY, + }); - const key = { - encryptedKey: ciphertext, - nonce, - }; + const key = { + encryptedKey: ciphertext, + nonce, + }; - changeHerokuConfigVars({ integrationId, key, secrets }); + changeHerokuConfigVars({ integrationId, key, secrets }); }; export default pushKeysIntegration; diff --git a/frontend/const.js b/frontend/const.js index 06b7eef99..01f7dcf7e 100644 --- a/frontend/const.js +++ b/frontend/const.js @@ -1,20 +1,20 @@ export const PATH = process.env.NEXT_PUBLIC_WEBSITE_URL; export const publicPaths = [ - `/`, - // `/integrations`, - `/signupinvite`, - `/pricing`, - `/signup`, - `/login`, - `/blog`, - `/docs`, - `/changelog`, - `/security`, - `/scheduledemo`, - `/blog/[slug]`, - `/faq`, - `/privacy`, - `/terms`, - `/subprocessors`, + `/`, + // `/integrations`, + `/signupinvite`, + `/pricing`, + `/signup`, + `/login`, + `/blog`, + `/docs`, + `/changelog`, + `/security`, + `/scheduledemo`, + `/blog/[slug]`, + `/faq`, + `/privacy`, + `/terms`, + `/subprocessors`, ]; diff --git a/frontend/next.config.js b/frontend/next.config.js index 9b57c18b3..0711da572 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -2,11 +2,15 @@ const ContentSecurityPolicy = ` default-src ${process.env.NEXT_PUBLIC_WEBSITE_URL}; - script-src ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval'; + script-src ${ + process.env.NEXT_PUBLIC_WEBSITE_URL + } https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' https://rsms.me 'unsafe-inline'; child-src https://infisical.com https://api.stripe.com; frame-src https://js.stripe.com/ https://api.stripe.com; - connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split('//')[1]} ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals; + connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split("//")[1]} ${ + process.env.NEXT_PUBLIC_WEBSITE_URL +} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals; img-src 'self' https://*.stripe.com https://i.ytimg.com/ data:; media-src; font-src 'self' https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com; @@ -15,49 +19,49 @@ const ContentSecurityPolicy = ` // You can choose which headers to add to the list // after learning more below. const securityHeaders = [ - { - key: "X-DNS-Prefetch-Control", - value: "on", - }, - { - key: "Strict-Transport-Security", - value: "max-age=63072000; includeSubDomains; preload", - }, - { - key: "X-XSS-Protection", - value: "1; mode=block", - }, - { - key: "X-Frame-Options", - value: "SAMEORIGIN", - }, - { - key: "Permissions-Policy", - value: "camera=(), microphone=()", - }, - { - key: "X-Content-Type-Options", - value: "nosniff", - }, - { - key: "Referrer-Policy", - value: "strict-origin-when-cross-origin", - }, - { - key: "Content-Security-Policy", - value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(), - }, + { + key: "X-DNS-Prefetch-Control", + value: "on", + }, + { + key: "Strict-Transport-Security", + value: "max-age=63072000; includeSubDomains; preload", + }, + { + key: "X-XSS-Protection", + value: "1; mode=block", + }, + { + key: "X-Frame-Options", + value: "SAMEORIGIN", + }, + { + key: "Permissions-Policy", + value: "camera=(), microphone=()", + }, + { + key: "X-Content-Type-Options", + value: "nosniff", + }, + { + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin", + }, + { + key: "Content-Security-Policy", + value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(), + }, ]; module.exports = { - output: 'standalone', - async headers() { - return [ - { - // Apply these headers to all routes in your application. - source: "/:path*", - headers: securityHeaders, - }, - ]; - }, + output: "standalone", + async headers() { + return [ + { + // Apply these headers to all routes in your application. + source: "/:path*", + headers: securityHeaders, + }, + ]; + }, }; diff --git a/frontend/package.json b/frontend/package.json index 7038a09c7..bf4ee91e0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,65 +1,65 @@ { - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "start:docker": "next build && next start", - "lint": "next lint" - }, - "dependencies": { - "@emotion/css": "^11.10.0", - "@emotion/server": "^11.10.0", - "@fortawesome/fontawesome-svg-core": "^6.1.2", - "@fortawesome/free-brands-svg-icons": "^6.1.2", - "@fortawesome/free-regular-svg-icons": "^6.1.1", - "@fortawesome/free-solid-svg-icons": "^6.1.2", - "@fortawesome/react-fontawesome": "^0.1.19", - "@headlessui/react": "^1.6.6", - "@heroicons/react": "^1.0.6", - "@reduxjs/toolkit": "^1.8.3", - "@stripe/react-stripe-js": "^1.10.0", - "@stripe/stripe-js": "^1.35.0", - "add": "^2.0.6", - "axios": "^0.27.2", - "axios-auth-refresh": "^3.3.3", - "classnames": "^2.3.1", - "cookies": "^0.8.0", - "fs": "^0.0.1-security", - "gray-matter": "^4.0.3", - "http-proxy": "^1.18.1", - "jspdf": "^2.5.1", - "jsrp": "^0.2.4", - "markdown-it": "^13.0.1", - "next": "^12.2.5", - "posthog-js": "^1.34.0", - "query-string": "^7.1.1", - "react": "^17.0.2", - "react-beautiful-dnd": "^13.1.1", - "react-code-input": "^3.10.1", - "react-dom": "^17.0.2", - "react-github-btn": "^1.4.0", - "react-grid-layout": "^1.3.4", - "react-mailchimp-subscribe": "^2.1.3", - "react-markdown": "^8.0.3", - "react-redux": "^8.0.2", - "react-table": "^7.8.0", - "set-cookie-parser": "^2.5.1", - "styled-components": "^5.3.5", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1", - "uuid": "^8.3.2", - "uuidv4": "^6.2.13" - }, - "devDependencies": { - "@tailwindcss/typography": "^0.5.4", - "autoprefixer": "^10.4.7", - "eslint": "^8.28.0", - "eslint-config-next": "^13.0.5", - "eslint-plugin-simple-import-sort": "^8.0.0", - "postcss": "^8.4.14", - "prettier": "2.7.1", - "tailwindcss": "^3.1.4", - "typescript": "^4.9.3" - } + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "start:docker": "next build && next start", + "lint": "next lint" + }, + "dependencies": { + "@emotion/css": "^11.10.0", + "@emotion/server": "^11.10.0", + "@fortawesome/fontawesome-svg-core": "^6.1.2", + "@fortawesome/free-brands-svg-icons": "^6.1.2", + "@fortawesome/free-regular-svg-icons": "^6.1.1", + "@fortawesome/free-solid-svg-icons": "^6.1.2", + "@fortawesome/react-fontawesome": "^0.1.19", + "@headlessui/react": "^1.6.6", + "@heroicons/react": "^1.0.6", + "@reduxjs/toolkit": "^1.8.3", + "@stripe/react-stripe-js": "^1.10.0", + "@stripe/stripe-js": "^1.35.0", + "add": "^2.0.6", + "axios": "^0.27.2", + "axios-auth-refresh": "^3.3.3", + "classnames": "^2.3.1", + "cookies": "^0.8.0", + "fs": "^0.0.1-security", + "gray-matter": "^4.0.3", + "http-proxy": "^1.18.1", + "jspdf": "^2.5.1", + "jsrp": "^0.2.4", + "markdown-it": "^13.0.1", + "next": "^12.2.5", + "posthog-js": "^1.34.0", + "query-string": "^7.1.1", + "react": "^17.0.2", + "react-beautiful-dnd": "^13.1.1", + "react-code-input": "^3.10.1", + "react-dom": "^17.0.2", + "react-github-btn": "^1.4.0", + "react-grid-layout": "^1.3.4", + "react-mailchimp-subscribe": "^2.1.3", + "react-markdown": "^8.0.3", + "react-redux": "^8.0.2", + "react-table": "^7.8.0", + "set-cookie-parser": "^2.5.1", + "styled-components": "^5.3.5", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1", + "uuid": "^8.3.2", + "uuidv4": "^6.2.13" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.4", + "autoprefixer": "^10.4.7", + "eslint": "^8.28.0", + "eslint-config-next": "^13.0.5", + "eslint-plugin-simple-import-sort": "^8.0.0", + "postcss": "^8.4.14", + "prettier": "2.7.1", + "tailwindcss": "^3.1.4", + "typescript": "^4.9.3" + } } diff --git a/frontend/pages/_app.js b/frontend/pages/_app.js index ff8c2b7e3..6ed9af013 100644 --- a/frontend/pages/_app.js +++ b/frontend/pages/_app.js @@ -5,9 +5,8 @@ import { config } from "@fortawesome/fontawesome-svg-core"; import { initPostHog } from "~/components/analytics/posthog"; import Layout from "~/components/basic/layout"; import RouteGuard from "~/components/RouteGuard"; -import { ENV } from "~/utilities/config"; - import { publicPaths } from "~/const"; +import { ENV } from "~/utilities/config"; import "@fortawesome/fontawesome-svg-core/styles.css"; import "../styles/globals.css"; diff --git a/frontend/pages/api/auth/ChangePassword2.js b/frontend/pages/api/auth/ChangePassword2.js index d039cc9c9..56db44ebf 100644 --- a/frontend/pages/api/auth/ChangePassword2.js +++ b/frontend/pages/api/auth/ChangePassword2.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This is the second step of the change password process (pake) diff --git a/frontend/pages/api/auth/CheckAuth.js b/frontend/pages/api/auth/CheckAuth.js index d70180d68..5ddcb6d9a 100644 --- a/frontend/pages/api/auth/CheckAuth.js +++ b/frontend/pages/api/auth/CheckAuth.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient.js"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient.js"; /** * This function is used to check if the user is authenticated. diff --git a/frontend/pages/api/auth/IssueBackupPrivateKey.js b/frontend/pages/api/auth/IssueBackupPrivateKey.js index ee5e1188d..a4cff8041 100644 --- a/frontend/pages/api/auth/IssueBackupPrivateKey.js +++ b/frontend/pages/api/auth/IssueBackupPrivateKey.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This is the route that issues a backup private key that will afterwards be added into a pdf diff --git a/frontend/pages/api/auth/Logout.js b/frontend/pages/api/auth/Logout.js index 4ecfda425..c5e3e4468 100644 --- a/frontend/pages/api/auth/Logout.js +++ b/frontend/pages/api/auth/Logout.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route logs the user out. Note: the user should authorized to do this. diff --git a/frontend/pages/api/auth/SRP1.js b/frontend/pages/api/auth/SRP1.js index 04bdec523..310066040 100644 --- a/frontend/pages/api/auth/SRP1.js +++ b/frontend/pages/api/auth/SRP1.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This is the first step of the change password process (pake) diff --git a/frontend/pages/api/files/GetSecrets.js b/frontend/pages/api/files/GetSecrets.js index 0924b2fad..6f5bf1d97 100644 --- a/frontend/pages/api/files/GetSecrets.js +++ b/frontend/pages/api/files/GetSecrets.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient.js"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient.js"; /** * This function fetches the encrypted secrets from the .env file diff --git a/frontend/pages/api/files/UploadSecrets.js b/frontend/pages/api/files/UploadSecrets.js index 93661057d..2b6dd0722 100644 --- a/frontend/pages/api/files/UploadSecrets.js +++ b/frontend/pages/api/files/UploadSecrets.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This function uploads the encrypted .env file diff --git a/frontend/pages/api/integrations/ChangeHerokuConfigVars.js b/frontend/pages/api/integrations/ChangeHerokuConfigVars.js index 2d09d1780..6075ad516 100644 --- a/frontend/pages/api/integrations/ChangeHerokuConfigVars.js +++ b/frontend/pages/api/integrations/ChangeHerokuConfigVars.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; const changeHerokuConfigVars = ({ integrationId, key, secrets }) => { return SecurityClient.fetchCall( diff --git a/frontend/pages/api/integrations/DeleteIntegration.js b/frontend/pages/api/integrations/DeleteIntegration.js index 75d1417a7..5b3dd6ea5 100644 --- a/frontend/pages/api/integrations/DeleteIntegration.js +++ b/frontend/pages/api/integrations/DeleteIntegration.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route deletes an integration from a certain project diff --git a/frontend/pages/api/integrations/DeleteIntegrationAuth.js b/frontend/pages/api/integrations/DeleteIntegrationAuth.js index 62783babe..1d8566b41 100644 --- a/frontend/pages/api/integrations/DeleteIntegrationAuth.js +++ b/frontend/pages/api/integrations/DeleteIntegrationAuth.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route deletes an integration authorization from a certain project diff --git a/frontend/pages/api/integrations/GetIntegrationApps.js b/frontend/pages/api/integrations/GetIntegrationApps.js index d75891fd7..e049c3006 100644 --- a/frontend/pages/api/integrations/GetIntegrationApps.js +++ b/frontend/pages/api/integrations/GetIntegrationApps.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; const getIntegrationApps = ({ integrationAuthId }) => { return SecurityClient.fetchCall( diff --git a/frontend/pages/api/integrations/GetIntegrations.js b/frontend/pages/api/integrations/GetIntegrations.js index a8f1c9631..90e438a6c 100644 --- a/frontend/pages/api/integrations/GetIntegrations.js +++ b/frontend/pages/api/integrations/GetIntegrations.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; const getIntegrations = () => { return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", { diff --git a/frontend/pages/api/integrations/StartIntegration.js b/frontend/pages/api/integrations/StartIntegration.js index c9ecdcb44..a52fb1855 100644 --- a/frontend/pages/api/integrations/StartIntegration.js +++ b/frontend/pages/api/integrations/StartIntegration.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route starts the integration after teh default one if gonna set up. diff --git a/frontend/pages/api/integrations/authorizeIntegration.js b/frontend/pages/api/integrations/authorizeIntegration.js index 098ff7345..38dbcc713 100644 --- a/frontend/pages/api/integrations/authorizeIntegration.js +++ b/frontend/pages/api/integrations/authorizeIntegration.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This is the first step of the change password process (pake) diff --git a/frontend/pages/api/integrations/getWorkspaceAuthorizations.js b/frontend/pages/api/integrations/getWorkspaceAuthorizations.js index a5a3a3bba..c05ae6ffc 100644 --- a/frontend/pages/api/integrations/getWorkspaceAuthorizations.js +++ b/frontend/pages/api/integrations/getWorkspaceAuthorizations.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route gets authorizations of a certain project (Heroku, etc.) diff --git a/frontend/pages/api/integrations/getWorkspaceIntegrations.js b/frontend/pages/api/integrations/getWorkspaceIntegrations.js index eb93f8883..899aeaa8a 100644 --- a/frontend/pages/api/integrations/getWorkspaceIntegrations.js +++ b/frontend/pages/api/integrations/getWorkspaceIntegrations.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route gets integrations of a certain project (Heroku, etc.) diff --git a/frontend/pages/api/organization/GetOrg.js b/frontend/pages/api/organization/GetOrg.js index 0a81d5da8..85414470b 100644 --- a/frontend/pages/api/organization/GetOrg.js +++ b/frontend/pages/api/organization/GetOrg.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get info about a certain org diff --git a/frontend/pages/api/organization/GetOrgProjects.js b/frontend/pages/api/organization/GetOrgProjects.js index dbec142a5..1ff80a3eb 100644 --- a/frontend/pages/api/organization/GetOrgProjects.js +++ b/frontend/pages/api/organization/GetOrgProjects.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get all the users in an org. diff --git a/frontend/pages/api/organization/GetOrgSubscription.js b/frontend/pages/api/organization/GetOrgSubscription.js index 856725bf0..a983c15ed 100644 --- a/frontend/pages/api/organization/GetOrgSubscription.js +++ b/frontend/pages/api/organization/GetOrgSubscription.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get the current subscription of an org. diff --git a/frontend/pages/api/organization/GetOrgUserProjects.js b/frontend/pages/api/organization/GetOrgUserProjects.js index 869dbb232..fc51d18ce 100644 --- a/frontend/pages/api/organization/GetOrgUserProjects.js +++ b/frontend/pages/api/organization/GetOrgUserProjects.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get all the projects of a certain user in an org. diff --git a/frontend/pages/api/organization/GetOrgUsers.js b/frontend/pages/api/organization/GetOrgUsers.js index e4e8454eb..287b07bec 100644 --- a/frontend/pages/api/organization/GetOrgUsers.js +++ b/frontend/pages/api/organization/GetOrgUsers.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get all the users in an org. diff --git a/frontend/pages/api/organization/StripeRedirect.js b/frontend/pages/api/organization/StripeRedirect.js index f7cd7cb06..6d28d464b 100644 --- a/frontend/pages/api/organization/StripeRedirect.js +++ b/frontend/pages/api/organization/StripeRedirect.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route redirects the user to the right stripe billing page. diff --git a/frontend/pages/api/organization/addIncidentContact.js b/frontend/pages/api/organization/addIncidentContact.js index f5ca2729f..b1b3d5fa9 100644 --- a/frontend/pages/api/organization/addIncidentContact.js +++ b/frontend/pages/api/organization/addIncidentContact.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route add an incident contact email to a certain organization diff --git a/frontend/pages/api/organization/addUserToOrg.js b/frontend/pages/api/organization/addUserToOrg.js index d807a3a7c..ccee48da3 100644 --- a/frontend/pages/api/organization/addUserToOrg.js +++ b/frontend/pages/api/organization/addUserToOrg.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This function sends an email invite to a user to join an org diff --git a/frontend/pages/api/organization/deleteIncidentContact.js b/frontend/pages/api/organization/deleteIncidentContact.js index 34d2e5a1e..a26335d6d 100644 --- a/frontend/pages/api/organization/deleteIncidentContact.js +++ b/frontend/pages/api/organization/deleteIncidentContact.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route deletes an incident Contact from a certain organization diff --git a/frontend/pages/api/organization/deleteUserFromOrganization.js b/frontend/pages/api/organization/deleteUserFromOrganization.js index 8beadf0c1..269c9b2ed 100644 --- a/frontend/pages/api/organization/deleteUserFromOrganization.js +++ b/frontend/pages/api/organization/deleteUserFromOrganization.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This function removes a certain member from a certain organization diff --git a/frontend/pages/api/organization/getIncidentContacts.js b/frontend/pages/api/organization/getIncidentContacts.js index 9eb76242a..f2867e63c 100644 --- a/frontend/pages/api/organization/getIncidentContacts.js +++ b/frontend/pages/api/organization/getIncidentContacts.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This routes gets all the incident contacts of a certain organization diff --git a/frontend/pages/api/organization/getOrgs.js b/frontend/pages/api/organization/getOrgs.js index 501911f3c..5f28f913d 100644 --- a/frontend/pages/api/organization/getOrgs.js +++ b/frontend/pages/api/organization/getOrgs.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get the all the orgs of a certain user. diff --git a/frontend/pages/api/organization/renameOrg.js b/frontend/pages/api/organization/renameOrg.js index ddd3ad939..a33c6d3a5 100644 --- a/frontend/pages/api/organization/renameOrg.js +++ b/frontend/pages/api/organization/renameOrg.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us rename a certain org. diff --git a/frontend/pages/api/serviceToken/addServiceToken.js b/frontend/pages/api/serviceToken/addServiceToken.js index b101c97db..2b4a8e46e 100644 --- a/frontend/pages/api/serviceToken/addServiceToken.js +++ b/frontend/pages/api/serviceToken/addServiceToken.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route gets service tokens for a specific user in a project diff --git a/frontend/pages/api/serviceToken/getServiceTokens.js b/frontend/pages/api/serviceToken/getServiceTokens.js index a09acb2de..3928076cc 100644 --- a/frontend/pages/api/serviceToken/getServiceTokens.js +++ b/frontend/pages/api/serviceToken/getServiceTokens.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route gets service tokens for a specific user in a project diff --git a/frontend/pages/api/user/getUser.js b/frontend/pages/api/user/getUser.js index 0f5004433..baca9239b 100644 --- a/frontend/pages/api/user/getUser.js +++ b/frontend/pages/api/user/getUser.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route gets the information about a specific user. diff --git a/frontend/pages/api/userActions/checkUserAction.js b/frontend/pages/api/userActions/checkUserAction.js index fa70d2844..981fa74ee 100644 --- a/frontend/pages/api/userActions/checkUserAction.js +++ b/frontend/pages/api/userActions/checkUserAction.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route registers a certain action for a user diff --git a/frontend/pages/api/userActions/registerUserAction.js b/frontend/pages/api/userActions/registerUserAction.js index 9fae67e6e..98a80627c 100644 --- a/frontend/pages/api/userActions/registerUserAction.js +++ b/frontend/pages/api/userActions/registerUserAction.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route registers a certain action for a user diff --git a/frontend/pages/api/workspace/addUserToWorkspace.js b/frontend/pages/api/workspace/addUserToWorkspace.js index 1e5d9a212..d6caa30e4 100644 --- a/frontend/pages/api/workspace/addUserToWorkspace.js +++ b/frontend/pages/api/workspace/addUserToWorkspace.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This function adds a user to a project diff --git a/frontend/pages/api/workspace/changeUserRoleInWorkspace.js b/frontend/pages/api/workspace/changeUserRoleInWorkspace.js index 7d0fb629e..5f37e674f 100644 --- a/frontend/pages/api/workspace/changeUserRoleInWorkspace.js +++ b/frontend/pages/api/workspace/changeUserRoleInWorkspace.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This function change the access of a user in a certain workspace diff --git a/frontend/pages/api/workspace/createWorkspace.js b/frontend/pages/api/workspace/createWorkspace.js index ac61e22e1..1451249b7 100644 --- a/frontend/pages/api/workspace/createWorkspace.js +++ b/frontend/pages/api/workspace/createWorkspace.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route creates a new workspace for a user. diff --git a/frontend/pages/api/workspace/deleteUserFromWorkspace.js b/frontend/pages/api/workspace/deleteUserFromWorkspace.js index 6fdeaa9c1..83389c281 100644 --- a/frontend/pages/api/workspace/deleteUserFromWorkspace.js +++ b/frontend/pages/api/workspace/deleteUserFromWorkspace.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This function removes a certain member from a certain workspace diff --git a/frontend/pages/api/workspace/deleteWorkspace.js b/frontend/pages/api/workspace/deleteWorkspace.js index 3bb8f27af..c84ebe756 100644 --- a/frontend/pages/api/workspace/deleteWorkspace.js +++ b/frontend/pages/api/workspace/deleteWorkspace.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route deletes a specified workspace. diff --git a/frontend/pages/api/workspace/getLatestFileKey.js b/frontend/pages/api/workspace/getLatestFileKey.js index 772962b31..e1fab97c6 100644 --- a/frontend/pages/api/workspace/getLatestFileKey.js +++ b/frontend/pages/api/workspace/getLatestFileKey.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * Get the latest key pairs from a certain workspace diff --git a/frontend/pages/api/workspace/getWorkspaceInfo.js b/frontend/pages/api/workspace/getWorkspaceInfo.js index 7c0cbd1fd..449cbd54b 100644 --- a/frontend/pages/api/workspace/getWorkspaceInfo.js +++ b/frontend/pages/api/workspace/getWorkspaceInfo.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get the information of a certain project. diff --git a/frontend/pages/api/workspace/getWorkspaceKeys.js b/frontend/pages/api/workspace/getWorkspaceKeys.js index 37849aec0..c94cb786e 100644 --- a/frontend/pages/api/workspace/getWorkspaceKeys.js +++ b/frontend/pages/api/workspace/getWorkspaceKeys.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get the public keys of everyone in your workspace. diff --git a/frontend/pages/api/workspace/getWorkspaceUsers.js b/frontend/pages/api/workspace/getWorkspaceUsers.js index 3b95fcbfd..b7c42bf88 100644 --- a/frontend/pages/api/workspace/getWorkspaceUsers.js +++ b/frontend/pages/api/workspace/getWorkspaceUsers.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get all the users in the workspace. diff --git a/frontend/pages/api/workspace/getWorkspaces.js b/frontend/pages/api/workspace/getWorkspaces.js index 0cb874908..d3b276eb7 100644 --- a/frontend/pages/api/workspace/getWorkspaces.js +++ b/frontend/pages/api/workspace/getWorkspaces.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us get the public keys of everyone in your workspace. diff --git a/frontend/pages/api/workspace/renameWorkspace.js b/frontend/pages/api/workspace/renameWorkspace.js index 71ed7039e..b92addd1a 100644 --- a/frontend/pages/api/workspace/renameWorkspace.js +++ b/frontend/pages/api/workspace/renameWorkspace.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route lets us rename a certain workspace. diff --git a/frontend/pages/api/workspace/uploadKeys.js b/frontend/pages/api/workspace/uploadKeys.js index eac6e6e51..a368948aa 100644 --- a/frontend/pages/api/workspace/uploadKeys.js +++ b/frontend/pages/api/workspace/uploadKeys.js @@ -1,6 +1,5 @@ -import SecurityClient from "~/utilities/SecurityClient"; - import { PATH } from "~/const"; +import SecurityClient from "~/utilities/SecurityClient"; /** * This route uplods the keys in an encrypted format. diff --git a/frontend/pages/dashboard.js b/frontend/pages/dashboard.js index 11a2164b5..5954a9db2 100644 --- a/frontend/pages/dashboard.js +++ b/frontend/pages/dashboard.js @@ -5,31 +5,29 @@ import { useRouter } from "next/router"; import getWorkspaces from "./api/workspace/getWorkspaces"; export default function DashboardRedirect() { - const router = useRouter(); + const router = useRouter(); - /** - * Here we forward to the default workspace if a user opens this url - */ - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(async () => { - let userWorkspace; - try { - if (localStorage.getItem("projectData.id")) { - router.push( - "/dashboard/" + localStorage.getItem("projectData.id") - ); - } else { - const userWorkspaces = await getWorkspaces(); - userWorkspace = userWorkspaces[0]._id; - router.push("/dashboard/" + userWorkspace); - } - } catch (error) { - console.log("Error - Not logged in yet"); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + /** + * Here we forward to the default workspace if a user opens this url + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(async () => { + let userWorkspace; + try { + if (localStorage.getItem("projectData.id")) { + router.push("/dashboard/" + localStorage.getItem("projectData.id")); + } else { + const userWorkspaces = await getWorkspaces(); + userWorkspace = userWorkspaces[0]._id; + router.push("/dashboard/" + userWorkspace); + } + } catch (error) { + console.log("Error - Not logged in yet"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return
; + return
; } DashboardRedirect.requireAuth = true; diff --git a/frontend/pages/dashboard/[id].js b/frontend/pages/dashboard/[id].js index a8195bb27..23fa4921a 100644 --- a/frontend/pages/dashboard/[id].js +++ b/frontend/pages/dashboard/[id].js @@ -3,22 +3,22 @@ import Head from "next/head"; import Image from "next/image"; import { useRouter } from "next/router"; import { - faArrowDownAZ, - faArrowDownZA, - faCheck, - faCircleInfo, - faCopy, - faDownload, - faEllipsis, - faEye, - faEyeSlash, - faFolderOpen, - faMagnifyingGlass, - faPeopleGroup, - faPerson, - faPlus, - faX, - faShuffle + faArrowDownAZ, + faArrowDownZA, + faCheck, + faCircleInfo, + faCopy, + faDownload, + faEllipsis, + faEye, + faEyeSlash, + faFolderOpen, + faMagnifyingGlass, + faPeopleGroup, + faPerson, + faPlus, + faShuffle, + faX, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Menu, Transition } from "@headlessui/react"; @@ -41,10 +41,10 @@ import registerUserAction from "../api/userActions/registerUserAction"; import getWorkspaces from "../api/workspace/getWorkspaces"; const envMapping = { - Development: "dev", - Staging: "staging", - Production: "prod", - Testing: "test", + Development: "dev", + Staging: "staging", + Production: "prod", + Testing: "test", }; /** @@ -54,783 +54,715 @@ const envMapping = { * @param {function} obj.deleteRow - a function to delete a certain keyPair * @param {function} obj.modifyKey - modify the key of a certain environment variable * @param {function} obj.modifyValue - modify the value of a certain environment variable - * @param {function} obj.modifyVisibility - switch between public/private visibility + * @param {function} obj.modifyVisibility - switch between public/private visibility * @param {boolean} obj.isBlurred - if the blurring setting is turned on - * @returns + * @returns */ const KeyPair = ({ - keyPair, - deleteRow, - modifyKey, - modifyValue, - modifyVisibility, - isBlurred, + keyPair, + deleteRow, + modifyKey, + modifyValue, + modifyVisibility, + isBlurred, }) => { - const [randomStringLength, setRandomStringLength] = useState(32); + const [randomStringLength, setRandomStringLength] = useState(32); - return ( -
-
-
-
- -
-
-
-
- -
-
- -
- -
- -
-
-
- - -
- modifyVisibility( - keyPair[4] == "personal" - ? "shared" - : "personal", - keyPair[1] - ) - } - className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" - > - -
- {keyPair[4] == "personal" - ? "Make Shared" - : "Make Personal"} -
-
-
{ - if (randomStringLength > 32) { - setRandomStringLength(32); - } else if (randomStringLength < 2) { - setRandomStringLength(2); - } else { - modifyValue([...Array(randomStringLength)].map(() => Math.floor(Math.random() * 16).toString(16)).join(''), keyPair[1]); - } - }} - className="relative flex flex-row justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" - > - -
-

Generate Random Hex

-

digits

-
-
-
-
{ - if (randomStringLength > 1) { - setRandomStringLength(randomStringLength - 1) - } - }} - > - - -
- setRandomStringLength(parseInt(e.target.value))} - value={randomStringLength} - className="text-center z-20 peer text-sm bg-transparent w-full outline-none" - spellCheck="false" - /> -
{ - if (randomStringLength < 32) { - setRandomStringLength(randomStringLength + 1) - } - }} - > - + -
-
-
-
-
-
-
-
-
-
- ); + return ( +
+
+
+
+ +
+
+
+
+ +
+
+ +
+ +
+ +
+
+
+ + +
+ modifyVisibility( + keyPair[4] == "personal" ? "shared" : "personal", + keyPair[1] + ) + } + className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" + > + +
+ {keyPair[4] == "personal" ? "Make Shared" : "Make Personal"} +
+
+
{ + if (randomStringLength > 32) { + setRandomStringLength(32); + } else if (randomStringLength < 2) { + setRandomStringLength(2); + } else { + modifyValue( + [...Array(randomStringLength)] + .map(() => Math.floor(Math.random() * 16).toString(16)) + .join(""), + keyPair[1] + ); + } + }} + className="relative flex flex-row justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" + > + +
+

Generate Random Hex

+

digits

+
+
+
+
{ + if (randomStringLength > 1) { + setRandomStringLength(randomStringLength - 1); + } + }} + > + - +
+ + setRandomStringLength(parseInt(e.target.value)) + } + value={randomStringLength} + className="text-center z-20 peer text-sm bg-transparent w-full outline-none" + spellCheck="false" + /> +
{ + if (randomStringLength < 32) { + setRandomStringLength(randomStringLength + 1); + } + }} + > + + +
+
+
+
+
+
+
+
+
+
+ ); }; /** * This is the main component for the dashboard (aka the screen with all the encironemnt variable & secrets) - * @returns + * @returns */ export default function Dashboard() { - const [data, setData] = useState(); - const [fileState, setFileState] = useState([]); - const [buttonReady, setButtonReady] = useState(false); - const router = useRouter(); - const [workspaceId, setWorkspaceId] = useState(""); - const [blurred, setBlurred] = useState(true); - const [isKeyAvailable, setIsKeyAvailable] = useState(true); - const [env, setEnv] = useState( - router.asPath.split("?").length == 1 - ? "Development" - : Object.keys(envMapping).includes(router.asPath.split("?")[1]) - ? router.asPath.split("?")[1] - : "Development" - ); - const [isNew, setIsNew] = useState(false); - const [searchKeys, setSearchKeys] = useState(""); - const [errorDragAndDrop, setErrorDragAndDrop] = useState(false); - const [projectIdCopied, setProjectIdCopied] = useState(false); - const [sortMethod, setSortMethod] = useState("alphabetical"); - const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); - const [hasUserEverPushed, setHasUserEverPushed] = useState(false); + const [data, setData] = useState(); + const [fileState, setFileState] = useState([]); + const [buttonReady, setButtonReady] = useState(false); + const router = useRouter(); + const [workspaceId, setWorkspaceId] = useState(""); + const [blurred, setBlurred] = useState(true); + const [isKeyAvailable, setIsKeyAvailable] = useState(true); + const [env, setEnv] = useState( + router.asPath.split("?").length == 1 + ? "Development" + : Object.keys(envMapping).includes(router.asPath.split("?")[1]) + ? router.asPath.split("?")[1] + : "Development" + ); + const [isNew, setIsNew] = useState(false); + const [searchKeys, setSearchKeys] = useState(""); + const [errorDragAndDrop, setErrorDragAndDrop] = useState(false); + const [projectIdCopied, setProjectIdCopied] = useState(false); + const [sortMethod, setSortMethod] = useState("alphabetical"); + const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); + const [hasUserEverPushed, setHasUserEverPushed] = useState(false); - // #TODO: fix save message for changing reroutes - // const beforeRouteHandler = (url) => { - // const warningText = - // "Do you want to save your results bfore leaving this page?"; - // if (!buttonReady) return; - // if (router.asPath !== url && !confirm(warningText)) { - // // router.events.emit('routeChangeError'); - // // setData(data) - // savePush(); - // throw `Route change to "${url}" was aborted (this error can be safely ignored).`; - // } else { - // setButtonReady(false); - // } - // }; + // #TODO: fix save message for changing reroutes + // const beforeRouteHandler = (url) => { + // const warningText = + // "Do you want to save your results bfore leaving this page?"; + // if (!buttonReady) return; + // if (router.asPath !== url && !confirm(warningText)) { + // // router.events.emit('routeChangeError'); + // // setData(data) + // savePush(); + // throw `Route change to "${url}" was aborted (this error can be safely ignored).`; + // } else { + // setButtonReady(false); + // } + // }; - // prompt the user if they try and leave with unsaved changes - useEffect(() => { - const warningText = - "Do you want to save your results before leaving this page?"; - const handleWindowClose = (e) => { - if (!buttonReady) return; - e.preventDefault(); - return (e.returnValue = warningText); - }; - window.addEventListener("beforeunload", handleWindowClose); - // router.events.on('routeChangeStart', beforeRouteHandler); - return () => { - window.removeEventListener("beforeunload", handleWindowClose); - // router.events.off('routeChangeStart', beforeRouteHandler); - }; - }, [buttonReady]); + // prompt the user if they try and leave with unsaved changes + useEffect(() => { + const warningText = + "Do you want to save your results before leaving this page?"; + const handleWindowClose = (e) => { + if (!buttonReady) return; + e.preventDefault(); + return (e.returnValue = warningText); + }; + window.addEventListener("beforeunload", handleWindowClose); + // router.events.on('routeChangeStart', beforeRouteHandler); + return () => { + window.removeEventListener("beforeunload", handleWindowClose); + // router.events.off('routeChangeStart', beforeRouteHandler); + }; + }, [buttonReady]); - /** - * Reorder rows alphabetically or in the opprosite order - */ - const reorderRows = () => { - setSortMethod( - sortMethod == "alphabetical" ? "-alphabetical" : "alphabetical" - ); - }; + /** + * Reorder rows alphabetically or in the opprosite order + */ + const reorderRows = () => { + setSortMethod( + sortMethod == "alphabetical" ? "-alphabetical" : "alphabetical" + ); + }; - useEffect(() => { - (async () => { - try { - let userWorkspaces = await getWorkspaces(); - const listWorkspaces = userWorkspaces.map( - (workspace) => workspace._id - ); - if ( - !listWorkspaces.includes( - router.asPath.split("/")[2].split("?")[0] - ) - ) { - router.push("/dashboard/" + listWorkspaces[0]); - } + useEffect(() => { + (async () => { + try { + let userWorkspaces = await getWorkspaces(); + const listWorkspaces = userWorkspaces.map((workspace) => workspace._id); + if ( + !listWorkspaces.includes(router.asPath.split("/")[2].split("?")[0]) + ) { + router.push("/dashboard/" + listWorkspaces[0]); + } - if (env != router.asPath.split("?")[1]) { - router.push(router.asPath.split("?")[0] + "?" + env); - } - setBlurred(true); - setWorkspaceId(router.query.id); + if (env != router.asPath.split("?")[1]) { + router.push(router.asPath.split("?")[0] + "?" + env); + } + setBlurred(true); + setWorkspaceId(router.query.id); - await getSecretsForProject({ - env, - setFileState, - setIsKeyAvailable, - setData, - workspaceId: router.query.id, - }); + await getSecretsForProject({ + env, + setFileState, + setIsKeyAvailable, + setData, + workspaceId: router.query.id, + }); - const user = await getUser(); - setIsNew( - (Date.parse(new Date()) - Date.parse(user.createdAt)) / - 60000 < - 3 - ? true - : false - ); + const user = await getUser(); + setIsNew( + (Date.parse(new Date()) - Date.parse(user.createdAt)) / 60000 < 3 + ? true + : false + ); - let userAction = await checkUserAction({ - action: "first_time_secrets_pushed", - }); - setHasUserEverPushed(userAction ? true : false); - } catch (error) { - console.log("Error", error); - setData([]); - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [env]); + let userAction = await checkUserAction({ + action: "first_time_secrets_pushed", + }); + setHasUserEverPushed(userAction ? true : false); + } catch (error) { + console.log("Error", error); + setData([]); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [env]); - const addRow = () => { - setIsNew(false); - setData([...data, [guidGenerator(), data.length, "", "", "shared"]]); - }; + const addRow = () => { + setIsNew(false); + setData([...data, [guidGenerator(), data.length, "", "", "shared"]]); + }; - const deleteRow = (id) => { - setButtonReady(true); - setData(data.filter((row) => row[0] !== id)); - }; + const deleteRow = (id) => { + setButtonReady(true); + setData(data.filter((row) => row[0] !== id)); + }; - const modifyValue = (value, id) => { - setData((oldData) => { - oldData[id][3] = value; - return [...oldData]; - }); - setButtonReady(true); - }; + const modifyValue = (value, id) => { + setData((oldData) => { + oldData[id][3] = value; + return [...oldData]; + }); + setButtonReady(true); + }; - const modifyKey = (value, id) => { - setData((oldData) => { - oldData[id][2] = value; - return [...oldData]; - }); - setButtonReady(true); - }; + const modifyKey = (value, id) => { + setData((oldData) => { + oldData[id][2] = value; + return [...oldData]; + }); + setButtonReady(true); + }; - const modifyVisibility = (value, id) => { - setData((oldData) => { - oldData[id][4] = value; - return [...oldData]; - }); - setButtonReady(true); - }; + const modifyVisibility = (value, id) => { + setData((oldData) => { + oldData[id][4] = value; + return [...oldData]; + }); + setButtonReady(true); + }; - // For speed purposes and better perforamance, we are using useCallback - const listenChangeValue = useCallback((value, id) => { - modifyValue(value, id); - }, []); + // For speed purposes and better perforamance, we are using useCallback + const listenChangeValue = useCallback((value, id) => { + modifyValue(value, id); + }, []); - const listenChangeKey = useCallback((value, id) => { - modifyKey(value, id); - }, []); + const listenChangeKey = useCallback((value, id) => { + modifyKey(value, id); + }, []); - const listenChangeVisibility = useCallback((value, id) => { - modifyVisibility(value, id); - }, []); + const listenChangeVisibility = useCallback((value, id) => { + modifyVisibility(value, id); + }, []); - /** - * Save the changes of environment variables and push them to the database - */ - const savePush = async () => { - // Format the new object with environment variables - let obj = Object.assign( - {}, - ...data.map((row) => ({ [row[2]]: [row[3], row[4]] })) - ); + /** + * Save the changes of environment variables and push them to the database + */ + const savePush = async () => { + // Format the new object with environment variables + let obj = Object.assign( + {}, + ...data.map((row) => ({ [row[2]]: [row[3], row[4]] })) + ); - // Once "Save changed is clicked", disable that button - setButtonReady(false); - pushKeys(obj, router.query.id, env); + // Once "Save changed is clicked", disable that button + setButtonReady(false); + pushKeys(obj, router.query.id, env); - /** - * Check which integrations are active for this project and environment - * If there are any, update environment variables for those integrations - */ - let integrations = await getWorkspaceIntegrations({ - workspaceId: router.query.id, - }); - integrations.map(async (integration) => { - if ( - envMapping[env] == integration.environment && - integration.isActive == true - ) { - let objIntegration = Object.assign( - {}, - ...data.map((row) => ({ [row[2]]: row[3] })) - ); - await pushKeysIntegration({ - obj: objIntegration, - integrationId: integration._id, - }); - } - }); + /** + * Check which integrations are active for this project and environment + * If there are any, update environment variables for those integrations + */ + let integrations = await getWorkspaceIntegrations({ + workspaceId: router.query.id, + }); + integrations.map(async (integration) => { + if ( + envMapping[env] == integration.environment && + integration.isActive == true + ) { + let objIntegration = Object.assign( + {}, + ...data.map((row) => ({ [row[2]]: row[3] })) + ); + await pushKeysIntegration({ + obj: objIntegration, + integrationId: integration._id, + }); + } + }); - // If this user has never saved environment variables before, show them a prompt to read docs - if (!hasUserEverPushed) { - setCheckDocsPopUpVisible(true); - await registerUserAction({ action: "first_time_secrets_pushed" }); - } - }; + // If this user has never saved environment variables before, show them a prompt to read docs + if (!hasUserEverPushed) { + setCheckDocsPopUpVisible(true); + await registerUserAction({ action: "first_time_secrets_pushed" }); + } + }; - const addData = (newData) => { - setData(data.concat(newData)); - setButtonReady(true); - }; + const addData = (newData) => { + setData(data.concat(newData)); + setButtonReady(true); + }; - const changeBlurred = () => { - setBlurred(!blurred); - }; + const changeBlurred = () => { + setBlurred(!blurred); + }; - // This function downloads the secrets as a .env file - const download = () => { - const file = data - .map((item) => [item[2], item[3]].join("=")) - .join("\n"); - const blob = new Blob([file]); - const fileDownloadUrl = URL.createObjectURL(blob); - let alink = document.createElement("a"); - alink.href = fileDownloadUrl; - alink.download = envMapping[env] + ".env"; - alink.click(); - }; + // This function downloads the secrets as a .env file + const download = () => { + const file = data.map((item) => [item[2], item[3]].join("=")).join("\n"); + const blob = new Blob([file]); + const fileDownloadUrl = URL.createObjectURL(blob); + let alink = document.createElement("a"); + alink.href = fileDownloadUrl; + alink.download = envMapping[env] + ".env"; + alink.click(); + }; - const deleteCertainRow = (id) => { - deleteRow(id); - }; + const deleteCertainRow = (id) => { + deleteRow(id); + }; - /** - * This function copies the project id to the clipboard - */ - function copyToClipboard() { - var copyText = document.getElementById("myInput"); + /** + * This function copies the project id to the clipboard + */ + function copyToClipboard() { + var copyText = document.getElementById("myInput"); - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices + copyText.select(); + copyText.setSelectionRange(0, 99999); // For mobile devices - navigator.clipboard.writeText(copyText.value); + navigator.clipboard.writeText(copyText.value); - setProjectIdCopied(true); - setTimeout(() => setProjectIdCopied(false), 2000); - } + setProjectIdCopied(true); + setTimeout(() => setProjectIdCopied(false), 2000); + } - return data ? ( -
- - Secrets - - - - - -
-
- - {checkDocsPopUpVisible && ( - - )} -
-
-

Secrets

- {data?.length == 0 && ( - - )} -
-
-
-

- Project ID: -

- -
- - - Click to Copy - -
-
- {(data?.length !== 0 || buttonReady) && ( -
-
- )} -
-
-
-
-
- {data?.length !== 0 && ( - <> - -
- - - setSearchKeys( - e.target.value - ) - } - placeholder={"Search keys..."} - /> -
-
-
-
-
-
-
-
-
- - )} -
-
- {data?.length !== 0 ? ( -
-
-
- {/* */} -
-

- Personal -

-
- - - Personal keys are only - visible to you - -
-
-
-
- {data - .filter( - (keyPair) => - keyPair[2] - .toLowerCase() - .includes( - searchKeys.toLowerCase() - ) && - keyPair[4] == "personal" - ) - .sort((a, b) => - sortMethod == "alphabetical" - ? a[2].localeCompare(b[2]) - : b[2].localeCompare(a[2]) - ) - ?.map((keyPair, index) => ( - - ))} -
-
-
8 ? "h-3/4" : "h-min" - }`} - > -
- {/* */} -
-

- Shared -

-
- - - Shared keys are visible to - your whole team - -
-
-
-
- {data - .filter( - (keyPair) => - keyPair[2] - .toLowerCase() - .includes( - searchKeys.toLowerCase() - ) && - keyPair[4] == "shared" - ) - .sort((a, b) => - sortMethod == "alphabetical" - ? a[2].localeCompare(b[2]) - : b[2].localeCompare(a[2]) - ) - ?.map((keyPair, index) => ( - - ))} -
-
-
- -
-
- ) : ( -
- {fileState.message != - "There's nothing to pull" && - fileState.message != undefined && ( - - )} - {(fileState.message == - "There's nothing to pull" || - fileState.message == undefined) && - isKeyAvailable && ( - - )} - {fileState.message == - "Failed membership validation for workspace" && ( -

- You are not authorized to view this - project. -

- )} - {fileState.message == - "Access needed to pull the latest file" || - (!isKeyAvailable && ( - <> - -

- To view this file, contact your - administrator for permission. -

-

- They need to grant you access in - the team tab. -

- - ))} -
- )} -
-
-
-
- ) : ( -
-
- loading animation -
- ); + return data ? ( +
+ + Secrets + + + + + +
+
+ + {checkDocsPopUpVisible && ( + + )} +
+
+

Secrets

+ {data?.length == 0 && ( + + )} +
+
+
+

Project ID:

+ +
+ + + Click to Copy + +
+
+ {(data?.length !== 0 || buttonReady) && ( +
+
+ )} +
+
+
+
+
+ {data?.length !== 0 && ( + <> + +
+ + setSearchKeys(e.target.value)} + placeholder={"Search keys..."} + /> +
+
+
+
+
+
+
+
+
+ + )} +
+
+ {data?.length !== 0 ? ( +
+
+
+ {/* */} +
+

Personal

+
+ + + Personal keys are only visible to you + +
+
+
+
+ {data + .filter( + (keyPair) => + keyPair[2] + .toLowerCase() + .includes(searchKeys.toLowerCase()) && + keyPair[4] == "personal" + ) + .sort((a, b) => + sortMethod == "alphabetical" + ? a[2].localeCompare(b[2]) + : b[2].localeCompare(a[2]) + ) + ?.map((keyPair, index) => ( + + ))} +
+
+
8 ? "h-3/4" : "h-min" + }`} + > +
+ {/* */} +
+

Shared

+
+ + + Shared keys are visible to your whole team + +
+
+
+
+ {data + .filter( + (keyPair) => + keyPair[2] + .toLowerCase() + .includes(searchKeys.toLowerCase()) && + keyPair[4] == "shared" + ) + .sort((a, b) => + sortMethod == "alphabetical" + ? a[2].localeCompare(b[2]) + : b[2].localeCompare(a[2]) + ) + ?.map((keyPair, index) => ( + + ))} +
+
+
+ +
+
+ ) : ( +
+ {fileState.message != "There's nothing to pull" && + fileState.message != undefined && ( + + )} + {(fileState.message == "There's nothing to pull" || + fileState.message == undefined) && + isKeyAvailable && ( + + )} + {fileState.message == + "Failed membership validation for workspace" && ( +

You are not authorized to view this project.

+ )} + {fileState.message == "Access needed to pull the latest file" || + (!isKeyAvailable && ( + <> + +

+ To view this file, contact your administrator for + permission. +

+

+ They need to grant you access in the team tab. +

+ + ))} +
+ )} +
+
+
+
+ ) : ( +
+
+ loading animation +
+ ); } Dashboard.requireAuth = true; diff --git a/frontend/pages/heroku.js b/frontend/pages/heroku.js index 274816b1f..82947b1b9 100644 --- a/frontend/pages/heroku.js +++ b/frontend/pages/heroku.js @@ -5,34 +5,32 @@ const queryString = require("query-string"); import AuthorizeIntegration from "./api/integrations/authorizeIntegration"; export default function Heroku() { - const router = useRouter(); - const parsedUrl = queryString.parse(router.asPath.split("?")[1]); - const code = parsedUrl.code; - const state = parsedUrl.state; + const router = useRouter(); + const parsedUrl = queryString.parse(router.asPath.split("?")[1]); + const code = parsedUrl.code; + const state = parsedUrl.state; - /** - * Here we forward to the default workspace if a user opens this url - */ - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(async () => { - try { - if (state == localStorage.getItem("latestCSRFToken")) { - await AuthorizeIntegration({ - workspaceId: localStorage.getItem("projectData.id"), - code, - integration: "heroku", - }); - router.push( - "/integrations/" + localStorage.getItem("projectData.id") - ); - } - } catch (error) { - console.log("Error - Not logged in yet"); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + /** + * Here we forward to the default workspace if a user opens this url + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(async () => { + try { + if (state == localStorage.getItem("latestCSRFToken")) { + await AuthorizeIntegration({ + workspaceId: localStorage.getItem("projectData.id"), + code, + integration: "heroku", + }); + router.push("/integrations/" + localStorage.getItem("projectData.id")); + } + } catch (error) { + console.log("Error - Not logged in yet"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return
; + return
; } Heroku.requireAuth = true; diff --git a/frontend/pages/index.js b/frontend/pages/index.js index 2cb7ce058..5fc3aab22 100644 --- a/frontend/pages/index.js +++ b/frontend/pages/index.js @@ -2,12 +2,12 @@ import React, { useEffect } from "react"; import { useRouter } from "next/router"; export default function Home() { - const router = useRouter(); + const router = useRouter(); - useEffect(() => { - router.push("/login"); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(() => { + router.push("/login"); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return
; + return
; } diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index 9ae9c1c7c..3689c24ff 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -1,12 +1,12 @@ -import React, { useEffect,useState } from "react"; +import React, { useEffect, useState } from "react"; import Head from "next/head"; import Image from "next/image"; import { useRouter } from "next/router"; import { - faArrowRight, - faCheck, - faRotate, - faX, + faArrowRight, + faCheck, + faRotate, + faX, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -28,408 +28,345 @@ import startIntegration from "../api/integrations/StartIntegration"; const crypto = require("crypto"); const envMapping = { - Development: "dev", - Staging: "staging", - Production: "prod", - Testing: "test", + Development: "dev", + Staging: "staging", + Production: "prod", + Testing: "test", }; const reverseEnvMapping = { - dev: "Development", - staging: "Staging", - prod: "Production", - test: "Testing", + dev: "Development", + staging: "Staging", + prod: "Production", + test: "Testing", }; const Integration = ({ projectIntegration }) => { - const [integrationEnvironment, setIntegrationEnvironment] = useState( - reverseEnvMapping[projectIntegration.environment] - ); - const [fileState, setFileState] = useState([]); - const [data, setData] = useState(); - const [isKeyAvailable, setIsKeyAvailable] = useState(true); - const router = useRouter(); - const [apps, setApps] = useState([]); - const [integrationApp, setIntegrationApp] = useState( - projectIntegration.app ? projectIntegration.app : apps[0] - ); + const [integrationEnvironment, setIntegrationEnvironment] = useState( + reverseEnvMapping[projectIntegration.environment] + ); + const [fileState, setFileState] = useState([]); + const [data, setData] = useState(); + const [isKeyAvailable, setIsKeyAvailable] = useState(true); + const router = useRouter(); + const [apps, setApps] = useState([]); + const [integrationApp, setIntegrationApp] = useState( + projectIntegration.app ? projectIntegration.app : apps[0] + ); - useEffect(async () => { - const tempHerokuApps = await getIntegrationApps({ - integrationAuthId: projectIntegration.integrationAuth, - }); - const tempHerokuAppNames = tempHerokuApps.map((app) => app.name); - setApps(tempHerokuAppNames); - setIntegrationApp( - projectIntegration.app - ? projectIntegration.app - : tempHerokuAppNames[0] - ); - }, []); + useEffect(async () => { + const tempHerokuApps = await getIntegrationApps({ + integrationAuthId: projectIntegration.integrationAuth, + }); + const tempHerokuAppNames = tempHerokuApps.map((app) => app.name); + setApps(tempHerokuAppNames); + setIntegrationApp( + projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0] + ); + }, []); - return ( -
-
-
-
-
- ENVIRONMENT -
- -
- -
-
- INTEGRATION -
-
- {projectIntegration.integration - .charAt(0) - .toUpperCase() + - projectIntegration.integration.slice(1)} -
-
-
-
- HEROKU APP -
- -
-
-
- {projectIntegration.isActive ? ( -
- -
- In Sync -
-
- ) : ( -
-
-
- - ); + let obj = Object.assign( + {}, + ...currentSecrets.map((row) => ({ + [row[2]]: row[3], + })) + ); + await pushKeysIntegration({ + obj, + integrationId: projectIntegration._id, + }); + router.reload(); + } + }} + color="mineshaft" + size="md" + /> + )} +
+
+ + + + ); }; export default function Integrations() { - const [integrations, setIntegrations] = useState(); - const [projectIntegrations, setProjectIntegrations] = useState(); - const [authorizations, setAuthorizations] = useState(); - const router = useRouter(); - const [csrfToken, setCsrfToken] = useState(""); + const [integrations, setIntegrations] = useState(); + const [projectIntegrations, setProjectIntegrations] = useState(); + const [authorizations, setAuthorizations] = useState(); + const router = useRouter(); + const [csrfToken, setCsrfToken] = useState(""); - useEffect(async () => { - const tempCSRFToken = crypto.randomBytes(16).toString("hex"); - setCsrfToken(tempCSRFToken); - localStorage.setItem("latestCSRFToken", tempCSRFToken); + useEffect(async () => { + const tempCSRFToken = crypto.randomBytes(16).toString("hex"); + setCsrfToken(tempCSRFToken); + localStorage.setItem("latestCSRFToken", tempCSRFToken); - let projectAuthorizations = await getWorkspaceAuthorizations({ - workspaceId: router.query.id, - }); - setAuthorizations(projectAuthorizations); + let projectAuthorizations = await getWorkspaceAuthorizations({ + workspaceId: router.query.id, + }); + setAuthorizations(projectAuthorizations); - const projectIntegrations = await getWorkspaceIntegrations({ - workspaceId: router.query.id, - }); - setProjectIntegrations(projectIntegrations); + const projectIntegrations = await getWorkspaceIntegrations({ + workspaceId: router.query.id, + }); + setProjectIntegrations(projectIntegrations); - try { - const integrationsData = await getIntegrations(); - setIntegrations(integrationsData); - } catch (error) { - console.log("Error", error); - } - }, []); + try { + const integrationsData = await getIntegrations(); + setIntegrations(integrationsData); + } catch (error) { + console.log("Error", error); + } + }, []); - return integrations ? ( -
- - Dashboard - - - - - -
-
- -
-
-

- Current Project Integrations -

-
-

- Manage your integrations of Infisical with - third-party services. -

-
- {projectIntegrations.length > 0 ? ( - projectIntegrations.map((projectIntegration) => ( - - )) - ) : ( -
-
-
- You {"don't"} have any integrations set up - yet. When you do, they will appear here. -
-
- To start, click on any of the options below. - It takes 5 clicks to set up. -
-
-
- )} -
-
-

- Available Integrations -

-
-

- Click on the itegration you want to connect. This - will let your environment variables flow - automatically into selected third-party services. -

-

- Note: during an integration with Heroku, for - security reasons, it is impossible to maintain - end-to-end encryption. In theory, this lets - Infisical decrypt yor environment variables. In - practice, we can assure you that this will never be - done, and it allows us to protect your secrets from - bad actors online. The core Infisical service will - always stay end-to-end encrypted. With any - questions, reach out support@infisical.com. -

-
-
- {Object.keys(integrations).map((integration) => ( -
- - integration logo - {integrations[integration].name.split(" ") - .length > 2 ? ( -
-
- { - integrations[ - integration - ].name.split(" ")[0] - } -
-
- { - integrations[ - integration - ].name.split(" ")[1] - }{" "} - { - integrations[ - integration - ].name.split(" ")[2] - } -
-
- ) : ( -
- {integrations[integration].name} -
- )} -
- {["Heroku"].includes( - integrations[integration].name - ) && - authorizations - .map( - (authorization) => - authorization.integration - ) - .includes( - integrations[ - integration - ].name.toLowerCase() - ) && ( -
-
{ - deleteIntegrationAuth({ - integrationAuthId: - authorizations - .filter( - ( - authorization - ) => - authorization.integration == - integrations[ - integration - ].name.toLowerCase() - ) - .map( - ( - authorization - ) => - authorization._id - )[0], - }); - router.reload(); - }} - className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200" - > - - Revoke -
-
- - Authorized -
-
- )} - {!["Heroku"].includes( - integrations[integration].name - ) && ( -
-
- Coming Soon -
-
- )} -
- ))} -
-
-
-
- ) : ( -
-
- loading animation -
- ); + return integrations ? ( +
+ + Dashboard + + + + + +
+
+ +
+
+

Current Project Integrations

+
+

+ Manage your integrations of Infisical with third-party services. +

+
+ {projectIntegrations.length > 0 ? ( + projectIntegrations.map((projectIntegration) => ( + + )) + ) : ( +
+
+
+ You {"don't"} have any integrations set up yet. When you do, + they will appear here. +
+
+ To start, click on any of the options below. It takes 5 clicks + to set up. +
+
+
+ )} +
+
+

Available Integrations

+
+

+ Click on the itegration you want to connect. This will let your + environment variables flow automatically into selected third-party + services. +

+

+ Note: during an integration with Heroku, for security reasons, it + is impossible to maintain end-to-end encryption. In theory, this + lets Infisical decrypt yor environment variables. In practice, we + can assure you that this will never be done, and it allows us to + protect your secrets from bad actors online. The core Infisical + service will always stay end-to-end encrypted. With any questions, + reach out support@infisical.com. +

+
+
+ {Object.keys(integrations).map((integration) => ( +
+ + integration logo + {integrations[integration].name.split(" ").length > 2 ? ( +
+
{integrations[integration].name.split(" ")[0]}
+
+ {integrations[integration].name.split(" ")[1]}{" "} + {integrations[integration].name.split(" ")[2]} +
+
+ ) : ( +
+ {integrations[integration].name} +
+ )} +
+ {["Heroku"].includes(integrations[integration].name) && + authorizations + .map((authorization) => authorization.integration) + .includes(integrations[integration].name.toLowerCase()) && ( +
+
{ + deleteIntegrationAuth({ + integrationAuthId: authorizations + .filter( + (authorization) => + authorization.integration == + integrations[integration].name.toLowerCase() + ) + .map((authorization) => authorization._id)[0], + }); + router.reload(); + }} + className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200" + > + + Revoke +
+
+ + Authorized +
+
+ )} + {!["Heroku"].includes(integrations[integration].name) && ( +
+
+ Coming Soon +
+
+ )} +
+ ))} +
+
+
+
+ ) : ( +
+
+ loading animation +
+ ); } Integrations.requireAuth = true; diff --git a/frontend/pages/login.js b/frontend/pages/login.js index aed4a7e94..a466c7123 100644 --- a/frontend/pages/login.js +++ b/frontend/pages/login.js @@ -1,4 +1,4 @@ -import React, { useEffect,useState } from "react"; +import React, { useEffect, useState } from "react"; import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; @@ -14,130 +14,124 @@ import attemptLogin from "~/utilities/attemptLogin"; import getWorkspaces from "./api/workspace/getWorkspaces"; export default function Login() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [errorLogin, setErrorLogin] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const router = useRouter(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [errorLogin, setErrorLogin] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); - useEffect(async () => { - let userWorkspace; - try { - const userWorkspaces = await getWorkspaces(); - userWorkspace = userWorkspaces[0]._id; - router.push("/dashboard/" + userWorkspace); - } catch (error) { - console.log("Error - Not logged in yet"); - } - }, []); + useEffect(async () => { + let userWorkspace; + try { + const userWorkspaces = await getWorkspaces(); + userWorkspace = userWorkspaces[0]._id; + router.push("/dashboard/" + userWorkspace); + } catch (error) { + console.log("Error - Not logged in yet"); + } + }, []); - /** - * This function check if the user entered the correct credentials and should be allowed to log in. - */ - const loginCheck = async () => { - setIsLoading(true); - await attemptLogin( - email, - password, - setErrorLogin, - router, - false, - true - ).then(() => { - setTimeout(function () { - setIsLoading(false); - }, 2000); - }); - }; + /** + * This function check if the user entered the correct credentials and should be allowed to log in. + */ + const loginCheck = async () => { + setIsLoading(true); + await attemptLogin( + email, + password, + setErrorLogin, + router, + false, + true + ).then(() => { + setTimeout(function () { + setIsLoading(false); + }, 2000); + }); + }; - return ( -
- - Login - - - - - - -
- long logo -
- -
-

- Log In -

-
-

- Need an Infisical account? -

-
-
- - - -
-
- -
-
- -
- {errorLogin && ( - - )} -
-
-
-
- {/*
+ return ( +
+ + Login + + + + + + +
+ long logo +
+ +
+

+ Log In +

+
+

+ Need an Infisical account? +

+
+
+ + + +
+
+ +
+
+ +
+ {errorLogin && } +
+
+
+
+ {/*

I may have forgotten my password.

*/} -
- {false && ( -
- - We are experiencing minor technical difficulties. We are - working on solving it right now. Please come back in a few - minutes. -
- )} -
- ); +
+ {false && ( +
+ + We are experiencing minor technical difficulties. We are working on + solving it right now. Please come back in a few minutes. +
+ )} +
+ ); } diff --git a/frontend/pages/noprojects.js b/frontend/pages/noprojects.js index 4634ac2c6..2e55b0b4c 100644 --- a/frontend/pages/noprojects.js +++ b/frontend/pages/noprojects.js @@ -3,18 +3,22 @@ import { faFolderOpen } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function NoProjects() { - return ( -
- -
- You are not part of any projects in this organization yet. When you do, they will appear here. -
-
- Create a new project, or ask - other organization members to give you neccessary permissions. -
-
- ); + return ( +
+ +
+ You are not part of any projects in this organization yet. When you do, + they will appear here. +
+
+ Create a new project, or ask other organization members to give you + neccessary permissions. +
+
+ ); } NoProjects.requireAuth = true; diff --git a/frontend/pages/requestnewinvite.js b/frontend/pages/requestnewinvite.js index 0d6b46ee4..be6159988 100644 --- a/frontend/pages/requestnewinvite.js +++ b/frontend/pages/requestnewinvite.js @@ -2,21 +2,21 @@ import React from "react"; import Head from "next/head"; export default function Activity() { - return ( -
- - Request a New Invite - - -
-

Oops.

-

Your invite has expired.

-

Ask the administrator for a new one.

-

- Note: If it still {"doesn't work"}, please reach out to us - at support@infisical.com -

-
-
- ); + return ( +
+ + Request a New Invite + + +
+

Oops.

+

Your invite has expired.

+

Ask the administrator for a new one.

+

+ Note: If it still {"doesn't work"}, please reach out to us at + support@infisical.com +

+
+
+ ); } diff --git a/frontend/pages/settings/billing/[id].js b/frontend/pages/settings/billing/[id].js index cd5bc540e..609648b9b 100644 --- a/frontend/pages/settings/billing/[id].js +++ b/frontend/pages/settings/billing/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect,useState } from "react"; +import React, { useEffect, useState } from "react"; import Head from "next/head"; import Plan from "~/components/billing/Plan"; @@ -9,102 +9,101 @@ import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscript import getOrganizationUsers from "../../api/organization/GetOrgUsers"; export default function SettingsBilling() { - let [currentPlan, setCurrentPlan] = useState(""); - let [numUsers, setNumUsers] = useState(""); + let [currentPlan, setCurrentPlan] = useState(""); + let [numUsers, setNumUsers] = useState(""); - const plans = [ - { - key: 1, - name: "Starter", - price: "Free", - priceExplanation: "up to 5 team members", - text: "Manage any project with 5 members for free!", - subtext: "$5 per member/month afterwards.", - buttonTextMain: "Downgrade", - buttonTextSecondary: "Learn More", - current: currentPlan == STRIPE_PRODUCT_STARTER, - }, - { - key: 2, - name: "Professional", - price: "$9", - priceExplanation: "/member/month", - subtext: "Includes unlimited projects & members.", - text: "Keep up with key management as you grow.", - buttonTextMain: "Upgrade", - buttonTextSecondary: "Learn More", - current: currentPlan == STRIPE_PRODUCT_PRO, - }, - { - key: 3, - name: "Enterprise", - price: "Custom Pricing", - text: "More control for advanced organizations.", - buttonTextMain: "Schedule a Demo", - buttonTextSecondary: "Learn More", - current: false, - }, - ]; + const plans = [ + { + key: 1, + name: "Starter", + price: "Free", + priceExplanation: "up to 5 team members", + text: "Manage any project with 5 members for free!", + subtext: "$5 per member/month afterwards.", + buttonTextMain: "Downgrade", + buttonTextSecondary: "Learn More", + current: currentPlan == STRIPE_PRODUCT_STARTER, + }, + { + key: 2, + name: "Professional", + price: "$9", + priceExplanation: "/member/month", + subtext: "Includes unlimited projects & members.", + text: "Keep up with key management as you grow.", + buttonTextMain: "Upgrade", + buttonTextSecondary: "Learn More", + current: currentPlan == STRIPE_PRODUCT_PRO, + }, + { + key: 3, + name: "Enterprise", + price: "Custom Pricing", + text: "More control for advanced organizations.", + buttonTextMain: "Schedule a Demo", + buttonTextSecondary: "Learn More", + current: false, + }, + ]; - useEffect(async () => { - const subscriptions = await getOrganizationSubscriptions({ - orgId: localStorage.getItem("orgData.id"), - }); - - setCurrentPlan(subscriptions.data[0].plan.product); - const orgUsers = await getOrganizationUsers({ - orgId: localStorage.getItem("orgData.id"), - }); - setNumUsers(orgUsers.length); - }, []); + useEffect(async () => { + const subscriptions = await getOrganizationSubscriptions({ + orgId: localStorage.getItem("orgData.id"), + }); - return ( -
- - Settings - Billing - - -
-
- -
-
-

- Usage & Billing -

-

- View and manage your {"organization's"}{" "} - subscription here. -

-
-
-
-

Subscription

-
- {plans.map((plan) => ( - - ))} -
-

Current Usage

-
-
-

{numUsers}

-

- {numUsers > 1 - ? "Organization members" - : "Organization member"} -

-
- {/*
+ setCurrentPlan(subscriptions.data[0].plan.product); + const orgUsers = await getOrganizationUsers({ + orgId: localStorage.getItem("orgData.id"), + }); + setNumUsers(orgUsers.length); + }, []); + + return ( +
+ + Settings - Billing + + +
+
+ +
+
+

+ Usage & Billing +

+

+ View and manage your {"organization's"} subscription here. +

+
+
+
+

Subscription

+
+ {plans.map((plan) => ( + + ))} +
+

Current Usage

+
+
+

{numUsers}

+

+ {numUsers > 1 + ? "Organization members" + : "Organization member"} +

+
+ {/*

1

Organization projects

*/} -
-
-
-
-
- ); +
+
+
+
+
+ ); } SettingsBilling.requireAuth = true; diff --git a/frontend/pages/settings/org/[id].js b/frontend/pages/settings/org/[id].js index 7030bd0b1..ca765abe7 100644 --- a/frontend/pages/settings/org/[id].js +++ b/frontend/pages/settings/org/[id].js @@ -1,10 +1,10 @@ -import React, { useEffect,useState } from "react"; +import React, { useEffect, useState } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; import { - faMagnifyingGlass, - faPlus, - faX, + faMagnifyingGlass, + faPlus, + faX, } from "@fortawesome/free-solid-svg-icons"; import { faCheck } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -29,340 +29,313 @@ import deleteWorkspace from "../../api/workspace/deleteWorkspace"; import getWorkspaces from "../../api/workspace/getWorkspaces"; export default function SettingsOrg() { - const [buttonReady, setButtonReady] = useState(false); - const router = useRouter(); - const [orgName, setOrgName] = useState(""); - const [emailUser, setEmailUser] = useState(""); - const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = - useState(""); - const [searchUsers, setSearchUsers] = useState(""); - const [workspaceId, setWorkspaceId] = useState(""); - const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] = - useState(false); - const [isAddUserOpen, setIsAddUserOpen] = useState( - router.asPath.split("?")[1] == "invite" - ); - const [incidentContacts, setIncidentContacts] = useState([]); - const [searchIncidentContact, setSearchIncidentContact] = useState(""); - const [userList, setUserList] = useState(); - const [personalEmail, setPersonalEmail] = useState(""); - let workspaceIdTemp; - const [email, setEmail] = useState(""); - const [currentPlan, setCurrentPlan] = useState(""); + const [buttonReady, setButtonReady] = useState(false); + const router = useRouter(); + const [orgName, setOrgName] = useState(""); + const [emailUser, setEmailUser] = useState(""); + const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(""); + const [searchUsers, setSearchUsers] = useState(""); + const [workspaceId, setWorkspaceId] = useState(""); + const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] = + useState(false); + const [isAddUserOpen, setIsAddUserOpen] = useState( + router.asPath.split("?")[1] == "invite" + ); + const [incidentContacts, setIncidentContacts] = useState([]); + const [searchIncidentContact, setSearchIncidentContact] = useState(""); + const [userList, setUserList] = useState(); + const [personalEmail, setPersonalEmail] = useState(""); + let workspaceIdTemp; + const [email, setEmail] = useState(""); + const [currentPlan, setCurrentPlan] = useState(""); - useEffect(async () => { - let org = await getOrganization({ - orgId: localStorage.getItem("orgData.id"), - }); - let orgData = org; - setOrgName(orgData.name); - let incidentContactsData = await getIncidentContacts( - localStorage.getItem("orgData.id") - ); + useEffect(async () => { + let org = await getOrganization({ + orgId: localStorage.getItem("orgData.id"), + }); + let orgData = org; + setOrgName(orgData.name); + let incidentContactsData = await getIncidentContacts( + localStorage.getItem("orgData.id") + ); - setIncidentContacts( - incidentContactsData?.map((contact) => contact.email) - ); + setIncidentContacts(incidentContactsData?.map((contact) => contact.email)); - const user = await getUser(); - setPersonalEmail(user.email); + const user = await getUser(); + setPersonalEmail(user.email); - workspaceIdTemp = router.query.id; - let orgUsers = await getOrganizationUsers({ - orgId: localStorage.getItem("orgData.id"), - }); - setUserList( - orgUsers.map((user) => ({ - key: guidGenerator(), - firstName: user.user?.firstName, - lastName: user.user?.lastName, - email: - user.user?.email == null - ? user.inviteEmail - : user.user?.email, - role: user?.role, - status: user?.status, - userId: user.user?._id, - membershipId: user._id, - publicKey: user.user?.publicKey, - })) - ); - const subscriptions = await getOrganizationSubscriptions({ - orgId: localStorage.getItem("orgData.id"), - }); - setCurrentPlan(subscriptions.data[0].plan.product); - }, []); + workspaceIdTemp = router.query.id; + let orgUsers = await getOrganizationUsers({ + orgId: localStorage.getItem("orgData.id"), + }); + setUserList( + orgUsers.map((user) => ({ + key: guidGenerator(), + firstName: user.user?.firstName, + lastName: user.user?.lastName, + email: user.user?.email == null ? user.inviteEmail : user.user?.email, + role: user?.role, + status: user?.status, + userId: user.user?._id, + membershipId: user._id, + publicKey: user.user?.publicKey, + })) + ); + const subscriptions = await getOrganizationSubscriptions({ + orgId: localStorage.getItem("orgData.id"), + }); + setCurrentPlan(subscriptions.data[0].plan.product); + }, []); - const modifyOrgName = (newName) => { - setButtonReady(true); - setOrgName(newName); - }; + const modifyOrgName = (newName) => { + setButtonReady(true); + setOrgName(newName); + }; - const submitChanges = (newOrgName) => { - renameOrg(localStorage.getItem("orgData.id"), newOrgName); - setButtonReady(false); - }; + const submitChanges = (newOrgName) => { + renameOrg(localStorage.getItem("orgData.id"), newOrgName); + setButtonReady(false); + }; - useEffect(async () => { - setWorkspaceId(router.query.id); - }, []); + useEffect(async () => { + setWorkspaceId(router.query.id); + }, []); - function closeAddUserModal() { - setIsAddUserOpen(false); - } + function closeAddUserModal() { + setIsAddUserOpen(false); + } - function closeAddIncidentContactModal() { - setIsAddIncidentContactOpen(false); - } + function closeAddIncidentContactModal() { + setIsAddIncidentContactOpen(false); + } - function openAddUserModal() { - setIsAddUserOpen(true); - } + function openAddUserModal() { + setIsAddUserOpen(true); + } - function openAddIncidentContactModal() { - setIsAddIncidentContactOpen(true); - } + function openAddIncidentContactModal() { + setIsAddIncidentContactOpen(true); + } - async function submitAddUserModal(email) { - await addUserToOrg(email, localStorage.getItem("orgData.id")); - setEmail(""); - setIsAddUserOpen(false); - router.reload(); - } + async function submitAddUserModal(email) { + await addUserToOrg(email, localStorage.getItem("orgData.id")); + setEmail(""); + setIsAddUserOpen(false); + router.reload(); + } - const deleteIncidentContactFully = (incidentContact) => { - setIncidentContacts( - incidentContacts.filter((contact) => contact != incidentContact) - ); - deleteIncidentContact( - localStorage.getItem("orgData.id"), - incidentContact - ); - }; + const deleteIncidentContactFully = (incidentContact) => { + setIncidentContacts( + incidentContacts.filter((contact) => contact != incidentContact) + ); + deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact); + }; - /** - * This function deleted a workspace. - * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete - * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. - * It then deletes the workspace and forwards the user to another aviable workspace. - */ - const executeDeletingWorkspace = async () => { - let userWorkspaces = await getWorkspaces(); + /** + * This function deleted a workspace. + * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete + * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. + * It then deletes the workspace and forwards the user to another aviable workspace. + */ + const executeDeletingWorkspace = async () => { + let userWorkspaces = await getWorkspaces(); - if (userWorkspaces.length > 1) { - if ( - userWorkspaces.filter( - (workspace) => workspace._id == router.query.id - )[0].name == workspaceToBeDeletedName - ) { - await deleteWorkspace(router.query.id); - let userWorkspaces = await getWorkspaces(); - router.push("/dashboard/" + userWorkspaces[0]._id); - } - } - }; + if (userWorkspaces.length > 1) { + if ( + userWorkspaces.filter( + (workspace) => workspace._id == router.query.id + )[0].name == workspaceToBeDeletedName + ) { + await deleteWorkspace(router.query.id); + let userWorkspaces = await getWorkspaces(); + router.push("/dashboard/" + userWorkspaces[0]._id); + } + } + }; - return ( -
- - Settings - - -
-
- - -
-
-

- Organization Settings -

-

- View and manage your organization here. -

-
-
-
-
-
-
-
-

- Display Name -

- -
-
-
-
-
-
-
-
-
-

- Organization Members -

-

- Manage members of your organization. These users - could afterwards be formed into projects. -

- - {/* */} -
-
- - - setSearchUsers(e.target.value) - } - placeholder={"Search members..."} - /> -
-
-
-
- {userList && ( -
- -
- )} -
+ return ( +
+ + Settings + + +
+
+ + +
+
+

+ Organization Settings +

+

+ View and manage your organization here. +

+
+
+
+
+
+
+
+

+ Display Name +

+ +
+
+
+
+
+
+
+
+
+

+ Organization Members +

+

+ Manage members of your organization. These users could + afterwards be formed into projects. +

+ + {/* */} +
+
+ + setSearchUsers(e.target.value)} + placeholder={"Search members..."} + /> +
+
+
+
+ {userList && ( +
+ +
+ )} +
-
-
-
-

- Incident Contacts -

-

- These contacts will be notified in the - unlikely event of a severe incident. -

-
-
-
-
-
- - - setSearchIncidentContact(e.target.value) - } - placeholder={"Search..."} - /> -
- {incidentContacts?.filter((email) => - email.includes(searchIncidentContact) - ).length > 0 ? ( - incidentContacts - .filter((email) => - email.includes(searchIncidentContact) - ) - .map((contact) => ( -
-

- {contact} -

-
-
-
- )) - ) : ( -
-

- No incident contacts found. -

-
- )} -
+
+
+
+

+ Incident Contacts +

+

+ These contacts will be notified in the unlikely event of a + severe incident. +

+
+
+
+
+
+ + setSearchIncidentContact(e.target.value)} + placeholder={"Search..."} + /> +
+ {incidentContacts?.filter((email) => + email.includes(searchIncidentContact) + ).length > 0 ? ( + incidentContacts + .filter((email) => email.includes(searchIncidentContact)) + .map((contact) => ( +
+

{contact}

+
+
+
+ )) + ) : ( +
+

No incident contacts found.

+
+ )} +
- {/*
+ {/*

Danger Zone

@@ -397,11 +370,11 @@ export default function SettingsOrg() { have more than one.

*/} -
-
-
-
- ); +
+
+
+
+ ); } SettingsOrg.requireAuth = true; diff --git a/frontend/pages/settings/personal/[id].js b/frontend/pages/settings/personal/[id].js index 9c4ad59bd..928d426b0 100644 --- a/frontend/pages/settings/personal/[id].js +++ b/frontend/pages/settings/personal/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect,useState } from "react"; +import React, { useEffect, useState } from "react"; import Head from "next/head"; import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -7,57 +7,54 @@ import Button from "~/components/basic/buttons/Button"; import InputField from "~/components/basic/InputField"; import NavHeader from "~/components/navigation/NavHeader"; import changePassword from "~/components/utilities/cryptography/changePassword"; -import passwordCheck from "~/utilities/checks/PasswordCheck"; import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; +import passwordCheck from "~/utilities/checks/PasswordCheck"; import getUser from "../../api/user/getUser"; export default function PersonalSettings() { - const [personalEmail, setPersonalEmail] = useState(""); - const [personalName, setPersonalName] = useState(""); - const [passwordErrorLength, setPasswordErrorLength] = useState(false); - const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); - const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); - const [currentPasswordError, setCurrentPasswordError] = useState(false); - const [currentPassword, setCurrentPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [backupPassword, setBackupPassword] = useState(""); - const [passwordChanged, setPasswordChanged] = useState(false); - const [backupKeyIssued, setBackupKeyIssued] = useState(false); - const [backupKeyError, setBackupKeyError] = useState(false); + const [personalEmail, setPersonalEmail] = useState(""); + const [personalName, setPersonalName] = useState(""); + const [passwordErrorLength, setPasswordErrorLength] = useState(false); + const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); + const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); + const [currentPasswordError, setCurrentPasswordError] = useState(false); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [backupPassword, setBackupPassword] = useState(""); + const [passwordChanged, setPasswordChanged] = useState(false); + const [backupKeyIssued, setBackupKeyIssued] = useState(false); + const [backupKeyError, setBackupKeyError] = useState(false); - useEffect(async () => { - let user = await getUser(); - setPersonalEmail(user.email); - setPersonalName(user.firstName + " " + user.lastName); - }, []); + useEffect(async () => { + let user = await getUser(); + setPersonalEmail(user.email); + setPersonalName(user.firstName + " " + user.lastName); + }, []); - return ( -
- - Personal Settings - - -
-
- -
-
-

- Personal Settings -

-

- View and manage your personal information here. -

-
-
-
-
-
- {/*
+ return ( +
+ + Personal Settings + + +
+
+ +
+
+

+ Personal Settings +

+

+ View and manage your personal information here. +

+
+
+
+
+
+ {/*

Display Name @@ -101,235 +98,223 @@ export default function PersonalSettings() {

*/} -
-
-
-
-
-

- Change password -

-
-
-
- { - setCurrentPassword(password); - }} - type="password" - value={currentPassword} - isRequired - error={currentPasswordError} - errorText="The current password may be wrong" - /> -
- { - setNewPassword(password); - passwordCheck( - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - false - ); - }} - type="password" - value={newPassword} - isRequired - error={ - passwordErrorLength && - passwordErrorLowerCase && - passwordErrorNumber - } - /> -
- {passwordErrorLength || - passwordErrorLowerCase || - passwordErrorNumber ? ( -
-
- Password should contain at least: -
-
- {passwordErrorLength ? ( - - ) : ( - - )} -
- 14 characters -
-
-
- {passwordErrorLowerCase ? ( - - ) : ( - - )} -
- 1 lowercase character -
-
-
- {passwordErrorNumber ? ( - - ) : ( - - )} -
- 1 number -
-
-
- ) : ( -
- )} -
-
-
+
+
+
+
+
+

+ Change password +

+
+
+
+ { + setCurrentPassword(password); + }} + type="password" + value={currentPassword} + isRequired + error={currentPasswordError} + errorText="The current password may be wrong" + /> +
+ { + setNewPassword(password); + passwordCheck( + password, + setPasswordErrorLength, + setPasswordErrorNumber, + setPasswordErrorLowerCase, + false + ); + }} + type="password" + value={newPassword} + isRequired + error={ + passwordErrorLength && + passwordErrorLowerCase && + passwordErrorNumber + } + /> +
+ {passwordErrorLength || + passwordErrorLowerCase || + passwordErrorNumber ? ( +
+
+ Password should contain at least: +
+
+ {passwordErrorLength ? ( + + ) : ( + + )} +
+ 14 characters +
+
+
+ {passwordErrorLowerCase ? ( + + ) : ( + + )} +
+ 1 lowercase character +
+
+
+ {passwordErrorNumber ? ( + + ) : ( + + )} +
+ 1 number +
+
+
+ ) : ( +
+ )} +
+
+
-
-
-
-

- Emergency Kit -

-

- Your Emergency Kit contains the - information you’ll need to sign in to - your Infisical account. -

-

- Only the latest issued Emergency Kit - remains valid. To get a new Emergency - Kit, verify your password. -

-
-
-
- -
-
-
-
-
-
-
-
- ); +
+
+
+

+ Emergency Kit +

+

+ Your Emergency Kit contains the information you’ll need to + sign in to your Infisical account. +

+

+ Only the latest issued Emergency Kit remains valid. To get a + new Emergency Kit, verify your password. +

+
+
+
+ +
+
+
+
+
+
+
+
+ ); } PersonalSettings.requireAuth = true; diff --git a/frontend/pages/settings/project/[id].js b/frontend/pages/settings/project/[id].js index b04f4a81e..147ce57ec 100644 --- a/frontend/pages/settings/project/[id].js +++ b/frontend/pages/settings/project/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect,useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; @@ -15,212 +15,191 @@ import getWorkspaces from "../../api/workspace/getWorkspaces"; import renameWorkspace from "../../api/workspace/renameWorkspace"; export default function SettingsBasic() { - const [buttonReady, setButtonReady] = useState(false); - const router = useRouter(); - const [workspaceName, setWorkspaceName] = useState(""); - const [serviceTokens, setServiceTokens] = useState([]); - const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = - useState(""); - const [workspaceId, setWorkspaceId] = useState(""); - const [isAddOpen, setIsAddOpen] = useState(false); - let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = - useState(false); + const [buttonReady, setButtonReady] = useState(false); + const router = useRouter(); + const [workspaceName, setWorkspaceName] = useState(""); + const [serviceTokens, setServiceTokens] = useState([]); + const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(""); + const [workspaceId, setWorkspaceId] = useState(""); + const [isAddOpen, setIsAddOpen] = useState(false); + let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = + useState(false); - useEffect(async () => { - let userWorkspaces = await getWorkspaces(); - userWorkspaces.map((userWorkspace) => { - if (userWorkspace._id == router.query.id) { - setWorkspaceName(userWorkspace.name); - } - }); - let tempServiceTokens = await getServiceTokens({ - workspaceId: router.query.id, - }); - setServiceTokens(tempServiceTokens); - }, []); + useEffect(async () => { + let userWorkspaces = await getWorkspaces(); + userWorkspaces.map((userWorkspace) => { + if (userWorkspace._id == router.query.id) { + setWorkspaceName(userWorkspace.name); + } + }); + let tempServiceTokens = await getServiceTokens({ + workspaceId: router.query.id, + }); + setServiceTokens(tempServiceTokens); + }, []); - const modifyWorkspaceName = (newName) => { - setButtonReady(true); - setWorkspaceName(newName); - }; + const modifyWorkspaceName = (newName) => { + setButtonReady(true); + setWorkspaceName(newName); + }; - const submitChanges = (newWorkspaceName) => { - renameWorkspace(router.query.id, newWorkspaceName); - setButtonReady(false); - }; + const submitChanges = (newWorkspaceName) => { + renameWorkspace(router.query.id, newWorkspaceName); + setButtonReady(false); + }; - useEffect(async () => { - setWorkspaceId(router.query.id); - }, []); + useEffect(async () => { + setWorkspaceId(router.query.id); + }, []); - function closeAddModal() { - setIsAddOpen(false); - } + function closeAddModal() { + setIsAddOpen(false); + } - function openAddModal() { - setIsAddOpen(true); - } + function openAddModal() { + setIsAddOpen(true); + } - const closeAddServiceTokenModal = () => { - setIsAddServiceTokenDialogOpen(false); - }; + const closeAddServiceTokenModal = () => { + setIsAddServiceTokenDialogOpen(false); + }; - /** - * This function deleted a workspace. - * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete - * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. - * It then deletes the workspace and forwards the user to another aviable workspace. - */ - const executeDeletingWorkspace = async () => { - let userWorkspaces = await getWorkspaces(); + /** + * This function deleted a workspace. + * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete + * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. + * It then deletes the workspace and forwards the user to another aviable workspace. + */ + const executeDeletingWorkspace = async () => { + let userWorkspaces = await getWorkspaces(); - if (userWorkspaces.length > 1) { - if ( - userWorkspaces.filter( - (workspace) => workspace._id == router.query.id - )[0].name == workspaceToBeDeletedName - ) { - await deleteWorkspace(router.query.id); - let userWorkspaces = await getWorkspaces(); - router.push("/dashboard/" + userWorkspaces[0]._id); - } - } - }; + if (userWorkspaces.length > 1) { + if ( + userWorkspaces.filter( + (workspace) => workspace._id == router.query.id + )[0].name == workspaceToBeDeletedName + ) { + await deleteWorkspace(router.query.id); + let userWorkspaces = await getWorkspaces(); + router.push("/dashboard/" + userWorkspaces[0]._id); + } + } + }; - return ( -
- - Settings - - - -
-
- -
-
-

- Project Settings -

-

- These settings only apply to the currently - selected Project. -

-
-
-
-
-
-
-

- Display Name -

-
- -
-
-
-
-
-
-
-

- Project ID -

-

- To integrate Infisical into your code - base and get automatic injection of - environmental variables, you should use - the following Project ID. -

-

- For more guidance, including code - snipets for various languages and - frameworks, see{" "} - {/* eslint-disable-next-line react/jsx-no-target-blank */} - - Infisical Docs. - -

-
- -
-
-
-
-
-

- Service Tokens -

-

- Every service token is specific - to you, a certain project and a - certain environment within this - project. -

-
-
-
-
- -
+ return ( +
+ + Settings + + + +
+
+ +
+
+

+ Project Settings +

+

+ These settings only apply to the currently selected Project. +

+
+
+
+
+
+
+

Display Name

+
+ +
+
+
+
+
+
+
+

Project ID

+

+ To integrate Infisical into your code base and get automatic + injection of environmental variables, you should use the + following Project ID. +

+

+ For more guidance, including code snipets for various + languages and frameworks, see{" "} + {/* eslint-disable-next-line react/jsx-no-target-blank */} + + Infisical Docs. + +

+
+ +
+
+
+
+
+

+ Service Tokens +

+

+ Every service token is specific to you, a certain + project and a certain environment within this project. +

+
+
+
+
+ +
- {/*
+ {/*

Project Environments

@@ -255,47 +234,42 @@ export default function SettingsBasic() {
*/} -
-
-
-

- Danger Zone -

-

- As soon as you delete this project, you will not - be able to undo it. This will immediately remove - all the keys. If you still want to do that, - please enter the name of the project below. -

-
- -
- -

- Note: You can only delete a project in case you - have more than one. -

-
-
-
-
-
- ); +
+
+
+

Danger Zone

+

+ As soon as you delete this project, you will not be able to undo + it. This will immediately remove all the keys. If you still want + to do that, please enter the name of the project below. +

+
+ +
+ +

+ Note: You can only delete a project in case you have more than + one. +

+
+
+
+
+
+ ); } SettingsBasic.requireAuth = true; diff --git a/frontend/pages/signup.js b/frontend/pages/signup.js index e9f000ed2..14ae51dcf 100644 --- a/frontend/pages/signup.js +++ b/frontend/pages/signup.js @@ -7,13 +7,13 @@ import { useRouter } from "next/router"; import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Button from "~/components/basic/buttons/Button"; import Error from "~/components/basic/Error"; import InputField from "~/components/basic/InputField"; +import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; +import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import attemptLogin from "~/utilities/attemptLogin"; import passwordCheck from "~/utilities/checks/PasswordCheck"; -import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode"; import completeAccountInformationSignup from "./api/auth/CompleteAccountInformationSignup"; @@ -28,504 +28,478 @@ const client = new jsrp.client(); // The stye for the verification code input const props = { - inputStyle: { - fontFamily: "monospace", - margin: "4px", - MozAppearance: "textfield", - width: "55px", - borderRadius: "5px", - fontSize: "24px", - height: "55px", - paddingLeft: "7", - backgroundColor: "#0d1117", - color: "white", - border: "1px solid gray", - textAlign: "center", - }, + inputStyle: { + fontFamily: "monospace", + margin: "4px", + MozAppearance: "textfield", + width: "55px", + borderRadius: "5px", + fontSize: "24px", + height: "55px", + paddingLeft: "7", + backgroundColor: "#0d1117", + color: "white", + border: "1px solid gray", + textAlign: "center", + }, }; const propsPhone = { - inputStyle: { - fontFamily: "monospace", - margin: "4px", - MozAppearance: "textfield", - width: "40px", - borderRadius: "5px", - fontSize: "24px", - height: "40px", - paddingLeft: "7", - backgroundColor: "#0d1117", - color: "white", - border: "1px solid gray", - textAlign: "center", - }, + inputStyle: { + fontFamily: "monospace", + margin: "4px", + MozAppearance: "textfield", + width: "40px", + borderRadius: "5px", + fontSize: "24px", + height: "40px", + paddingLeft: "7", + backgroundColor: "#0d1117", + color: "white", + border: "1px solid gray", + textAlign: "center", + }, }; export default function SignUp() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [code, setCode] = useState(""); - const [codeError, setCodeError] = useState(false); - const [firstNameError, setFirstNameError] = useState(false); - const [lastNameError, setLastNameError] = useState(false); - const [passwordErrorLength, setPasswordErrorLength] = useState(false); - const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); - const [passwordErrorUpperCase, setPasswordErrorUpperCase] = useState(false); - const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); - const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = - useState(false); - const [emailError, setEmailError] = useState(false); - const [emailErrorMessage, setEmailErrorMessage] = useState(""); - const [step, setStep] = useState(1); - const router = useRouter(); - const [errorLogin, setErrorLogin] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [backupKeyError, setBackupKeyError] = useState(false); - const [verificationToken, setVerificationToken] = useState(); - const [backupKeyIssued, setBackupKeyIssued] = useState(false); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [code, setCode] = useState(""); + const [codeError, setCodeError] = useState(false); + const [firstNameError, setFirstNameError] = useState(false); + const [lastNameError, setLastNameError] = useState(false); + const [passwordErrorLength, setPasswordErrorLength] = useState(false); + const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); + const [passwordErrorUpperCase, setPasswordErrorUpperCase] = useState(false); + const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); + const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = + useState(false); + const [emailError, setEmailError] = useState(false); + const [emailErrorMessage, setEmailErrorMessage] = useState(""); + const [step, setStep] = useState(1); + const router = useRouter(); + const [errorLogin, setErrorLogin] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [backupKeyError, setBackupKeyError] = useState(false); + const [verificationToken, setVerificationToken] = useState(); + const [backupKeyIssued, setBackupKeyIssued] = useState(false); - useEffect(async () => { - let userWorkspace; - try { - const userWorkspaces = await getWorkspaces(); - userWorkspace = userWorkspaces[0]._id; - router.push("/dashboard/" + userWorkspace); - } catch (error) { - console.log("Error - Not logged in yet"); - } - }, []); + useEffect(async () => { + let userWorkspace; + try { + const userWorkspaces = await getWorkspaces(); + userWorkspace = userWorkspaces[0]._id; + router.push("/dashboard/" + userWorkspace); + } catch (error) { + console.log("Error - Not logged in yet"); + } + }, []); - /** - * Goes to the following step (out of 3) of the signup process. - * Step 1 is submitting your email - * Step 2 is Verifying your email with the code that you received - * Step 3 is Giving the final info. - */ - const incrementStep = async () => { - if (step == 1) { - setStep(2); - } else if (step == 2) { - // Checking if the code matches the email. - const response = await checkEmailVerificationCode(email, code); - if (response.status == "200" || code == "111222") { - setVerificationToken((await response.json()).token); - setStep(3); - } else { - setCodeError(true); - } - } else if (step == 3) { - setStep(4); - } - }; + /** + * Goes to the following step (out of 3) of the signup process. + * Step 1 is submitting your email + * Step 2 is Verifying your email with the code that you received + * Step 3 is Giving the final info. + */ + const incrementStep = async () => { + if (step == 1) { + setStep(2); + } else if (step == 2) { + // Checking if the code matches the email. + const response = await checkEmailVerificationCode(email, code); + if (response.status == "200" || code == "111222") { + setVerificationToken((await response.json()).token); + setStep(3); + } else { + setCodeError(true); + } + } else if (step == 3) { + setStep(4); + } + }; - /** - * Verifies if the entered email "looks" correct - */ - const emailCheck = () => { - var emailCheckBool = false; - if (!email) { - setEmailError(true); - setEmailErrorMessage("Please enter your email."); - emailCheckBool = true; - } else if ( - !email.includes("@") || - !email.includes(".") || - !/[a-z]/.test(email) - ) { - setEmailError(true); - setEmailErrorMessage("Please enter a valid email."); - emailCheckBool = true; - } else { - setEmailError(false); - } + /** + * Verifies if the entered email "looks" correct + */ + const emailCheck = () => { + var emailCheckBool = false; + if (!email) { + setEmailError(true); + setEmailErrorMessage("Please enter your email."); + emailCheckBool = true; + } else if ( + !email.includes("@") || + !email.includes(".") || + !/[a-z]/.test(email) + ) { + setEmailError(true); + setEmailErrorMessage("Please enter a valid email."); + emailCheckBool = true; + } else { + setEmailError(false); + } - // If everything is correct, go to the next step - if (!emailCheckBool) { - sendVerificationEmail(email); - incrementStep(); - } - }; + // If everything is correct, go to the next step + if (!emailCheckBool) { + sendVerificationEmail(email); + incrementStep(); + } + }; - // Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the criteria. - const signupErrorCheck = async () => { - setIsLoading(true); - var errorCheck = false; - if (!firstName) { - setFirstNameError(true); - errorCheck = true; - } else { - setFirstNameError(false); - } - if (!lastName) { - setLastNameError(true); - errorCheck = true; - } else { - setLastNameError(false); - } - errorCheck = passwordCheck( - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - errorCheck - ); + // Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the criteria. + const signupErrorCheck = async () => { + setIsLoading(true); + var errorCheck = false; + if (!firstName) { + setFirstNameError(true); + errorCheck = true; + } else { + setFirstNameError(false); + } + if (!lastName) { + setLastNameError(true); + errorCheck = true; + } else { + setLastNameError(false); + } + errorCheck = passwordCheck( + password, + setPasswordErrorLength, + setPasswordErrorNumber, + setPasswordErrorLowerCase, + errorCheck + ); - if (!errorCheck) { - // Generate a random pair of a public and a private key - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array); - const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array); + if (!errorCheck) { + // Generate a random pair of a public and a private key + const pair = nacl.box.keyPair(); + const secretKeyUint8Array = pair.secretKey; + const publicKeyUint8Array = pair.publicKey; + const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array); + const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array); - const { ciphertext, iv, tag } = Aes256Gcm.encrypt( - PRIVATE_KEY, - password - .slice(0, 32) - .padStart( - 32 + - (password.slice(0, 32).length - - new Blob([password]).size), - "0" - ) - ); - localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); + const { ciphertext, iv, tag } = Aes256Gcm.encrypt( + PRIVATE_KEY, + password + .slice(0, 32) + .padStart( + 32 + (password.slice(0, 32).length - new Blob([password]).size), + "0" + ) + ); + localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); - client.init( - { - username: email, - password: password, - }, - async () => { - client.createVerifier(async (err, result) => { - const response = await completeAccountInformationSignup( - { - email, - firstName, - lastName, - organizationName: firstName + "'s organization", - publicKey: PUBLIC_KEY, - ciphertext, - iv, - tag, - salt: result.salt, - verifier: result.verifier, - token: verificationToken, - } - ); + client.init( + { + username: email, + password: password, + }, + async () => { + client.createVerifier(async (err, result) => { + const response = await completeAccountInformationSignup({ + email, + firstName, + lastName, + organizationName: firstName + "'s organization", + publicKey: PUBLIC_KEY, + ciphertext, + iv, + tag, + salt: result.salt, + verifier: result.verifier, + token: verificationToken, + }); - // if everything works, go the main dashboard page. - if (!errorCheck && response.status == "200") { - response = await response.json(); + // if everything works, go the main dashboard page. + if (!errorCheck && response.status == "200") { + response = await response.json(); - localStorage.setItem("publicKey", PUBLIC_KEY); - localStorage.setItem( - "encryptedPrivateKey", - ciphertext - ); - localStorage.setItem("iv", iv); - localStorage.setItem("tag", tag); + localStorage.setItem("publicKey", PUBLIC_KEY); + localStorage.setItem("encryptedPrivateKey", ciphertext); + localStorage.setItem("iv", iv); + localStorage.setItem("tag", tag); - try { - await attemptLogin( - email, - password, - setErrorLogin, - router, - true, - false - ); - incrementStep(); - } catch (error) { - setIsLoading(false); - } - } - }); - } - ); - } else { - setIsLoading(false); - } - }; + try { + await attemptLogin( + email, + password, + setErrorLogin, + router, + true, + false + ); + incrementStep(); + } catch (error) { + setIsLoading(false); + } + } + }); + } + ); + } else { + setIsLoading(false); + } + }; - // Step 1 of the sign up process (enter the email or choose google authentication) - const step1 = ( -
-

- {"Let'"}s get started -

-
- - - -
-
- -
- {/*
+ // Step 1 of the sign up process (enter the email or choose google authentication) + const step1 = ( +
+

+ {"Let'"}s get started +

+
+ + + +
+
+ +
+ {/*

I do not want to receive emails about Infisical and its products.

*/} -
-

- By creating an account, you agree to our Terms and have read - and acknowledged the Privacy Policy. -

-
-
-
-
- ); +
+

+ By creating an account, you agree to our Terms and have read and + acknowledged the Privacy Policy. +

+
+
+
+
+ ); - // Step 2 of the signup process (enter the email verification code) - const step2 = ( -
-

- {"We've"} sent a verification email to{" "} -

-

- {email}{" "} -

-
- -
-
- -
- {codeError && ( - - )} -
-
-
- {/* + // Step 2 of the signup process (enter the email verification code) + const step2 = ( +
+

+ {"We've"} sent a verification email to{" "} +

+

+ {email}{" "} +

+
+ +
+
+ +
+ {codeError && ( + + )} +
+
+
+ {/* */} -

- Make sure to check your spam inbox. -

-
-
- ); +

+ Make sure to check your spam inbox. +

+
+
+ ); - // Step 3 of the signup process (enter the rest of the impformation) - const step3 = ( -
-

- Almost there! -

-
- -
-
- -
-
- { - setPassword(password); - passwordCheck( - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - false - ); - }} - type="password" - value={password} - isRequired - error={ - passwordErrorLength && - passwordErrorNumber && - passwordErrorLowerCase - } - /> - {passwordErrorLength || - passwordErrorLowerCase || - passwordErrorNumber ? ( -
-
- Password should contain at least: -
-
- {passwordErrorLength ? ( - - ) : ( - - )} -
- 14 characters -
-
-
- {passwordErrorLowerCase ? ( - - ) : ( - - )} -
- 1 lowercase character -
-
-
- {passwordErrorNumber ? ( - - ) : ( - - )} -
- 1 number -
-
-
- ) : ( -
- )} -
-
-
-
- ); + // Step 3 of the signup process (enter the rest of the impformation) + const step3 = ( +
+

+ Almost there! +

+
+ +
+
+ +
+
+ { + setPassword(password); + passwordCheck( + password, + setPasswordErrorLength, + setPasswordErrorNumber, + setPasswordErrorLowerCase, + false + ); + }} + type="password" + value={password} + isRequired + error={ + passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase + } + /> + {passwordErrorLength || + passwordErrorLowerCase || + passwordErrorNumber ? ( +
+
+ Password should contain at least: +
+
+ {passwordErrorLength ? ( + + ) : ( + + )} +
+ 14 characters +
+
+
+ {passwordErrorLowerCase ? ( + + ) : ( + + )} +
+ 1 lowercase character +
+
+
+ {passwordErrorNumber ? ( + + ) : ( + + )} +
+ 1 number +
+
+
+ ) : ( +
+ )} +
+
+
+
+ ); - // Step 4 of the sign up process (download the emergency kit pdf) - const step4 = ( -
-

- Save your Emergency Kit -

-
-
- If you get locked out of your account, your Emergency Kit is - the only way to sign in. -
-
- We recommend you download it and keep it somewhere safe. -
-
-
- - It contains your Secret Key which we cannot access or recover - for you if you lose it. -
-
-
-
- ); +
+
+ ); - return ( -
- - Infiscal | Sign Up - - - - - -
- -
- Infisical Wide Logo -
- - {step == 1 - ? step1 - : step == 2 - ? step2 - : step == 3 - ? step3 - : step4} -
-
- ); + return ( +
+ + Infiscal | Sign Up + + + + + +
+ +
+ Infisical Wide Logo +
+ + {step == 1 ? step1 : step == 2 ? step2 : step == 3 ? step3 : step4} +
+
+ ); } diff --git a/frontend/pages/signupinvite.js b/frontend/pages/signupinvite.js index 256bbb83f..8e7f1d4a0 100644 --- a/frontend/pages/signupinvite.js +++ b/frontend/pages/signupinvite.js @@ -3,15 +3,15 @@ import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { faCheck, faWarning,faX } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Button from "~/components/basic/buttons/Button"; import InputField from "~/components/basic/InputField"; +import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; +import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import attemptLogin from "~/utilities/attemptLogin"; import passwordCheck from "~/utilities/checks/PasswordCheck"; -import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite"; import verifySignupInvite from "./api/auth/VerifySignupInvite"; @@ -23,340 +23,323 @@ const client = new jsrp.client(); const queryString = require("query-string"); export default function SignupInvite() { - const [password, setPassword] = useState(""); - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [firstNameError, setFirstNameError] = useState(false); - const [lastNameError, setLastNameError] = useState(false); - const [passwordErrorLength, setPasswordErrorLength] = useState(false); - const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); - const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); - const router = useRouter(); - const parsedUrl = queryString.parse(router.asPath.split("?")[1]); - const [email, setEmail] = useState(parsedUrl.to); - const token = parsedUrl.token; - const [errorLogin, setErrorLogin] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [step, setStep] = useState(1); - const [backupKeyError, setBackupKeyError] = useState(false); - const [verificationToken, setVerificationToken] = useState(); - const [backupKeyIssued, setBackupKeyIssued] = useState(false); + const [password, setPassword] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [firstNameError, setFirstNameError] = useState(false); + const [lastNameError, setLastNameError] = useState(false); + const [passwordErrorLength, setPasswordErrorLength] = useState(false); + const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); + const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); + const router = useRouter(); + const parsedUrl = queryString.parse(router.asPath.split("?")[1]); + const [email, setEmail] = useState(parsedUrl.to); + const token = parsedUrl.token; + const [errorLogin, setErrorLogin] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [step, setStep] = useState(1); + const [backupKeyError, setBackupKeyError] = useState(false); + const [verificationToken, setVerificationToken] = useState(); + const [backupKeyIssued, setBackupKeyIssued] = useState(false); - // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria. - const signupErrorCheck = async () => { - setIsLoading(true); - var errorCheck = false; - if (!firstName) { - setFirstNameError(true); - errorCheck = true; - } else { - setFirstNameError(false); - } - if (!lastName) { - setLastNameError(true); - errorCheck = true; - } else { - setLastNameError(false); - } - errorCheck = passwordCheck( - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - errorCheck - ); + // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria. + const signupErrorCheck = async () => { + setIsLoading(true); + var errorCheck = false; + if (!firstName) { + setFirstNameError(true); + errorCheck = true; + } else { + setFirstNameError(false); + } + if (!lastName) { + setLastNameError(true); + errorCheck = true; + } else { + setLastNameError(false); + } + errorCheck = passwordCheck( + password, + setPasswordErrorLength, + setPasswordErrorNumber, + setPasswordErrorLowerCase, + errorCheck + ); - if (!errorCheck) { - // Generate a random pair of a public and a private key - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array); - const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array); + if (!errorCheck) { + // Generate a random pair of a public and a private key + const pair = nacl.box.keyPair(); + const secretKeyUint8Array = pair.secretKey; + const publicKeyUint8Array = pair.publicKey; + const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array); + const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array); - const { ciphertext, iv, tag } = Aes256Gcm.encrypt( - PRIVATE_KEY, - password - .slice(0, 32) - .padStart( - 32 + - (password.slice(0, 32).length - - new Blob([password]).size), - "0" - ) - ); - localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); + const { ciphertext, iv, tag } = Aes256Gcm.encrypt( + PRIVATE_KEY, + password + .slice(0, 32) + .padStart( + 32 + (password.slice(0, 32).length - new Blob([password]).size), + "0" + ) + ); + localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); - client.init( - { - username: email, - password: password, - }, - async () => { - client.createVerifier(async (err, result) => { - const response = - await completeAccountInformationSignupInvite({ - email, - firstName, - lastName, - publicKey: PUBLIC_KEY, - ciphertext, - iv, - tag, - salt: result.salt, - verifier: result.verifier, - token: verificationToken, - }); + client.init( + { + username: email, + password: password, + }, + async () => { + client.createVerifier(async (err, result) => { + const response = await completeAccountInformationSignupInvite({ + email, + firstName, + lastName, + publicKey: PUBLIC_KEY, + ciphertext, + iv, + tag, + salt: result.salt, + verifier: result.verifier, + token: verificationToken, + }); - // if everything works, go the main dashboard page. - if (!errorCheck && response.status == "200") { - response = await response.json(); + // if everything works, go the main dashboard page. + if (!errorCheck && response.status == "200") { + response = await response.json(); - localStorage.setItem("publicKey", PUBLIC_KEY); - localStorage.setItem( - "encryptedPrivateKey", - ciphertext - ); - localStorage.setItem("iv", iv); - localStorage.setItem("tag", tag); + localStorage.setItem("publicKey", PUBLIC_KEY); + localStorage.setItem("encryptedPrivateKey", ciphertext); + localStorage.setItem("iv", iv); + localStorage.setItem("tag", tag); - try { - await attemptLogin( - email, - password, - setErrorLogin, - router, - false, - false - ); - setStep(3); - } catch (error) { - setIsLoading(false); - console.log("Error", error); - } - } - }); - } - ); - } else { - setIsLoading(false); - } - }; + try { + await attemptLogin( + email, + password, + setErrorLogin, + router, + false, + false + ); + setStep(3); + } catch (error) { + setIsLoading(false); + console.log("Error", error); + } + } + }); + } + ); + } else { + setIsLoading(false); + } + }; - // Step 4 of the sign up process (download the emergency kit pdf) - const stepConfirmEmail = ( -
-

- Confirm your email -

- verify email -
-
-
- ); + // Step 4 of the sign up process (download the emergency kit pdf) + const stepConfirmEmail = ( +
+

+ Confirm your email +

+ verify email +
+
+
+ ); - // Because this is the invite signup - we directly go to the last step of signup (email is already verified) - const main = ( -
-

- Almost there! -

-
- -
-
- -
-
- { - setPassword(password); - passwordCheck( - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - false - ); - }} - type="password" - value={password} - isRequired - error={ - passwordErrorLength && - passwordErrorNumber && - passwordErrorLowerCase - } - /> - {passwordErrorLength || - passwordErrorLowerCase || - passwordErrorNumber ? ( -
-
- Password should contain at least: -
-
- {passwordErrorLength ? ( - - ) : ( - - )} -
- 14 characters -
-
-
- {passwordErrorLowerCase ? ( - - ) : ( - - )} -
- 1 lowercase character -
-
-
- {passwordErrorNumber ? ( - - ) : ( - - )} -
- 1 number -
-
-
- ) : ( -
- )} -
-
-
-
- ); + // Because this is the invite signup - we directly go to the last step of signup (email is already verified) + const main = ( +
+

+ Almost there! +

+
+ +
+
+ +
+
+ { + setPassword(password); + passwordCheck( + password, + setPasswordErrorLength, + setPasswordErrorNumber, + setPasswordErrorLowerCase, + false + ); + }} + type="password" + value={password} + isRequired + error={ + passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase + } + /> + {passwordErrorLength || + passwordErrorLowerCase || + passwordErrorNumber ? ( +
+
+ Password should contain at least: +
+
+ {passwordErrorLength ? ( + + ) : ( + + )} +
+ 14 characters +
+
+
+ {passwordErrorLowerCase ? ( + + ) : ( + + )} +
+ 1 lowercase character +
+
+
+ {passwordErrorNumber ? ( + + ) : ( + + )} +
+ 1 number +
+
+
+ ) : ( +
+ )} +
+
+
+
+ ); - // Step 4 of the sign up process (download the emergency kit pdf) - const step4 = ( -
-

- Save your Emergency Kit -

-
-
- If you get locked out of your account, your Emergency Kit is - the only way to sign in. -
-
- We recommend you download it and keep it somewhere safe. -
-
-
- - It contains your Secret Key which we cannot access or recover - for you if you lose it. -
-
-
-
- ); +
+ + ); - return ( -
- - Sign Up - - - -
- Infisical Wide Logo -
- - {step == 1 ? stepConfirmEmail : step == 2 ? main : step4} -
- ); + return ( +
+ + Sign Up + + + +
+ Infisical Wide Logo +
+ + {step == 1 ? stepConfirmEmail : step == 2 ? main : step4} +
+ ); } diff --git a/frontend/pages/users/[id].js b/frontend/pages/users/[id].js index f46abdf1d..de0307786 100644 --- a/frontend/pages/users/[id].js +++ b/frontend/pages/users/[id].js @@ -21,190 +21,183 @@ import uploadKeys from "../api/workspace/uploadKeys"; // #TODO: Update all the workspaceIds const crypto = require("crypto"); const { - decryptAssymmetric, - encryptAssymmetric, + decryptAssymmetric, + encryptAssymmetric, } = require("../../components/utilities/cryptography/crypto"); const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); export default function Users() { - let [isAddOpen, setIsAddOpen] = useState(false); - // let [isDeleteOpen, setIsDeleteOpen] = useState(false); - // let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false); - let [email, setEmail] = useState(""); - const [personalEmail, setPersonalEmail] = useState(""); - const [searchUsers, setSearchUsers] = useState(""); + let [isAddOpen, setIsAddOpen] = useState(false); + // let [isDeleteOpen, setIsDeleteOpen] = useState(false); + // let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false); + let [email, setEmail] = useState(""); + const [personalEmail, setPersonalEmail] = useState(""); + const [searchUsers, setSearchUsers] = useState(""); - const router = useRouter(); - let workspaceId; + const router = useRouter(); + let workspaceId; - function closeAddModal() { - setIsAddOpen(false); - } + function closeAddModal() { + setIsAddOpen(false); + } - // function closeDeleteModal() { - // setIsDeleteOpen(false); - // } + // function closeDeleteModal() { + // setIsDeleteOpen(false); + // } - // function deleteMembership(userId) { - // deleteUserFromWorkspace(userId, router.query.id) - // } + // function deleteMembership(userId) { + // deleteUserFromWorkspace(userId, router.query.id) + // } - // function openDeleteModal() { - // setIsDeleteOpen(true); - // } + // function openDeleteModal() { + // setIsDeleteOpen(true); + // } - async function submitAddModal() { - let result = await addUserToWorkspace(email, router.query.id); - if (result?.invitee && result?.latestKey) { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + async function submitAddModal() { + let result = await addUserToWorkspace(email, router.query.id); + if (result?.invitee && result?.latestKey) { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - // 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, - }); + // 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: result.invitee.publicKey, - privateKey: PRIVATE_KEY, - }); + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: key, + publicKey: result.invitee.publicKey, + privateKey: PRIVATE_KEY, + }); - uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce); - } - setEmail(""); - setIsAddOpen(false); - router.reload(); - } + uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce); + } + setEmail(""); + setIsAddOpen(false); + router.reload(); + } - function openAddModal() { - setIsAddOpen(true); - } + function openAddModal() { + setIsAddOpen(true); + } - const [userList, setUserList] = useState(); - const [orgUserList, setOrgUserList] = useState([]); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(async () => { - const user = await getUser(); - setPersonalEmail(user.email); + const [userList, setUserList] = useState(); + const [orgUserList, setOrgUserList] = useState([]); + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(async () => { + const user = await getUser(); + setPersonalEmail(user.email); - workspaceId = router.query.id; - let workspaceUsers = await getWorkspaceUsers({ - workspaceId, - }); - const tempUserList = workspaceUsers.map((user) => ({ - key: guidGenerator(), - firstName: user.user?.firstName, - lastName: user.user?.lastName, - email: - user.user?.email == null ? user.inviteEmail : user.user?.email, - role: user?.role, - status: user?.status, - userId: user.user?._id, - membershipId: user._id, - publicKey: user.user?.publicKey, - })); - setUserList(tempUserList); - const orgUsers = await getOrganizationUsers({ - orgId: localStorage.getItem("orgData.id"), - }); - setOrgUserList(orgUsers); - setEmail( - orgUsers - ?.filter((user) => user.status == "accepted") - .map((user) => user.user.email) - .filter( - (email) => - !tempUserList - ?.map((user1) => user1.email) - .includes(email) - )[0] - ); - }, []); + workspaceId = router.query.id; + let workspaceUsers = await getWorkspaceUsers({ + workspaceId, + }); + const tempUserList = workspaceUsers.map((user) => ({ + key: guidGenerator(), + firstName: user.user?.firstName, + lastName: user.user?.lastName, + email: user.user?.email == null ? user.inviteEmail : user.user?.email, + role: user?.role, + status: user?.status, + userId: user.user?._id, + membershipId: user._id, + publicKey: user.user?.publicKey, + })); + setUserList(tempUserList); + const orgUsers = await getOrganizationUsers({ + orgId: localStorage.getItem("orgData.id"), + }); + setOrgUserList(orgUsers); + setEmail( + orgUsers + ?.filter((user) => user.status == "accepted") + .map((user) => user.user.email) + .filter( + (email) => !tempUserList?.map((user1) => user1.email).includes(email) + )[0] + ); + }, []); - return userList ? ( -
- - Users - - - -
-

Project Members

-

- This pages shows the members of the selected project. -

-
- user.status == "accepted") - .map((user) => user.user.email) - .filter( - (email) => - !userList - ?.map((user1) => user1.email) - .includes(email) - )} - workspaceId={workspaceId} - setEmail={setEmail} - /> - {/* */} -
-
- - setSearchUsers(e.target.value)} - placeholder={"Search members..."} - /> -
-
-
-
-
- -
-
- ) : ( -
-
- loading animation -
- ); + return userList ? ( +
+ + Users + + + +
+

Project Members

+

+ This pages shows the members of the selected project. +

+
+ user.status == "accepted") + .map((user) => user.user.email) + .filter( + (email) => !userList?.map((user1) => user1.email).includes(email) + )} + workspaceId={workspaceId} + setEmail={setEmail} + /> + {/* */} +
+
+ + setSearchUsers(e.target.value)} + placeholder={"Search members..."} + /> +
+
+
+
+
+ +
+
+ ) : ( +
+
+ loading animation +
+ ); } Users.requireAuth = true; diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 33ad091d2..12a703d90 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 54cefd4f8..6f3d038cf 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,1686 +1,1685 @@ const colors = require("tailwindcss/colors"); const round = (num) => - num - .toFixed(7) - .replace(/(\.[0-9]+?)0+$/, "$1") - .replace(/\.0$/, ""); + num + .toFixed(7) + .replace(/(\.[0-9]+?)0+$/, "$1") + .replace(/\.0$/, ""); const rem = (px) => `${round(px / 16)}rem`; const em = (px, base) => `${round(px / base)}em`; let defaultModifiers = { - sm: { - css: [ - { - fontSize: rem(14), - lineHeight: round(24 / 14), - p: { - marginTop: em(16, 14), - marginBottom: em(16, 14), - }, - '[class~="lead"]': { - fontSize: em(18, 14), - lineHeight: round(28 / 18), - marginTop: em(16, 18), - marginBottom: em(16, 18), - }, - blockquote: { - marginTop: em(24, 18), - marginBottom: em(24, 18), - paddingLeft: em(20, 18), - }, - h1: { - fontSize: em(30, 14), - marginTop: "0", - marginBottom: em(24, 30), - lineHeight: round(36 / 30), - }, - h2: { - fontSize: em(20, 14), - marginTop: em(32, 20), - marginBottom: em(16, 20), - lineHeight: round(28 / 20), - }, - h3: { - fontSize: em(18, 14), - marginTop: em(28, 18), - marginBottom: em(8, 18), - lineHeight: round(28 / 18), - }, - h4: { - marginTop: em(20, 14), - marginBottom: em(8, 14), - lineHeight: round(20 / 14), - }, - img: { - marginTop: em(24, 14), - marginBottom: em(24, 14), - }, - video: { - marginTop: em(24, 14), - marginBottom: em(24, 14), - }, - figure: { - marginTop: em(24, 14), - marginBottom: em(24, 14), - }, - "figure > *": { - marginTop: "0", - marginBottom: "0", - }, - figcaption: { - fontSize: em(12, 14), - lineHeight: round(16 / 12), - marginTop: em(8, 12), - }, - code: { - fontSize: em(12, 14), - }, - "h2 code": { - fontSize: em(18, 20), - }, - "h3 code": { - fontSize: em(16, 18), - }, - pre: { - fontSize: em(12, 14), - lineHeight: round(20 / 12), - marginTop: em(20, 12), - marginBottom: em(20, 12), - borderRadius: rem(4), - paddingTop: em(8, 12), - paddingRight: em(12, 12), - paddingBottom: em(8, 12), - paddingLeft: em(12, 12), - }, - ol: { - marginTop: em(16, 14), - marginBottom: em(16, 14), - paddingLeft: em(22, 14), - }, - ul: { - marginTop: em(16, 14), - marginBottom: em(16, 14), - paddingLeft: em(22, 14), - }, - li: { - marginTop: em(4, 14), - marginBottom: em(4, 14), - }, - "ol > li": { - paddingLeft: em(6, 14), - }, - "ul > li": { - paddingLeft: em(6, 14), - }, - "> ul > li p": { - marginTop: em(8, 14), - marginBottom: em(8, 14), - }, - "> ul > li > *:first-child": { - marginTop: em(16, 14), - }, - "> ul > li > *:last-child": { - marginBottom: em(16, 14), - }, - "> ol > li > *:first-child": { - marginTop: em(16, 14), - }, - "> ol > li > *:last-child": { - marginBottom: em(16, 14), - }, - "ul ul, ul ol, ol ul, ol ol": { - marginTop: em(8, 14), - marginBottom: em(8, 14), - }, - hr: { - marginTop: em(40, 14), - marginBottom: em(40, 14), - }, - "hr + *": { - marginTop: "0", - }, - "h2 + *": { - marginTop: "0", - }, - "h3 + *": { - marginTop: "0", - }, - "h4 + *": { - marginTop: "0", - }, - table: { - fontSize: em(12, 14), - lineHeight: round(18 / 12), - }, - "thead th": { - paddingRight: em(12, 12), - paddingBottom: em(8, 12), - paddingLeft: em(12, 12), - }, - "thead th:first-child": { - paddingLeft: "0", - }, - "thead th:last-child": { - paddingRight: "0", - }, - "tbody td, tfoot td": { - paddingTop: em(8, 12), - paddingRight: em(12, 12), - paddingBottom: em(8, 12), - paddingLeft: em(12, 12), - }, - "tbody td:first-child, tfoot td:first-child": { - paddingLeft: "0", - }, - "tbody td:last-child, tfoot td:last-child": { - paddingRight: "0", - }, - }, - { - "> :first-child": { - marginTop: "0", - }, - "> :last-child": { - marginBottom: "0", - }, - }, - ], - }, - base: { - css: [ - { - fontSize: rem(16), - lineHeight: round(28 / 16), - p: { - marginTop: em(20, 16), - marginBottom: em(20, 16), - }, - '[class~="lead"]': { - fontSize: em(20, 16), - lineHeight: round(32 / 20), - marginTop: em(24, 20), - marginBottom: em(24, 20), - }, - blockquote: { - marginTop: em(32, 20), - marginBottom: em(32, 20), - paddingLeft: em(20, 20), - }, - h1: { - fontSize: em(36, 16), - marginTop: "0", - marginBottom: em(32, 36), - lineHeight: round(40 / 36), - }, - h2: { - fontSize: em(24, 16), - marginTop: em(48, 24), - marginBottom: em(24, 24), - lineHeight: round(32 / 24), - }, - h3: { - fontSize: em(20, 16), - marginTop: em(32, 20), - marginBottom: em(12, 20), - lineHeight: round(32 / 20), - }, - h4: { - marginTop: em(24, 16), - marginBottom: em(8, 16), - lineHeight: round(24 / 16), - }, - img: { - marginTop: em(32, 16), - marginBottom: em(32, 16), - }, - video: { - marginTop: em(32, 16), - marginBottom: em(32, 16), - }, - figure: { - marginTop: em(32, 16), - marginBottom: em(32, 16), - }, - "figure > *": { - marginTop: "0", - marginBottom: "0", - }, - figcaption: { - fontSize: em(14, 16), - lineHeight: round(20 / 14), - marginTop: em(12, 14), - }, - code: { - fontSize: em(14, 16), - }, - "h2 code": { - fontSize: em(21, 24), - }, - "h3 code": { - fontSize: em(18, 20), - }, - pre: { - fontSize: em(14, 16), - lineHeight: round(24 / 14), - marginTop: em(24, 14), - marginBottom: em(24, 14), - borderRadius: rem(6), - paddingTop: em(12, 14), - paddingRight: em(16, 14), - paddingBottom: em(12, 14), - paddingLeft: em(16, 14), - }, - ol: { - marginTop: em(20, 16), - marginBottom: em(20, 16), - paddingLeft: em(26, 16), - }, - ul: { - marginTop: em(20, 16), - marginBottom: em(20, 16), - paddingLeft: em(26, 16), - }, - li: { - marginTop: em(8, 16), - marginBottom: em(8, 16), - }, - "ol > li": { - paddingLeft: em(6, 16), - }, - "ul > li": { - paddingLeft: em(6, 16), - }, - "> ul > li p": { - marginTop: em(12, 16), - marginBottom: em(12, 16), - }, - "> ul > li > *:first-child": { - marginTop: em(20, 16), - }, - "> ul > li > *:last-child": { - marginBottom: em(20, 16), - }, - "> ol > li > *:first-child": { - marginTop: em(20, 16), - }, - "> ol > li > *:last-child": { - marginBottom: em(20, 16), - }, - "ul ul, ul ol, ol ul, ol ol": { - marginTop: em(12, 16), - marginBottom: em(12, 16), - }, - hr: { - marginTop: em(48, 16), - marginBottom: em(48, 16), - }, - "hr + *": { - marginTop: "0", - }, - "h2 + *": { - marginTop: "0", - }, - "h3 + *": { - marginTop: "0", - }, - "h4 + *": { - marginTop: "0", - }, - table: { - fontSize: em(14, 16), - lineHeight: round(24 / 14), - }, - "thead th": { - paddingRight: em(8, 14), - paddingBottom: em(8, 14), - paddingLeft: em(8, 14), - }, - "thead th:first-child": { - paddingLeft: "0", - }, - "thead th:last-child": { - paddingRight: "0", - }, - "tbody td, tfoot td": { - paddingTop: em(8, 14), - paddingRight: em(8, 14), - paddingBottom: em(8, 14), - paddingLeft: em(8, 14), - }, - "tbody td:first-child, tfoot td:first-child": { - paddingLeft: "0", - }, - "tbody td:last-child, tfoot td:last-child": { - paddingRight: "0", - }, - }, - { - "> :first-child": { - marginTop: "0", - }, - "> :last-child": { - marginBottom: "0", - }, - }, - ], - }, - lg: { - css: [ - { - fontSize: rem(18), - lineHeight: round(32 / 18), - p: { - marginTop: em(24, 18), - marginBottom: em(24, 18), - }, - '[class~="lead"]': { - fontSize: em(22, 18), - lineHeight: round(32 / 22), - marginTop: em(24, 22), - marginBottom: em(24, 22), - }, - blockquote: { - marginTop: em(40, 24), - marginBottom: em(40, 24), - paddingLeft: em(24, 24), - }, - h1: { - fontSize: em(48, 18), - marginTop: "0", - marginBottom: em(40, 48), - lineHeight: round(48 / 48), - }, - h2: { - fontSize: em(30, 18), - marginTop: em(56, 30), - marginBottom: em(32, 30), - lineHeight: round(40 / 30), - }, - h3: { - fontSize: em(24, 18), - marginTop: em(40, 24), - marginBottom: em(16, 24), - lineHeight: round(36 / 24), - }, - h4: { - marginTop: em(32, 18), - marginBottom: em(8, 18), - lineHeight: round(28 / 18), - }, - img: { - marginTop: em(32, 18), - marginBottom: em(32, 18), - }, - video: { - marginTop: em(32, 18), - marginBottom: em(32, 18), - }, - figure: { - marginTop: em(32, 18), - marginBottom: em(32, 18), - }, - "figure > *": { - marginTop: "0", - marginBottom: "0", - }, - figcaption: { - fontSize: em(16, 18), - lineHeight: round(24 / 16), - marginTop: em(16, 16), - }, - code: { - fontSize: em(16, 18), - }, - "h2 code": { - fontSize: em(26, 30), - }, - "h3 code": { - fontSize: em(21, 24), - }, - pre: { - fontSize: em(16, 18), - lineHeight: round(28 / 16), - marginTop: em(32, 16), - marginBottom: em(32, 16), - borderRadius: rem(6), - paddingTop: em(16, 16), - paddingRight: em(24, 16), - paddingBottom: em(16, 16), - paddingLeft: em(24, 16), - }, - ol: { - marginTop: em(24, 18), - marginBottom: em(24, 18), - paddingLeft: em(28, 18), - }, - ul: { - marginTop: em(24, 18), - marginBottom: em(24, 18), - paddingLeft: em(28, 18), - }, - li: { - marginTop: em(12, 18), - marginBottom: em(12, 18), - }, - "ol > li": { - paddingLeft: em(8, 18), - }, - "ul > li": { - paddingLeft: em(8, 18), - }, - "> ul > li p": { - marginTop: em(16, 18), - marginBottom: em(16, 18), - }, - "> ul > li > *:first-child": { - marginTop: em(24, 18), - }, - "> ul > li > *:last-child": { - marginBottom: em(24, 18), - }, - "> ol > li > *:first-child": { - marginTop: em(24, 18), - }, - "> ol > li > *:last-child": { - marginBottom: em(24, 18), - }, - "ul ul, ul ol, ol ul, ol ol": { - marginTop: em(16, 18), - marginBottom: em(16, 18), - }, - hr: { - marginTop: em(56, 18), - marginBottom: em(56, 18), - }, - "hr + *": { - marginTop: "0", - }, - "h2 + *": { - marginTop: "0", - }, - "h3 + *": { - marginTop: "0", - }, - "h4 + *": { - marginTop: "0", - }, - table: { - fontSize: em(16, 18), - lineHeight: round(24 / 16), - }, - "thead th": { - paddingRight: em(12, 16), - paddingBottom: em(12, 16), - paddingLeft: em(12, 16), - }, - "thead th:first-child": { - paddingLeft: "0", - }, - "thead th:last-child": { - paddingRight: "0", - }, - "tbody td, tfoot td": { - paddingTop: em(12, 16), - paddingRight: em(12, 16), - paddingBottom: em(12, 16), - paddingLeft: em(12, 16), - }, - "tbody td:first-child, tfoot td:first-child": { - paddingLeft: "0", - }, - "tbody td:last-child, tfoot td:last-child": { - paddingRight: "0", - }, - }, - { - "> :first-child": { - marginTop: "0", - }, - "> :last-child": { - marginBottom: "0", - }, - }, - ], - }, - xl: { - css: [ - { - fontSize: rem(20), - lineHeight: round(36 / 20), - p: { - marginTop: em(24, 20), - marginBottom: em(24, 20), - }, - '[class~="lead"]': { - fontSize: em(24, 20), - lineHeight: round(36 / 24), - marginTop: em(24, 24), - marginBottom: em(24, 24), - }, - blockquote: { - marginTop: em(48, 30), - marginBottom: em(48, 30), - paddingLeft: em(32, 30), - }, - h1: { - fontSize: em(56, 20), - marginTop: "0", - marginBottom: em(48, 56), - lineHeight: round(56 / 56), - }, - h2: { - fontSize: em(36, 20), - marginTop: em(56, 36), - marginBottom: em(32, 36), - lineHeight: round(40 / 36), - }, - h3: { - fontSize: em(30, 20), - marginTop: em(48, 30), - marginBottom: em(20, 30), - lineHeight: round(40 / 30), - }, - h4: { - marginTop: em(36, 20), - marginBottom: em(12, 20), - lineHeight: round(32 / 20), - }, - img: { - marginTop: em(40, 20), - marginBottom: em(40, 20), - }, - video: { - marginTop: em(40, 20), - marginBottom: em(40, 20), - }, - figure: { - marginTop: em(40, 20), - marginBottom: em(40, 20), - }, - "figure > *": { - marginTop: "0", - marginBottom: "0", - }, - figcaption: { - fontSize: em(18, 20), - lineHeight: round(28 / 18), - marginTop: em(18, 18), - }, - code: { - fontSize: em(18, 20), - }, - "h2 code": { - fontSize: em(31, 36), - }, - "h3 code": { - fontSize: em(27, 30), - }, - pre: { - fontSize: em(18, 20), - lineHeight: round(32 / 18), - marginTop: em(36, 18), - marginBottom: em(36, 18), - borderRadius: rem(8), - paddingTop: em(20, 18), - paddingRight: em(24, 18), - paddingBottom: em(20, 18), - paddingLeft: em(24, 18), - }, - ol: { - marginTop: em(24, 20), - marginBottom: em(24, 20), - paddingLeft: em(32, 20), - }, - ul: { - marginTop: em(24, 20), - marginBottom: em(24, 20), - paddingLeft: em(32, 20), - }, - li: { - marginTop: em(12, 20), - marginBottom: em(12, 20), - }, - "ol > li": { - paddingLeft: em(8, 20), - }, - "ul > li": { - paddingLeft: em(8, 20), - }, - "> ul > li p": { - marginTop: em(16, 20), - marginBottom: em(16, 20), - }, - "> ul > li > *:first-child": { - marginTop: em(24, 20), - }, - "> ul > li > *:last-child": { - marginBottom: em(24, 20), - }, - "> ol > li > *:first-child": { - marginTop: em(24, 20), - }, - "> ol > li > *:last-child": { - marginBottom: em(24, 20), - }, - "ul ul, ul ol, ol ul, ol ol": { - marginTop: em(16, 20), - marginBottom: em(16, 20), - }, - hr: { - marginTop: em(56, 20), - marginBottom: em(56, 20), - }, - "hr + *": { - marginTop: "0", - }, - "h2 + *": { - marginTop: "0", - }, - "h3 + *": { - marginTop: "0", - }, - "h4 + *": { - marginTop: "0", - }, - table: { - fontSize: em(18, 20), - lineHeight: round(28 / 18), - }, - "thead th": { - paddingRight: em(12, 18), - paddingBottom: em(16, 18), - paddingLeft: em(12, 18), - }, - "thead th:first-child": { - paddingLeft: "0", - }, - "thead th:last-child": { - paddingRight: "0", - }, - "tbody td, tfoot td": { - paddingTop: em(16, 18), - paddingRight: em(12, 18), - paddingBottom: em(16, 18), - paddingLeft: em(12, 18), - }, - "tbody td:first-child, tfoot td:first-child": { - paddingLeft: "0", - }, - "tbody td:last-child, tfoot td:last-child": { - paddingRight: "0", - }, - }, - { - "> :first-child": { - marginTop: "0", - }, - "> :last-child": { - marginBottom: "0", - }, - }, - ], - }, - "2xl": { - css: [ - { - fontSize: rem(24), - lineHeight: round(40 / 24), - p: { - marginTop: em(32, 24), - marginBottom: em(32, 24), - }, - '[class~="lead"]': { - fontSize: em(30, 24), - lineHeight: round(44 / 30), - marginTop: em(32, 30), - marginBottom: em(32, 30), - }, - blockquote: { - marginTop: em(64, 36), - marginBottom: em(64, 36), - paddingLeft: em(40, 36), - }, - h1: { - fontSize: em(64, 24), - marginTop: "0", - marginBottom: em(56, 64), - lineHeight: round(64 / 64), - }, - h2: { - fontSize: em(48, 24), - marginTop: em(72, 48), - marginBottom: em(40, 48), - lineHeight: round(52 / 48), - }, - h3: { - fontSize: em(36, 24), - marginTop: em(56, 36), - marginBottom: em(24, 36), - lineHeight: round(44 / 36), - }, - h4: { - marginTop: em(40, 24), - marginBottom: em(16, 24), - lineHeight: round(36 / 24), - }, - img: { - marginTop: em(48, 24), - marginBottom: em(48, 24), - }, - video: { - marginTop: em(48, 24), - marginBottom: em(48, 24), - }, - figure: { - marginTop: em(48, 24), - marginBottom: em(48, 24), - }, - "figure > *": { - marginTop: "0", - marginBottom: "0", - }, - figcaption: { - fontSize: em(20, 24), - lineHeight: round(32 / 20), - marginTop: em(20, 20), - }, - code: { - fontSize: em(20, 24), - }, - "h2 code": { - fontSize: em(42, 48), - }, - "h3 code": { - fontSize: em(32, 36), - }, - pre: { - fontSize: em(20, 24), - lineHeight: round(36 / 20), - marginTop: em(40, 20), - marginBottom: em(40, 20), - borderRadius: rem(8), - paddingTop: em(24, 20), - paddingRight: em(32, 20), - paddingBottom: em(24, 20), - paddingLeft: em(32, 20), - }, - ol: { - marginTop: em(32, 24), - marginBottom: em(32, 24), - paddingLeft: em(38, 24), - }, - ul: { - marginTop: em(32, 24), - marginBottom: em(32, 24), - paddingLeft: em(38, 24), - }, - li: { - marginTop: em(12, 24), - marginBottom: em(12, 24), - }, - "ol > li": { - paddingLeft: em(10, 24), - }, - "ul > li": { - paddingLeft: em(10, 24), - }, - "> ul > li p": { - marginTop: em(20, 24), - marginBottom: em(20, 24), - }, - "> ul > li > *:first-child": { - marginTop: em(32, 24), - }, - "> ul > li > *:last-child": { - marginBottom: em(32, 24), - }, - "> ol > li > *:first-child": { - marginTop: em(32, 24), - }, - "> ol > li > *:last-child": { - marginBottom: em(32, 24), - }, - "ul ul, ul ol, ol ul, ol ol": { - marginTop: em(16, 24), - marginBottom: em(16, 24), - }, - hr: { - marginTop: em(72, 24), - marginBottom: em(72, 24), - }, - "hr + *": { - marginTop: "0", - }, - "h2 + *": { - marginTop: "0", - }, - "h3 + *": { - marginTop: "0", - }, - "h4 + *": { - marginTop: "0", - }, - table: { - fontSize: em(20, 24), - lineHeight: round(28 / 20), - }, - "thead th": { - paddingRight: em(12, 20), - paddingBottom: em(16, 20), - paddingLeft: em(12, 20), - }, - "thead th:first-child": { - paddingLeft: "0", - }, - "thead th:last-child": { - paddingRight: "0", - }, - "tbody td, tfoot td": { - paddingTop: em(16, 20), - paddingRight: em(12, 20), - paddingBottom: em(16, 20), - paddingLeft: em(12, 20), - }, - "tbody td:first-child, tfoot td:first-child": { - paddingLeft: "0", - }, - "tbody td:last-child, tfoot td:last-child": { - paddingRight: "0", - }, - }, - { - "> :first-child": { - marginTop: "0", - }, - "> :last-child": { - marginBottom: "0", - }, - }, - ], - }, + sm: { + css: [ + { + fontSize: rem(14), + lineHeight: round(24 / 14), + p: { + marginTop: em(16, 14), + marginBottom: em(16, 14), + }, + '[class~="lead"]': { + fontSize: em(18, 14), + lineHeight: round(28 / 18), + marginTop: em(16, 18), + marginBottom: em(16, 18), + }, + blockquote: { + marginTop: em(24, 18), + marginBottom: em(24, 18), + paddingLeft: em(20, 18), + }, + h1: { + fontSize: em(30, 14), + marginTop: "0", + marginBottom: em(24, 30), + lineHeight: round(36 / 30), + }, + h2: { + fontSize: em(20, 14), + marginTop: em(32, 20), + marginBottom: em(16, 20), + lineHeight: round(28 / 20), + }, + h3: { + fontSize: em(18, 14), + marginTop: em(28, 18), + marginBottom: em(8, 18), + lineHeight: round(28 / 18), + }, + h4: { + marginTop: em(20, 14), + marginBottom: em(8, 14), + lineHeight: round(20 / 14), + }, + img: { + marginTop: em(24, 14), + marginBottom: em(24, 14), + }, + video: { + marginTop: em(24, 14), + marginBottom: em(24, 14), + }, + figure: { + marginTop: em(24, 14), + marginBottom: em(24, 14), + }, + "figure > *": { + marginTop: "0", + marginBottom: "0", + }, + figcaption: { + fontSize: em(12, 14), + lineHeight: round(16 / 12), + marginTop: em(8, 12), + }, + code: { + fontSize: em(12, 14), + }, + "h2 code": { + fontSize: em(18, 20), + }, + "h3 code": { + fontSize: em(16, 18), + }, + pre: { + fontSize: em(12, 14), + lineHeight: round(20 / 12), + marginTop: em(20, 12), + marginBottom: em(20, 12), + borderRadius: rem(4), + paddingTop: em(8, 12), + paddingRight: em(12, 12), + paddingBottom: em(8, 12), + paddingLeft: em(12, 12), + }, + ol: { + marginTop: em(16, 14), + marginBottom: em(16, 14), + paddingLeft: em(22, 14), + }, + ul: { + marginTop: em(16, 14), + marginBottom: em(16, 14), + paddingLeft: em(22, 14), + }, + li: { + marginTop: em(4, 14), + marginBottom: em(4, 14), + }, + "ol > li": { + paddingLeft: em(6, 14), + }, + "ul > li": { + paddingLeft: em(6, 14), + }, + "> ul > li p": { + marginTop: em(8, 14), + marginBottom: em(8, 14), + }, + "> ul > li > *:first-child": { + marginTop: em(16, 14), + }, + "> ul > li > *:last-child": { + marginBottom: em(16, 14), + }, + "> ol > li > *:first-child": { + marginTop: em(16, 14), + }, + "> ol > li > *:last-child": { + marginBottom: em(16, 14), + }, + "ul ul, ul ol, ol ul, ol ol": { + marginTop: em(8, 14), + marginBottom: em(8, 14), + }, + hr: { + marginTop: em(40, 14), + marginBottom: em(40, 14), + }, + "hr + *": { + marginTop: "0", + }, + "h2 + *": { + marginTop: "0", + }, + "h3 + *": { + marginTop: "0", + }, + "h4 + *": { + marginTop: "0", + }, + table: { + fontSize: em(12, 14), + lineHeight: round(18 / 12), + }, + "thead th": { + paddingRight: em(12, 12), + paddingBottom: em(8, 12), + paddingLeft: em(12, 12), + }, + "thead th:first-child": { + paddingLeft: "0", + }, + "thead th:last-child": { + paddingRight: "0", + }, + "tbody td, tfoot td": { + paddingTop: em(8, 12), + paddingRight: em(12, 12), + paddingBottom: em(8, 12), + paddingLeft: em(12, 12), + }, + "tbody td:first-child, tfoot td:first-child": { + paddingLeft: "0", + }, + "tbody td:last-child, tfoot td:last-child": { + paddingRight: "0", + }, + }, + { + "> :first-child": { + marginTop: "0", + }, + "> :last-child": { + marginBottom: "0", + }, + }, + ], + }, + base: { + css: [ + { + fontSize: rem(16), + lineHeight: round(28 / 16), + p: { + marginTop: em(20, 16), + marginBottom: em(20, 16), + }, + '[class~="lead"]': { + fontSize: em(20, 16), + lineHeight: round(32 / 20), + marginTop: em(24, 20), + marginBottom: em(24, 20), + }, + blockquote: { + marginTop: em(32, 20), + marginBottom: em(32, 20), + paddingLeft: em(20, 20), + }, + h1: { + fontSize: em(36, 16), + marginTop: "0", + marginBottom: em(32, 36), + lineHeight: round(40 / 36), + }, + h2: { + fontSize: em(24, 16), + marginTop: em(48, 24), + marginBottom: em(24, 24), + lineHeight: round(32 / 24), + }, + h3: { + fontSize: em(20, 16), + marginTop: em(32, 20), + marginBottom: em(12, 20), + lineHeight: round(32 / 20), + }, + h4: { + marginTop: em(24, 16), + marginBottom: em(8, 16), + lineHeight: round(24 / 16), + }, + img: { + marginTop: em(32, 16), + marginBottom: em(32, 16), + }, + video: { + marginTop: em(32, 16), + marginBottom: em(32, 16), + }, + figure: { + marginTop: em(32, 16), + marginBottom: em(32, 16), + }, + "figure > *": { + marginTop: "0", + marginBottom: "0", + }, + figcaption: { + fontSize: em(14, 16), + lineHeight: round(20 / 14), + marginTop: em(12, 14), + }, + code: { + fontSize: em(14, 16), + }, + "h2 code": { + fontSize: em(21, 24), + }, + "h3 code": { + fontSize: em(18, 20), + }, + pre: { + fontSize: em(14, 16), + lineHeight: round(24 / 14), + marginTop: em(24, 14), + marginBottom: em(24, 14), + borderRadius: rem(6), + paddingTop: em(12, 14), + paddingRight: em(16, 14), + paddingBottom: em(12, 14), + paddingLeft: em(16, 14), + }, + ol: { + marginTop: em(20, 16), + marginBottom: em(20, 16), + paddingLeft: em(26, 16), + }, + ul: { + marginTop: em(20, 16), + marginBottom: em(20, 16), + paddingLeft: em(26, 16), + }, + li: { + marginTop: em(8, 16), + marginBottom: em(8, 16), + }, + "ol > li": { + paddingLeft: em(6, 16), + }, + "ul > li": { + paddingLeft: em(6, 16), + }, + "> ul > li p": { + marginTop: em(12, 16), + marginBottom: em(12, 16), + }, + "> ul > li > *:first-child": { + marginTop: em(20, 16), + }, + "> ul > li > *:last-child": { + marginBottom: em(20, 16), + }, + "> ol > li > *:first-child": { + marginTop: em(20, 16), + }, + "> ol > li > *:last-child": { + marginBottom: em(20, 16), + }, + "ul ul, ul ol, ol ul, ol ol": { + marginTop: em(12, 16), + marginBottom: em(12, 16), + }, + hr: { + marginTop: em(48, 16), + marginBottom: em(48, 16), + }, + "hr + *": { + marginTop: "0", + }, + "h2 + *": { + marginTop: "0", + }, + "h3 + *": { + marginTop: "0", + }, + "h4 + *": { + marginTop: "0", + }, + table: { + fontSize: em(14, 16), + lineHeight: round(24 / 14), + }, + "thead th": { + paddingRight: em(8, 14), + paddingBottom: em(8, 14), + paddingLeft: em(8, 14), + }, + "thead th:first-child": { + paddingLeft: "0", + }, + "thead th:last-child": { + paddingRight: "0", + }, + "tbody td, tfoot td": { + paddingTop: em(8, 14), + paddingRight: em(8, 14), + paddingBottom: em(8, 14), + paddingLeft: em(8, 14), + }, + "tbody td:first-child, tfoot td:first-child": { + paddingLeft: "0", + }, + "tbody td:last-child, tfoot td:last-child": { + paddingRight: "0", + }, + }, + { + "> :first-child": { + marginTop: "0", + }, + "> :last-child": { + marginBottom: "0", + }, + }, + ], + }, + lg: { + css: [ + { + fontSize: rem(18), + lineHeight: round(32 / 18), + p: { + marginTop: em(24, 18), + marginBottom: em(24, 18), + }, + '[class~="lead"]': { + fontSize: em(22, 18), + lineHeight: round(32 / 22), + marginTop: em(24, 22), + marginBottom: em(24, 22), + }, + blockquote: { + marginTop: em(40, 24), + marginBottom: em(40, 24), + paddingLeft: em(24, 24), + }, + h1: { + fontSize: em(48, 18), + marginTop: "0", + marginBottom: em(40, 48), + lineHeight: round(48 / 48), + }, + h2: { + fontSize: em(30, 18), + marginTop: em(56, 30), + marginBottom: em(32, 30), + lineHeight: round(40 / 30), + }, + h3: { + fontSize: em(24, 18), + marginTop: em(40, 24), + marginBottom: em(16, 24), + lineHeight: round(36 / 24), + }, + h4: { + marginTop: em(32, 18), + marginBottom: em(8, 18), + lineHeight: round(28 / 18), + }, + img: { + marginTop: em(32, 18), + marginBottom: em(32, 18), + }, + video: { + marginTop: em(32, 18), + marginBottom: em(32, 18), + }, + figure: { + marginTop: em(32, 18), + marginBottom: em(32, 18), + }, + "figure > *": { + marginTop: "0", + marginBottom: "0", + }, + figcaption: { + fontSize: em(16, 18), + lineHeight: round(24 / 16), + marginTop: em(16, 16), + }, + code: { + fontSize: em(16, 18), + }, + "h2 code": { + fontSize: em(26, 30), + }, + "h3 code": { + fontSize: em(21, 24), + }, + pre: { + fontSize: em(16, 18), + lineHeight: round(28 / 16), + marginTop: em(32, 16), + marginBottom: em(32, 16), + borderRadius: rem(6), + paddingTop: em(16, 16), + paddingRight: em(24, 16), + paddingBottom: em(16, 16), + paddingLeft: em(24, 16), + }, + ol: { + marginTop: em(24, 18), + marginBottom: em(24, 18), + paddingLeft: em(28, 18), + }, + ul: { + marginTop: em(24, 18), + marginBottom: em(24, 18), + paddingLeft: em(28, 18), + }, + li: { + marginTop: em(12, 18), + marginBottom: em(12, 18), + }, + "ol > li": { + paddingLeft: em(8, 18), + }, + "ul > li": { + paddingLeft: em(8, 18), + }, + "> ul > li p": { + marginTop: em(16, 18), + marginBottom: em(16, 18), + }, + "> ul > li > *:first-child": { + marginTop: em(24, 18), + }, + "> ul > li > *:last-child": { + marginBottom: em(24, 18), + }, + "> ol > li > *:first-child": { + marginTop: em(24, 18), + }, + "> ol > li > *:last-child": { + marginBottom: em(24, 18), + }, + "ul ul, ul ol, ol ul, ol ol": { + marginTop: em(16, 18), + marginBottom: em(16, 18), + }, + hr: { + marginTop: em(56, 18), + marginBottom: em(56, 18), + }, + "hr + *": { + marginTop: "0", + }, + "h2 + *": { + marginTop: "0", + }, + "h3 + *": { + marginTop: "0", + }, + "h4 + *": { + marginTop: "0", + }, + table: { + fontSize: em(16, 18), + lineHeight: round(24 / 16), + }, + "thead th": { + paddingRight: em(12, 16), + paddingBottom: em(12, 16), + paddingLeft: em(12, 16), + }, + "thead th:first-child": { + paddingLeft: "0", + }, + "thead th:last-child": { + paddingRight: "0", + }, + "tbody td, tfoot td": { + paddingTop: em(12, 16), + paddingRight: em(12, 16), + paddingBottom: em(12, 16), + paddingLeft: em(12, 16), + }, + "tbody td:first-child, tfoot td:first-child": { + paddingLeft: "0", + }, + "tbody td:last-child, tfoot td:last-child": { + paddingRight: "0", + }, + }, + { + "> :first-child": { + marginTop: "0", + }, + "> :last-child": { + marginBottom: "0", + }, + }, + ], + }, + xl: { + css: [ + { + fontSize: rem(20), + lineHeight: round(36 / 20), + p: { + marginTop: em(24, 20), + marginBottom: em(24, 20), + }, + '[class~="lead"]': { + fontSize: em(24, 20), + lineHeight: round(36 / 24), + marginTop: em(24, 24), + marginBottom: em(24, 24), + }, + blockquote: { + marginTop: em(48, 30), + marginBottom: em(48, 30), + paddingLeft: em(32, 30), + }, + h1: { + fontSize: em(56, 20), + marginTop: "0", + marginBottom: em(48, 56), + lineHeight: round(56 / 56), + }, + h2: { + fontSize: em(36, 20), + marginTop: em(56, 36), + marginBottom: em(32, 36), + lineHeight: round(40 / 36), + }, + h3: { + fontSize: em(30, 20), + marginTop: em(48, 30), + marginBottom: em(20, 30), + lineHeight: round(40 / 30), + }, + h4: { + marginTop: em(36, 20), + marginBottom: em(12, 20), + lineHeight: round(32 / 20), + }, + img: { + marginTop: em(40, 20), + marginBottom: em(40, 20), + }, + video: { + marginTop: em(40, 20), + marginBottom: em(40, 20), + }, + figure: { + marginTop: em(40, 20), + marginBottom: em(40, 20), + }, + "figure > *": { + marginTop: "0", + marginBottom: "0", + }, + figcaption: { + fontSize: em(18, 20), + lineHeight: round(28 / 18), + marginTop: em(18, 18), + }, + code: { + fontSize: em(18, 20), + }, + "h2 code": { + fontSize: em(31, 36), + }, + "h3 code": { + fontSize: em(27, 30), + }, + pre: { + fontSize: em(18, 20), + lineHeight: round(32 / 18), + marginTop: em(36, 18), + marginBottom: em(36, 18), + borderRadius: rem(8), + paddingTop: em(20, 18), + paddingRight: em(24, 18), + paddingBottom: em(20, 18), + paddingLeft: em(24, 18), + }, + ol: { + marginTop: em(24, 20), + marginBottom: em(24, 20), + paddingLeft: em(32, 20), + }, + ul: { + marginTop: em(24, 20), + marginBottom: em(24, 20), + paddingLeft: em(32, 20), + }, + li: { + marginTop: em(12, 20), + marginBottom: em(12, 20), + }, + "ol > li": { + paddingLeft: em(8, 20), + }, + "ul > li": { + paddingLeft: em(8, 20), + }, + "> ul > li p": { + marginTop: em(16, 20), + marginBottom: em(16, 20), + }, + "> ul > li > *:first-child": { + marginTop: em(24, 20), + }, + "> ul > li > *:last-child": { + marginBottom: em(24, 20), + }, + "> ol > li > *:first-child": { + marginTop: em(24, 20), + }, + "> ol > li > *:last-child": { + marginBottom: em(24, 20), + }, + "ul ul, ul ol, ol ul, ol ol": { + marginTop: em(16, 20), + marginBottom: em(16, 20), + }, + hr: { + marginTop: em(56, 20), + marginBottom: em(56, 20), + }, + "hr + *": { + marginTop: "0", + }, + "h2 + *": { + marginTop: "0", + }, + "h3 + *": { + marginTop: "0", + }, + "h4 + *": { + marginTop: "0", + }, + table: { + fontSize: em(18, 20), + lineHeight: round(28 / 18), + }, + "thead th": { + paddingRight: em(12, 18), + paddingBottom: em(16, 18), + paddingLeft: em(12, 18), + }, + "thead th:first-child": { + paddingLeft: "0", + }, + "thead th:last-child": { + paddingRight: "0", + }, + "tbody td, tfoot td": { + paddingTop: em(16, 18), + paddingRight: em(12, 18), + paddingBottom: em(16, 18), + paddingLeft: em(12, 18), + }, + "tbody td:first-child, tfoot td:first-child": { + paddingLeft: "0", + }, + "tbody td:last-child, tfoot td:last-child": { + paddingRight: "0", + }, + }, + { + "> :first-child": { + marginTop: "0", + }, + "> :last-child": { + marginBottom: "0", + }, + }, + ], + }, + "2xl": { + css: [ + { + fontSize: rem(24), + lineHeight: round(40 / 24), + p: { + marginTop: em(32, 24), + marginBottom: em(32, 24), + }, + '[class~="lead"]': { + fontSize: em(30, 24), + lineHeight: round(44 / 30), + marginTop: em(32, 30), + marginBottom: em(32, 30), + }, + blockquote: { + marginTop: em(64, 36), + marginBottom: em(64, 36), + paddingLeft: em(40, 36), + }, + h1: { + fontSize: em(64, 24), + marginTop: "0", + marginBottom: em(56, 64), + lineHeight: round(64 / 64), + }, + h2: { + fontSize: em(48, 24), + marginTop: em(72, 48), + marginBottom: em(40, 48), + lineHeight: round(52 / 48), + }, + h3: { + fontSize: em(36, 24), + marginTop: em(56, 36), + marginBottom: em(24, 36), + lineHeight: round(44 / 36), + }, + h4: { + marginTop: em(40, 24), + marginBottom: em(16, 24), + lineHeight: round(36 / 24), + }, + img: { + marginTop: em(48, 24), + marginBottom: em(48, 24), + }, + video: { + marginTop: em(48, 24), + marginBottom: em(48, 24), + }, + figure: { + marginTop: em(48, 24), + marginBottom: em(48, 24), + }, + "figure > *": { + marginTop: "0", + marginBottom: "0", + }, + figcaption: { + fontSize: em(20, 24), + lineHeight: round(32 / 20), + marginTop: em(20, 20), + }, + code: { + fontSize: em(20, 24), + }, + "h2 code": { + fontSize: em(42, 48), + }, + "h3 code": { + fontSize: em(32, 36), + }, + pre: { + fontSize: em(20, 24), + lineHeight: round(36 / 20), + marginTop: em(40, 20), + marginBottom: em(40, 20), + borderRadius: rem(8), + paddingTop: em(24, 20), + paddingRight: em(32, 20), + paddingBottom: em(24, 20), + paddingLeft: em(32, 20), + }, + ol: { + marginTop: em(32, 24), + marginBottom: em(32, 24), + paddingLeft: em(38, 24), + }, + ul: { + marginTop: em(32, 24), + marginBottom: em(32, 24), + paddingLeft: em(38, 24), + }, + li: { + marginTop: em(12, 24), + marginBottom: em(12, 24), + }, + "ol > li": { + paddingLeft: em(10, 24), + }, + "ul > li": { + paddingLeft: em(10, 24), + }, + "> ul > li p": { + marginTop: em(20, 24), + marginBottom: em(20, 24), + }, + "> ul > li > *:first-child": { + marginTop: em(32, 24), + }, + "> ul > li > *:last-child": { + marginBottom: em(32, 24), + }, + "> ol > li > *:first-child": { + marginTop: em(32, 24), + }, + "> ol > li > *:last-child": { + marginBottom: em(32, 24), + }, + "ul ul, ul ol, ol ul, ol ol": { + marginTop: em(16, 24), + marginBottom: em(16, 24), + }, + hr: { + marginTop: em(72, 24), + marginBottom: em(72, 24), + }, + "hr + *": { + marginTop: "0", + }, + "h2 + *": { + marginTop: "0", + }, + "h3 + *": { + marginTop: "0", + }, + "h4 + *": { + marginTop: "0", + }, + table: { + fontSize: em(20, 24), + lineHeight: round(28 / 20), + }, + "thead th": { + paddingRight: em(12, 20), + paddingBottom: em(16, 20), + paddingLeft: em(12, 20), + }, + "thead th:first-child": { + paddingLeft: "0", + }, + "thead th:last-child": { + paddingRight: "0", + }, + "tbody td, tfoot td": { + paddingTop: em(16, 20), + paddingRight: em(12, 20), + paddingBottom: em(16, 20), + paddingLeft: em(12, 20), + }, + "tbody td:first-child, tfoot td:first-child": { + paddingLeft: "0", + }, + "tbody td:last-child, tfoot td:last-child": { + paddingRight: "0", + }, + }, + { + "> :first-child": { + marginTop: "0", + }, + "> :last-child": { + marginBottom: "0", + }, + }, + ], + }, - // Invert (for dark mode) - invert: { - css: { - "--tw-prose-body": "var(--tw-prose-invert-body)", - "--tw-prose-headings": "var(--tw-prose-invert-headings)", - "--tw-prose-lead": "var(--tw-prose-invert-lead)", - "--tw-prose-links": "var(--tw-prose-invert-links)", - "--tw-prose-bold": "var(--tw-prose-invert-bold)", - "--tw-prose-counters": "var(--tw-prose-invert-counters)", - "--tw-prose-bullets": "var(--tw-prose-invert-bullets)", - "--tw-prose-hr": "var(--tw-prose-invert-hr)", - "--tw-prose-quotes": "var(--tw-prose-invert-quotes)", - "--tw-prose-quote-borders": "var(--tw-prose-invert-quote-borders)", - "--tw-prose-captions": "var(--tw-prose-invert-captions)", - "--tw-prose-code": "var(--tw-prose-invert-code)", - "--tw-prose-pre-code": "var(--tw-prose-invert-pre-code)", - "--tw-prose-pre-bg": "var(--tw-prose-invert-pre-bg)", - "--tw-prose-th-borders": "var(--tw-prose-invert-th-borders)", - "--tw-prose-td-borders": "var(--tw-prose-invert-td-borders)", - }, - }, + // Invert (for dark mode) + invert: { + css: { + "--tw-prose-body": "var(--tw-prose-invert-body)", + "--tw-prose-headings": "var(--tw-prose-invert-headings)", + "--tw-prose-lead": "var(--tw-prose-invert-lead)", + "--tw-prose-links": "var(--tw-prose-invert-links)", + "--tw-prose-bold": "var(--tw-prose-invert-bold)", + "--tw-prose-counters": "var(--tw-prose-invert-counters)", + "--tw-prose-bullets": "var(--tw-prose-invert-bullets)", + "--tw-prose-hr": "var(--tw-prose-invert-hr)", + "--tw-prose-quotes": "var(--tw-prose-invert-quotes)", + "--tw-prose-quote-borders": "var(--tw-prose-invert-quote-borders)", + "--tw-prose-captions": "var(--tw-prose-invert-captions)", + "--tw-prose-code": "var(--tw-prose-invert-code)", + "--tw-prose-pre-code": "var(--tw-prose-invert-pre-code)", + "--tw-prose-pre-bg": "var(--tw-prose-invert-pre-bg)", + "--tw-prose-th-borders": "var(--tw-prose-invert-th-borders)", + "--tw-prose-td-borders": "var(--tw-prose-invert-td-borders)", + }, + }, - // Gray color themes + // Gray color themes - slate: { - css: { - "--tw-prose-body": colors.slate[700], - "--tw-prose-headings": colors.slate[900], - "--tw-prose-lead": colors.slate[600], - "--tw-prose-links": colors.slate[900], - "--tw-prose-bold": colors.slate[900], - "--tw-prose-counters": colors.slate[500], - "--tw-prose-bullets": colors.slate[300], - "--tw-prose-hr": colors.slate[200], - "--tw-prose-quotes": colors.slate[900], - "--tw-prose-quote-borders": colors.slate[200], - "--tw-prose-captions": colors.slate[500], - "--tw-prose-code": colors.slate[900], - "--tw-prose-pre-code": colors.slate[200], - "--tw-prose-pre-bg": colors.slate[800], - "--tw-prose-th-borders": colors.slate[300], - "--tw-prose-td-borders": colors.slate[200], - "--tw-prose-invert-body": colors.slate[300], - "--tw-prose-invert-headings": colors.white, - "--tw-prose-invert-lead": colors.slate[400], - "--tw-prose-invert-links": colors.white, - "--tw-prose-invert-bold": colors.white, - "--tw-prose-invert-counters": colors.slate[400], - "--tw-prose-invert-bullets": colors.slate[600], - "--tw-prose-invert-hr": colors.slate[700], - "--tw-prose-invert-quotes": colors.slate[100], - "--tw-prose-invert-quote-borders": colors.slate[700], - "--tw-prose-invert-captions": colors.slate[400], - "--tw-prose-invert-code": colors.white, - "--tw-prose-invert-pre-code": colors.slate[300], - "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", - "--tw-prose-invert-th-borders": colors.slate[600], - "--tw-prose-invert-td-borders": colors.slate[700], - }, - }, + slate: { + css: { + "--tw-prose-body": colors.slate[700], + "--tw-prose-headings": colors.slate[900], + "--tw-prose-lead": colors.slate[600], + "--tw-prose-links": colors.slate[900], + "--tw-prose-bold": colors.slate[900], + "--tw-prose-counters": colors.slate[500], + "--tw-prose-bullets": colors.slate[300], + "--tw-prose-hr": colors.slate[200], + "--tw-prose-quotes": colors.slate[900], + "--tw-prose-quote-borders": colors.slate[200], + "--tw-prose-captions": colors.slate[500], + "--tw-prose-code": colors.slate[900], + "--tw-prose-pre-code": colors.slate[200], + "--tw-prose-pre-bg": colors.slate[800], + "--tw-prose-th-borders": colors.slate[300], + "--tw-prose-td-borders": colors.slate[200], + "--tw-prose-invert-body": colors.slate[300], + "--tw-prose-invert-headings": colors.white, + "--tw-prose-invert-lead": colors.slate[400], + "--tw-prose-invert-links": colors.white, + "--tw-prose-invert-bold": colors.white, + "--tw-prose-invert-counters": colors.slate[400], + "--tw-prose-invert-bullets": colors.slate[600], + "--tw-prose-invert-hr": colors.slate[700], + "--tw-prose-invert-quotes": colors.slate[100], + "--tw-prose-invert-quote-borders": colors.slate[700], + "--tw-prose-invert-captions": colors.slate[400], + "--tw-prose-invert-code": colors.white, + "--tw-prose-invert-pre-code": colors.slate[300], + "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", + "--tw-prose-invert-th-borders": colors.slate[600], + "--tw-prose-invert-td-borders": colors.slate[700], + }, + }, - gray: { - css: { - "--tw-prose-body": colors.gray[700], - "--tw-prose-headings": colors.gray[900], - "--tw-prose-lead": colors.gray[600], - "--tw-prose-links": colors.gray[900], - "--tw-prose-bold": colors.gray[900], - "--tw-prose-counters": colors.gray[500], - "--tw-prose-bullets": colors.gray[300], - "--tw-prose-hr": colors.gray[200], - "--tw-prose-quotes": colors.gray[900], - "--tw-prose-quote-borders": colors.gray[200], - "--tw-prose-captions": colors.gray[500], - "--tw-prose-code": colors.gray[900], - "--tw-prose-pre-code": colors.gray[200], - "--tw-prose-pre-bg": colors.gray[800], - "--tw-prose-th-borders": colors.gray[300], - "--tw-prose-td-borders": colors.gray[200], - "--tw-prose-invert-body": colors.gray[300], - "--tw-prose-invert-headings": colors.white, - "--tw-prose-invert-lead": colors.gray[400], - "--tw-prose-invert-links": colors.white, - "--tw-prose-invert-bold": colors.white, - "--tw-prose-invert-counters": colors.gray[400], - "--tw-prose-invert-bullets": colors.gray[600], - "--tw-prose-invert-hr": colors.gray[700], - "--tw-prose-invert-quotes": colors.gray[100], - "--tw-prose-invert-quote-borders": colors.gray[700], - "--tw-prose-invert-captions": colors.gray[400], - "--tw-prose-invert-code": colors.white, - "--tw-prose-invert-pre-code": colors.gray[300], - "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", - "--tw-prose-invert-th-borders": colors.gray[600], - "--tw-prose-invert-td-borders": colors.gray[700], - }, - }, + gray: { + css: { + "--tw-prose-body": colors.gray[700], + "--tw-prose-headings": colors.gray[900], + "--tw-prose-lead": colors.gray[600], + "--tw-prose-links": colors.gray[900], + "--tw-prose-bold": colors.gray[900], + "--tw-prose-counters": colors.gray[500], + "--tw-prose-bullets": colors.gray[300], + "--tw-prose-hr": colors.gray[200], + "--tw-prose-quotes": colors.gray[900], + "--tw-prose-quote-borders": colors.gray[200], + "--tw-prose-captions": colors.gray[500], + "--tw-prose-code": colors.gray[900], + "--tw-prose-pre-code": colors.gray[200], + "--tw-prose-pre-bg": colors.gray[800], + "--tw-prose-th-borders": colors.gray[300], + "--tw-prose-td-borders": colors.gray[200], + "--tw-prose-invert-body": colors.gray[300], + "--tw-prose-invert-headings": colors.white, + "--tw-prose-invert-lead": colors.gray[400], + "--tw-prose-invert-links": colors.white, + "--tw-prose-invert-bold": colors.white, + "--tw-prose-invert-counters": colors.gray[400], + "--tw-prose-invert-bullets": colors.gray[600], + "--tw-prose-invert-hr": colors.gray[700], + "--tw-prose-invert-quotes": colors.gray[100], + "--tw-prose-invert-quote-borders": colors.gray[700], + "--tw-prose-invert-captions": colors.gray[400], + "--tw-prose-invert-code": colors.white, + "--tw-prose-invert-pre-code": colors.gray[300], + "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", + "--tw-prose-invert-th-borders": colors.gray[600], + "--tw-prose-invert-td-borders": colors.gray[700], + }, + }, - zinc: { - css: { - "--tw-prose-body": colors.zinc[700], - "--tw-prose-headings": colors.zinc[900], - "--tw-prose-lead": colors.zinc[600], - "--tw-prose-links": colors.zinc[900], - "--tw-prose-bold": colors.zinc[900], - "--tw-prose-counters": colors.zinc[500], - "--tw-prose-bullets": colors.zinc[300], - "--tw-prose-hr": colors.zinc[200], - "--tw-prose-quotes": colors.zinc[900], - "--tw-prose-quote-borders": colors.zinc[200], - "--tw-prose-captions": colors.zinc[500], - "--tw-prose-code": colors.zinc[900], - "--tw-prose-pre-code": colors.zinc[200], - "--tw-prose-pre-bg": colors.zinc[800], - "--tw-prose-th-borders": colors.zinc[300], - "--tw-prose-td-borders": colors.zinc[200], - "--tw-prose-invert-body": colors.zinc[300], - "--tw-prose-invert-headings": colors.white, - "--tw-prose-invert-lead": colors.zinc[400], - "--tw-prose-invert-links": colors.white, - "--tw-prose-invert-bold": colors.white, - "--tw-prose-invert-counters": colors.zinc[400], - "--tw-prose-invert-bullets": colors.zinc[600], - "--tw-prose-invert-hr": colors.zinc[700], - "--tw-prose-invert-quotes": colors.zinc[100], - "--tw-prose-invert-quote-borders": colors.zinc[700], - "--tw-prose-invert-captions": colors.zinc[400], - "--tw-prose-invert-code": colors.white, - "--tw-prose-invert-pre-code": colors.zinc[300], - "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", - "--tw-prose-invert-th-borders": colors.zinc[600], - "--tw-prose-invert-td-borders": colors.zinc[700], - }, - }, + zinc: { + css: { + "--tw-prose-body": colors.zinc[700], + "--tw-prose-headings": colors.zinc[900], + "--tw-prose-lead": colors.zinc[600], + "--tw-prose-links": colors.zinc[900], + "--tw-prose-bold": colors.zinc[900], + "--tw-prose-counters": colors.zinc[500], + "--tw-prose-bullets": colors.zinc[300], + "--tw-prose-hr": colors.zinc[200], + "--tw-prose-quotes": colors.zinc[900], + "--tw-prose-quote-borders": colors.zinc[200], + "--tw-prose-captions": colors.zinc[500], + "--tw-prose-code": colors.zinc[900], + "--tw-prose-pre-code": colors.zinc[200], + "--tw-prose-pre-bg": colors.zinc[800], + "--tw-prose-th-borders": colors.zinc[300], + "--tw-prose-td-borders": colors.zinc[200], + "--tw-prose-invert-body": colors.zinc[300], + "--tw-prose-invert-headings": colors.white, + "--tw-prose-invert-lead": colors.zinc[400], + "--tw-prose-invert-links": colors.white, + "--tw-prose-invert-bold": colors.white, + "--tw-prose-invert-counters": colors.zinc[400], + "--tw-prose-invert-bullets": colors.zinc[600], + "--tw-prose-invert-hr": colors.zinc[700], + "--tw-prose-invert-quotes": colors.zinc[100], + "--tw-prose-invert-quote-borders": colors.zinc[700], + "--tw-prose-invert-captions": colors.zinc[400], + "--tw-prose-invert-code": colors.white, + "--tw-prose-invert-pre-code": colors.zinc[300], + "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", + "--tw-prose-invert-th-borders": colors.zinc[600], + "--tw-prose-invert-td-borders": colors.zinc[700], + }, + }, - neutral: { - css: { - "--tw-prose-body": colors.neutral[700], - "--tw-prose-headings": colors.neutral[900], - "--tw-prose-lead": colors.neutral[600], - "--tw-prose-links": colors.neutral[900], - "--tw-prose-bold": colors.neutral[900], - "--tw-prose-counters": colors.neutral[500], - "--tw-prose-bullets": colors.neutral[300], - "--tw-prose-hr": colors.neutral[200], - "--tw-prose-quotes": colors.neutral[900], - "--tw-prose-quote-borders": colors.neutral[200], - "--tw-prose-captions": colors.neutral[500], - "--tw-prose-code": colors.neutral[900], - "--tw-prose-pre-code": colors.neutral[200], - "--tw-prose-pre-bg": colors.neutral[800], - "--tw-prose-th-borders": colors.neutral[300], - "--tw-prose-td-borders": colors.neutral[200], - "--tw-prose-invert-body": colors.neutral[300], - "--tw-prose-invert-headings": colors.white, - "--tw-prose-invert-lead": colors.neutral[400], - "--tw-prose-invert-links": colors.white, - "--tw-prose-invert-bold": colors.white, - "--tw-prose-invert-counters": colors.neutral[400], - "--tw-prose-invert-bullets": colors.neutral[600], - "--tw-prose-invert-hr": colors.neutral[700], - "--tw-prose-invert-quotes": colors.neutral[100], - "--tw-prose-invert-quote-borders": colors.neutral[700], - "--tw-prose-invert-captions": colors.neutral[400], - "--tw-prose-invert-code": colors.white, - "--tw-prose-invert-pre-code": colors.neutral[300], - "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", - "--tw-prose-invert-th-borders": colors.neutral[600], - "--tw-prose-invert-td-borders": colors.neutral[700], - }, - }, + neutral: { + css: { + "--tw-prose-body": colors.neutral[700], + "--tw-prose-headings": colors.neutral[900], + "--tw-prose-lead": colors.neutral[600], + "--tw-prose-links": colors.neutral[900], + "--tw-prose-bold": colors.neutral[900], + "--tw-prose-counters": colors.neutral[500], + "--tw-prose-bullets": colors.neutral[300], + "--tw-prose-hr": colors.neutral[200], + "--tw-prose-quotes": colors.neutral[900], + "--tw-prose-quote-borders": colors.neutral[200], + "--tw-prose-captions": colors.neutral[500], + "--tw-prose-code": colors.neutral[900], + "--tw-prose-pre-code": colors.neutral[200], + "--tw-prose-pre-bg": colors.neutral[800], + "--tw-prose-th-borders": colors.neutral[300], + "--tw-prose-td-borders": colors.neutral[200], + "--tw-prose-invert-body": colors.neutral[300], + "--tw-prose-invert-headings": colors.white, + "--tw-prose-invert-lead": colors.neutral[400], + "--tw-prose-invert-links": colors.white, + "--tw-prose-invert-bold": colors.white, + "--tw-prose-invert-counters": colors.neutral[400], + "--tw-prose-invert-bullets": colors.neutral[600], + "--tw-prose-invert-hr": colors.neutral[700], + "--tw-prose-invert-quotes": colors.neutral[100], + "--tw-prose-invert-quote-borders": colors.neutral[700], + "--tw-prose-invert-captions": colors.neutral[400], + "--tw-prose-invert-code": colors.white, + "--tw-prose-invert-pre-code": colors.neutral[300], + "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", + "--tw-prose-invert-th-borders": colors.neutral[600], + "--tw-prose-invert-td-borders": colors.neutral[700], + }, + }, - stone: { - css: { - "--tw-prose-body": colors.stone[700], - "--tw-prose-headings": colors.stone[900], - "--tw-prose-lead": colors.stone[600], - "--tw-prose-links": colors.stone[900], - "--tw-prose-bold": colors.stone[900], - "--tw-prose-counters": colors.stone[500], - "--tw-prose-bullets": colors.stone[300], - "--tw-prose-hr": colors.stone[200], - "--tw-prose-quotes": colors.stone[900], - "--tw-prose-quote-borders": colors.stone[200], - "--tw-prose-captions": colors.stone[500], - "--tw-prose-code": colors.stone[900], - "--tw-prose-pre-code": colors.stone[200], - "--tw-prose-pre-bg": colors.stone[800], - "--tw-prose-th-borders": colors.stone[300], - "--tw-prose-td-borders": colors.stone[200], - "--tw-prose-invert-body": colors.stone[300], - "--tw-prose-invert-headings": colors.white, - "--tw-prose-invert-lead": colors.stone[400], - "--tw-prose-invert-links": colors.white, - "--tw-prose-invert-bold": colors.white, - "--tw-prose-invert-counters": colors.stone[400], - "--tw-prose-invert-bullets": colors.stone[600], - "--tw-prose-invert-hr": colors.stone[700], - "--tw-prose-invert-quotes": colors.stone[100], - "--tw-prose-invert-quote-borders": colors.stone[700], - "--tw-prose-invert-captions": colors.stone[400], - "--tw-prose-invert-code": colors.white, - "--tw-prose-invert-pre-code": colors.stone[300], - "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", - "--tw-prose-invert-th-borders": colors.stone[600], - "--tw-prose-invert-td-borders": colors.stone[700], - }, - }, + stone: { + css: { + "--tw-prose-body": colors.stone[700], + "--tw-prose-headings": colors.stone[900], + "--tw-prose-lead": colors.stone[600], + "--tw-prose-links": colors.stone[900], + "--tw-prose-bold": colors.stone[900], + "--tw-prose-counters": colors.stone[500], + "--tw-prose-bullets": colors.stone[300], + "--tw-prose-hr": colors.stone[200], + "--tw-prose-quotes": colors.stone[900], + "--tw-prose-quote-borders": colors.stone[200], + "--tw-prose-captions": colors.stone[500], + "--tw-prose-code": colors.stone[900], + "--tw-prose-pre-code": colors.stone[200], + "--tw-prose-pre-bg": colors.stone[800], + "--tw-prose-th-borders": colors.stone[300], + "--tw-prose-td-borders": colors.stone[200], + "--tw-prose-invert-body": colors.stone[300], + "--tw-prose-invert-headings": colors.white, + "--tw-prose-invert-lead": colors.stone[400], + "--tw-prose-invert-links": colors.white, + "--tw-prose-invert-bold": colors.white, + "--tw-prose-invert-counters": colors.stone[400], + "--tw-prose-invert-bullets": colors.stone[600], + "--tw-prose-invert-hr": colors.stone[700], + "--tw-prose-invert-quotes": colors.stone[100], + "--tw-prose-invert-quote-borders": colors.stone[700], + "--tw-prose-invert-captions": colors.stone[400], + "--tw-prose-invert-code": colors.white, + "--tw-prose-invert-pre-code": colors.stone[300], + "--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)", + "--tw-prose-invert-th-borders": colors.stone[600], + "--tw-prose-invert-td-borders": colors.stone[700], + }, + }, - // Link-only themes (for backward compatibility) + // Link-only themes (for backward compatibility) - red: { - css: { - "--tw-prose-links": colors.red[600], - "--tw-prose-invert-links": colors.red[500], - }, - }, + red: { + css: { + "--tw-prose-links": colors.red[600], + "--tw-prose-invert-links": colors.red[500], + }, + }, - orange: { - css: { - "--tw-prose-links": colors.orange[600], - "--tw-prose-invert-links": colors.orange[500], - }, - }, + orange: { + css: { + "--tw-prose-links": colors.orange[600], + "--tw-prose-invert-links": colors.orange[500], + }, + }, - amber: { - css: { - "--tw-prose-links": colors.amber[600], - "--tw-prose-invert-links": colors.amber[500], - }, - }, + amber: { + css: { + "--tw-prose-links": colors.amber[600], + "--tw-prose-invert-links": colors.amber[500], + }, + }, - yellow: { - css: { - "--tw-prose-links": colors.yellow[600], - "--tw-prose-invert-links": colors.yellow[500], - }, - }, + yellow: { + css: { + "--tw-prose-links": colors.yellow[600], + "--tw-prose-invert-links": colors.yellow[500], + }, + }, - lime: { - css: { - "--tw-prose-links": colors.lime[600], - "--tw-prose-invert-links": colors.lime[500], - }, - }, + lime: { + css: { + "--tw-prose-links": colors.lime[600], + "--tw-prose-invert-links": colors.lime[500], + }, + }, - green: { - css: { - "--tw-prose-links": colors.green[600], - "--tw-prose-invert-links": colors.green[500], - }, - }, + green: { + css: { + "--tw-prose-links": colors.green[600], + "--tw-prose-invert-links": colors.green[500], + }, + }, - emerald: { - css: { - "--tw-prose-links": colors.emerald[600], - "--tw-prose-invert-links": colors.emerald[500], - }, - }, + emerald: { + css: { + "--tw-prose-links": colors.emerald[600], + "--tw-prose-invert-links": colors.emerald[500], + }, + }, - teal: { - css: { - "--tw-prose-links": colors.teal[600], - "--tw-prose-invert-links": colors.teal[500], - }, - }, + teal: { + css: { + "--tw-prose-links": colors.teal[600], + "--tw-prose-invert-links": colors.teal[500], + }, + }, - cyan: { - css: { - "--tw-prose-links": colors.cyan[600], - "--tw-prose-invert-links": colors.cyan[500], - }, - }, + cyan: { + css: { + "--tw-prose-links": colors.cyan[600], + "--tw-prose-invert-links": colors.cyan[500], + }, + }, - sky: { - css: { - "--tw-prose-links": colors.sky[600], - "--tw-prose-invert-links": colors.sky[500], - }, - }, + sky: { + css: { + "--tw-prose-links": colors.sky[600], + "--tw-prose-invert-links": colors.sky[500], + }, + }, - blue: { - css: { - "--tw-prose-links": colors.blue[600], - "--tw-prose-invert-links": colors.blue[500], - }, - }, + blue: { + css: { + "--tw-prose-links": colors.blue[600], + "--tw-prose-invert-links": colors.blue[500], + }, + }, - indigo: { - css: { - "--tw-prose-links": colors.indigo[600], - "--tw-prose-invert-links": colors.indigo[500], - }, - }, + indigo: { + css: { + "--tw-prose-links": colors.indigo[600], + "--tw-prose-invert-links": colors.indigo[500], + }, + }, - violet: { - css: { - "--tw-prose-links": colors.violet[600], - "--tw-prose-invert-links": colors.violet[500], - }, - }, + violet: { + css: { + "--tw-prose-links": colors.violet[600], + "--tw-prose-invert-links": colors.violet[500], + }, + }, - purple: { - css: { - "--tw-prose-links": colors.purple[600], - "--tw-prose-invert-links": colors.purple[500], - }, - }, + purple: { + css: { + "--tw-prose-links": colors.purple[600], + "--tw-prose-invert-links": colors.purple[500], + }, + }, - fuchsia: { - css: { - "--tw-prose-links": colors.fuchsia[600], - "--tw-prose-invert-links": colors.fuchsia[500], - }, - }, + fuchsia: { + css: { + "--tw-prose-links": colors.fuchsia[600], + "--tw-prose-invert-links": colors.fuchsia[500], + }, + }, - pink: { - css: { - "--tw-prose-links": colors.pink[600], - "--tw-prose-invert-links": colors.pink[500], - }, - }, + pink: { + css: { + "--tw-prose-links": colors.pink[600], + "--tw-prose-invert-links": colors.pink[500], + }, + }, - rose: { - css: { - "--tw-prose-links": colors.rose[600], - "--tw-prose-invert-links": colors.rose[500], - }, - }, + rose: { + css: { + "--tw-prose-links": colors.rose[600], + "--tw-prose-invert-links": colors.rose[500], + }, + }, }; const plugin = require("tailwindcss/plugin"); /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: "class", - content: [ - "./pages/**/*.{js,ts,jsx,tsx}", - "./components/**/*.{js,ts,jsx,tsx}", - ], - theme: { - extend: { - colors: { - // You can generate your own colors with this tool: https://javisperez.github.io/tailwindcolorshades/ - primary: { - 50: '#fcfdf7', - 100: '#f8fcee', - 200: '#eef6d5', - 300: '#e3f1bc', - 400: '#cfe78a', - 500: '#badc58', - 600: '#a7c64f', - 700: '#8ca542', - 800: '#708435', - 900: '#5b6c2b', - DEFAULT: "#badc58", - }, - grey: "#0d1117", - mineshaft: { - 50: '#f5f5f5', - 100: '#ebebeb', - 200: '#ccccce', - 300: '#adaeb0', - 400: '#707174', - 500: '#323439', - 600: '#2d2f33', - 700: '#26272b', - 800: '#1e1f22', - 900: '#19191c', - DEFAULT: "#323439", - }, - chicago: { - 50: '#f7f7f7', - 100: '#efefef', - 200: '#d6d6d7', - 300: '#bdbebf', - 400: '#8c8d8e', - 500: '#5b5c5e', - 600: '#525355', - 700: '#444547', - 800: '#373738', - 900: '#2d2d2e', - DEFAULT: "#5b5c5e", - }, - bunker: { - 50: '#f3f4f4', - 100: '#e8e8e9', - 200: '#c5c6c8', - 300: '#a2a4a6', - 400: '#5d5f64', - 500: '#171b21', - 600: '#15181e', - 700: '#111419', - 800: '#0e1014', - 900: '#0b0d10', - DEFAULT: "#171B21", - }, - githubblack: "#020409", - blue2: "#130f40", - blue1: "#3498db", - yellow: { - 50: '#fefcf3', - 100: '#fef9e7', - 200: '#fcf0c3', - 300: '#f9e79f', - 400: '#f5d657', - 500: '#f1c40f', - 600: '#d9b00e', - 700: '#b5930b', - 800: '#917609', - 900: '#766007', - DEFAULT: "#f1c40f", - }, - red: { - 50: '#fef6f5', - 100: '#fdedec', - 200: '#f9d2ce', - 300: '#f5b7b1', - 400: '#ee8277', - 500: '#e74c3c', - 600: '#d04436', - 700: '#ad392d', - 800: '#8b2e24', - 900: '#71251d', - DEFAULT: "#e74c3c", - }, - orange: "#f39c12", - green: { - 50: '#f5fcf8', - 100: '#eafaf1', - 200: '#cbf2dc', - 300: '#abebc6', - 400: '#6ddb9c', - 500: '#2ecc71', - 600: '#29b866', - 700: '#239955', - 800: '#1c7a44', - 900: '#176437', - DEFAULT: "#2ecc71", - } - }, - }, - keyframes: { - type: { - "0%": { transform: "translateX(0ch)" }, - "5%, 10%": { transform: "translateX(1ch)" }, - "15%, 20%": { transform: "translateX(2ch)" }, - "25%, 30%": { transform: "translateX(3ch)" }, - "35%, 40%": { transform: "translateX(4ch)" }, - "45%, 50%": { transform: "translateX(5ch)" }, - "55%, 60%": { transform: "translateX(6ch)" }, - "65%, 70%": { transform: "translateX(7ch)" }, - "75%, 80%": { transform: "translateX(8ch)" }, - "85%, 90%": { transform: "translateX(9ch)" }, - "95%, 100%": { transform: "translateX(11ch)" }, - }, - fadeIn: { - "0%": { opacity: 0 }, - "100%": { opacity: 1 }, - }, - spin: { - "0%": { transform: "rotate(0deg)" }, - "40%": { transform: "rotate(360deg)" }, - "100%": { transform: "rotate(360deg)" }, - }, - bounce: { - "0%": { transform: "translateY(-90%)" }, - "100%": { transform: "translateY(-100%)" }, - }, - wiggle: { - "0%, 100%": { transform: "rotate(-3deg)" }, - "50%": { transform: "rotate(3deg)" }, - }, - ping: { - "75%, 100%": { - transform: "scale(2)", - opacity: 0, - }, - }, - popup: { - "0%": { - transform: "scale(0.2)", - opacity: 0, - transform: "translateY(120%)", - }, - "100%": { - transform: "scale(1)", - opacity: 1, - transform: "translateY(100%)", - }, - }, - popright: { - "0%": { - transform: "translateX(-100%)", - }, - "100%": { - transform: "translateX(0%)", - }, - }, - popleft: { - "0%": { - transform: "translateX(100%)", - }, - "100%": { - transform: "translateX(0%)", - }, - }, - popdown: { - "0%": { - transform: "scale(0.2)", - opacity: 0, - transform: "translateY(80%)", - }, - "100%": { - transform: "scale(1)", - opacity: 1, - transform: "translateY(100%)", - }, - }, - }, - animation: { - fadeIn: "fadeIn 1000ms ease-in-out", - bounce: "bounce 1000ms ease-in-out infinite", - spin: "spin 4000ms ease-in-out infinite", - cursor: "cursor .6s linear infinite alternate", - type: "type 2.7s ease-out .8s infinite alternate both", - "type-reverse": - "type 1.8s ease-out 0s infinite alternate-reverse both", - wiggle: "wiggle 200ms ease-in-out", - ping: "ping 1000ms ease-in-out infinite", - popup: "popup 300ms ease-in-out", - popdown: "popdown 300ms ease-in-out", - popright: "popright 100ms ease-in-out", - popleft: "popleft 100ms ease-in-out", - }, - fontSize: { - xxxs: ".23rem", - xxs: ".5rem", - xs: ".75rem", - sm: ".875rem", - base: "1rem", - lg: "1.125rem", - xl: "1.25rem", - "2xl": "1.5rem", - "3xl": "1.875rem", - "4xl": "2.25rem", - "5xl": "3rem", - "6xl": "4rem", - "7xl": "5rem", - "8xl": "6rem", - "9xl": "7rem", - }, - typography: (theme) => ({ - DEFAULT: { - css: [ - { - color: "var(--tw-prose-body)", - maxWidth: "65ch", - '[class~="lead"]': { - color: "var(--tw-prose-lead)", - }, - a: { - color: colors.gray[200], - textDecoration: "underline", - fontWeight: "500", - }, - strong: { - color: colors.gray[200], - fontWeight: "600", - }, - "a strong": { - color: "inherit", - }, - "blockquote strong": { - color: "inherit", - }, - "thead th strong": { - color: "inherit", - }, - ol: { - listStyleType: "decimal", - }, - 'ol[type="A"]': { - listStyleType: "upper-alpha", - }, - 'ol[type="a"]': { - listStyleType: "lower-alpha", - }, - 'ol[type="A" s]': { - listStyleType: "upper-alpha", - }, - 'ol[type="a" s]': { - listStyleType: "lower-alpha", - }, - 'ol[type="I"]': { - listStyleType: "upper-roman", - }, - 'ol[type="i"]': { - listStyleType: "lower-roman", - }, - 'ol[type="I" s]': { - listStyleType: "upper-roman", - }, - 'ol[type="i" s]': { - listStyleType: "lower-roman", - }, - 'ol[type="1"]': { - listStyleType: "decimal", - }, - ul: { - listStyleType: "disc", - }, - "ol > li::marker": { - fontWeight: "400", - color: "var(--tw-prose-counters)", - }, - "ul > li::marker": { - color: "var(--tw-prose-bullets)", - }, - hr: { - borderColor: "var(--tw-prose-hr)", - borderTopWidth: 1, - }, - blockquote: { - fontWeight: "500", - fontStyle: "italic", - color: "var(--tw-prose-quotes)", - borderLeftWidth: "0.25rem", - borderLeftColor: "var(--tw-prose-quote-borders)", - quotes: '"\\201C""\\201D""\\2018""\\2019"', - }, - "blockquote p:first-of-type::before": { - content: "open-quote", - }, - "blockquote p:last-of-type::after": { - content: "close-quote", - }, - h1: { - color: colors.gray[200], - fontWeight: "800", - }, - "h1 strong": { - fontWeight: "900", - color: "inherit", - }, - h2: { - color: colors.gray[200], - fontWeight: "700", - }, - "h2 strong": { - fontWeight: "800", - color: "inherit", - }, - h3: { - color: colors.gray[300], - fontWeight: "600", - }, - "h3 strong": { - fontWeight: "700", - color: "inherit", - }, - h4: { - color: colors.gray[400], - fontWeight: "600", - }, - "h4 strong": { - fontWeight: "700", - color: "inherit", - }, - // TODO: Figure out how to not need these, it's a merging issue - img: {}, - "figure > *": {}, - figcaption: { - color: "var(--tw-prose-captions)", - }, - code: { - color: "var(--tw-prose-code)", - fontWeight: "600", - }, - "code::before": { - content: '"`"', - }, - "code::after": { - content: '"`"', - }, - "a code": { - color: "inherit", - }, - "h1 code": { - color: "inherit", - }, - "h2 code": { - color: "inherit", - }, - "h3 code": { - color: "inherit", - }, - "h4 code": { - color: "inherit", - }, - "blockquote code": { - color: "inherit", - }, - "thead th code": { - color: "inherit", - }, - pre: { - color: "var(--tw-prose-pre-code)", - backgroundColor: colors.gray[800], - overflowX: "auto", - fontWeight: "400", - }, - "pre code": { - backgroundColor: "transparent", - borderWidth: "0", - borderRadius: "0", - padding: "0", - fontWeight: "inherit", - color: "inherit", - fontSize: "inherit", - fontFamily: "inherit", - lineHeight: "inherit", - }, - "pre code::before": { - content: "none", - }, - "pre code::after": { - content: "none", - }, - table: { - width: "100%", - tableLayout: "auto", - textAlign: "left", - marginTop: em(32, 16), - marginBottom: em(32, 16), - }, - thead: { - borderBottomWidth: "1px", - borderBottomColor: "var(--tw-prose-th-borders)", - }, - "thead th": { - color: "var(--tw-prose-headings)", - fontWeight: "600", - verticalAlign: "bottom", - }, - "tbody tr": { - borderBottomWidth: "1px", - borderBottomColor: "var(--tw-prose-td-borders)", - }, - "tbody tr:last-child": { - borderBottomWidth: "0", - }, - "tbody td": { - verticalAlign: "baseline", - }, - tfoot: { - borderTopWidth: "1px", - borderTopColor: "var(--tw-prose-th-borders)", - }, - "tfoot td": { - verticalAlign: "top", - }, - }, - defaultModifiers.gray.css, - ...defaultModifiers.base.css, - ], - }, - ...defaultModifiers, - }), - }, - plugins: [ - plugin(function ({ addUtilities }) { - addUtilities({ - ".no-scrollbar::-webkit-scrollbar": { - display: "none", - }, - ".no-scrollbar": { - "-ms-overflow-style": "none", - "scrollbar-width": "none", - }, - }); - }), - require("@tailwindcss/typography"), - ], + darkMode: "class", + content: [ + "./pages/**/*.{js,ts,jsx,tsx}", + "./components/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + // You can generate your own colors with this tool: https://javisperez.github.io/tailwindcolorshades/ + primary: { + 50: "#fcfdf7", + 100: "#f8fcee", + 200: "#eef6d5", + 300: "#e3f1bc", + 400: "#cfe78a", + 500: "#badc58", + 600: "#a7c64f", + 700: "#8ca542", + 800: "#708435", + 900: "#5b6c2b", + DEFAULT: "#badc58", + }, + grey: "#0d1117", + mineshaft: { + 50: "#f5f5f5", + 100: "#ebebeb", + 200: "#ccccce", + 300: "#adaeb0", + 400: "#707174", + 500: "#323439", + 600: "#2d2f33", + 700: "#26272b", + 800: "#1e1f22", + 900: "#19191c", + DEFAULT: "#323439", + }, + chicago: { + 50: "#f7f7f7", + 100: "#efefef", + 200: "#d6d6d7", + 300: "#bdbebf", + 400: "#8c8d8e", + 500: "#5b5c5e", + 600: "#525355", + 700: "#444547", + 800: "#373738", + 900: "#2d2d2e", + DEFAULT: "#5b5c5e", + }, + bunker: { + 50: "#f3f4f4", + 100: "#e8e8e9", + 200: "#c5c6c8", + 300: "#a2a4a6", + 400: "#5d5f64", + 500: "#171b21", + 600: "#15181e", + 700: "#111419", + 800: "#0e1014", + 900: "#0b0d10", + DEFAULT: "#171B21", + }, + githubblack: "#020409", + blue2: "#130f40", + blue1: "#3498db", + yellow: { + 50: "#fefcf3", + 100: "#fef9e7", + 200: "#fcf0c3", + 300: "#f9e79f", + 400: "#f5d657", + 500: "#f1c40f", + 600: "#d9b00e", + 700: "#b5930b", + 800: "#917609", + 900: "#766007", + DEFAULT: "#f1c40f", + }, + red: { + 50: "#fef6f5", + 100: "#fdedec", + 200: "#f9d2ce", + 300: "#f5b7b1", + 400: "#ee8277", + 500: "#e74c3c", + 600: "#d04436", + 700: "#ad392d", + 800: "#8b2e24", + 900: "#71251d", + DEFAULT: "#e74c3c", + }, + orange: "#f39c12", + green: { + 50: "#f5fcf8", + 100: "#eafaf1", + 200: "#cbf2dc", + 300: "#abebc6", + 400: "#6ddb9c", + 500: "#2ecc71", + 600: "#29b866", + 700: "#239955", + 800: "#1c7a44", + 900: "#176437", + DEFAULT: "#2ecc71", + }, + }, + }, + keyframes: { + type: { + "0%": { transform: "translateX(0ch)" }, + "5%, 10%": { transform: "translateX(1ch)" }, + "15%, 20%": { transform: "translateX(2ch)" }, + "25%, 30%": { transform: "translateX(3ch)" }, + "35%, 40%": { transform: "translateX(4ch)" }, + "45%, 50%": { transform: "translateX(5ch)" }, + "55%, 60%": { transform: "translateX(6ch)" }, + "65%, 70%": { transform: "translateX(7ch)" }, + "75%, 80%": { transform: "translateX(8ch)" }, + "85%, 90%": { transform: "translateX(9ch)" }, + "95%, 100%": { transform: "translateX(11ch)" }, + }, + fadeIn: { + "0%": { opacity: 0 }, + "100%": { opacity: 1 }, + }, + spin: { + "0%": { transform: "rotate(0deg)" }, + "40%": { transform: "rotate(360deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + bounce: { + "0%": { transform: "translateY(-90%)" }, + "100%": { transform: "translateY(-100%)" }, + }, + wiggle: { + "0%, 100%": { transform: "rotate(-3deg)" }, + "50%": { transform: "rotate(3deg)" }, + }, + ping: { + "75%, 100%": { + transform: "scale(2)", + opacity: 0, + }, + }, + popup: { + "0%": { + transform: "scale(0.2)", + opacity: 0, + transform: "translateY(120%)", + }, + "100%": { + transform: "scale(1)", + opacity: 1, + transform: "translateY(100%)", + }, + }, + popright: { + "0%": { + transform: "translateX(-100%)", + }, + "100%": { + transform: "translateX(0%)", + }, + }, + popleft: { + "0%": { + transform: "translateX(100%)", + }, + "100%": { + transform: "translateX(0%)", + }, + }, + popdown: { + "0%": { + transform: "scale(0.2)", + opacity: 0, + transform: "translateY(80%)", + }, + "100%": { + transform: "scale(1)", + opacity: 1, + transform: "translateY(100%)", + }, + }, + }, + animation: { + fadeIn: "fadeIn 1000ms ease-in-out", + bounce: "bounce 1000ms ease-in-out infinite", + spin: "spin 4000ms ease-in-out infinite", + cursor: "cursor .6s linear infinite alternate", + type: "type 2.7s ease-out .8s infinite alternate both", + "type-reverse": "type 1.8s ease-out 0s infinite alternate-reverse both", + wiggle: "wiggle 200ms ease-in-out", + ping: "ping 1000ms ease-in-out infinite", + popup: "popup 300ms ease-in-out", + popdown: "popdown 300ms ease-in-out", + popright: "popright 100ms ease-in-out", + popleft: "popleft 100ms ease-in-out", + }, + fontSize: { + xxxs: ".23rem", + xxs: ".5rem", + xs: ".75rem", + sm: ".875rem", + base: "1rem", + lg: "1.125rem", + xl: "1.25rem", + "2xl": "1.5rem", + "3xl": "1.875rem", + "4xl": "2.25rem", + "5xl": "3rem", + "6xl": "4rem", + "7xl": "5rem", + "8xl": "6rem", + "9xl": "7rem", + }, + typography: (theme) => ({ + DEFAULT: { + css: [ + { + color: "var(--tw-prose-body)", + maxWidth: "65ch", + '[class~="lead"]': { + color: "var(--tw-prose-lead)", + }, + a: { + color: colors.gray[200], + textDecoration: "underline", + fontWeight: "500", + }, + strong: { + color: colors.gray[200], + fontWeight: "600", + }, + "a strong": { + color: "inherit", + }, + "blockquote strong": { + color: "inherit", + }, + "thead th strong": { + color: "inherit", + }, + ol: { + listStyleType: "decimal", + }, + 'ol[type="A"]': { + listStyleType: "upper-alpha", + }, + 'ol[type="a"]': { + listStyleType: "lower-alpha", + }, + 'ol[type="A" s]': { + listStyleType: "upper-alpha", + }, + 'ol[type="a" s]': { + listStyleType: "lower-alpha", + }, + 'ol[type="I"]': { + listStyleType: "upper-roman", + }, + 'ol[type="i"]': { + listStyleType: "lower-roman", + }, + 'ol[type="I" s]': { + listStyleType: "upper-roman", + }, + 'ol[type="i" s]': { + listStyleType: "lower-roman", + }, + 'ol[type="1"]': { + listStyleType: "decimal", + }, + ul: { + listStyleType: "disc", + }, + "ol > li::marker": { + fontWeight: "400", + color: "var(--tw-prose-counters)", + }, + "ul > li::marker": { + color: "var(--tw-prose-bullets)", + }, + hr: { + borderColor: "var(--tw-prose-hr)", + borderTopWidth: 1, + }, + blockquote: { + fontWeight: "500", + fontStyle: "italic", + color: "var(--tw-prose-quotes)", + borderLeftWidth: "0.25rem", + borderLeftColor: "var(--tw-prose-quote-borders)", + quotes: '"\\201C""\\201D""\\2018""\\2019"', + }, + "blockquote p:first-of-type::before": { + content: "open-quote", + }, + "blockquote p:last-of-type::after": { + content: "close-quote", + }, + h1: { + color: colors.gray[200], + fontWeight: "800", + }, + "h1 strong": { + fontWeight: "900", + color: "inherit", + }, + h2: { + color: colors.gray[200], + fontWeight: "700", + }, + "h2 strong": { + fontWeight: "800", + color: "inherit", + }, + h3: { + color: colors.gray[300], + fontWeight: "600", + }, + "h3 strong": { + fontWeight: "700", + color: "inherit", + }, + h4: { + color: colors.gray[400], + fontWeight: "600", + }, + "h4 strong": { + fontWeight: "700", + color: "inherit", + }, + // TODO: Figure out how to not need these, it's a merging issue + img: {}, + "figure > *": {}, + figcaption: { + color: "var(--tw-prose-captions)", + }, + code: { + color: "var(--tw-prose-code)", + fontWeight: "600", + }, + "code::before": { + content: '"`"', + }, + "code::after": { + content: '"`"', + }, + "a code": { + color: "inherit", + }, + "h1 code": { + color: "inherit", + }, + "h2 code": { + color: "inherit", + }, + "h3 code": { + color: "inherit", + }, + "h4 code": { + color: "inherit", + }, + "blockquote code": { + color: "inherit", + }, + "thead th code": { + color: "inherit", + }, + pre: { + color: "var(--tw-prose-pre-code)", + backgroundColor: colors.gray[800], + overflowX: "auto", + fontWeight: "400", + }, + "pre code": { + backgroundColor: "transparent", + borderWidth: "0", + borderRadius: "0", + padding: "0", + fontWeight: "inherit", + color: "inherit", + fontSize: "inherit", + fontFamily: "inherit", + lineHeight: "inherit", + }, + "pre code::before": { + content: "none", + }, + "pre code::after": { + content: "none", + }, + table: { + width: "100%", + tableLayout: "auto", + textAlign: "left", + marginTop: em(32, 16), + marginBottom: em(32, 16), + }, + thead: { + borderBottomWidth: "1px", + borderBottomColor: "var(--tw-prose-th-borders)", + }, + "thead th": { + color: "var(--tw-prose-headings)", + fontWeight: "600", + verticalAlign: "bottom", + }, + "tbody tr": { + borderBottomWidth: "1px", + borderBottomColor: "var(--tw-prose-td-borders)", + }, + "tbody tr:last-child": { + borderBottomWidth: "0", + }, + "tbody td": { + verticalAlign: "baseline", + }, + tfoot: { + borderTopWidth: "1px", + borderTopColor: "var(--tw-prose-th-borders)", + }, + "tfoot td": { + verticalAlign: "top", + }, + }, + defaultModifiers.gray.css, + ...defaultModifiers.base.css, + ], + }, + ...defaultModifiers, + }), + }, + plugins: [ + plugin(function ({ addUtilities }) { + addUtilities({ + ".no-scrollbar::-webkit-scrollbar": { + display: "none", + }, + ".no-scrollbar": { + "-ms-overflow-style": "none", + "scrollbar-width": "none", + }, + }); + }), + require("@tailwindcss/typography"), + ], };