diff --git a/frontend/src/components/basic/table/ProjectUsersTable.tsx b/frontend/src/components/basic/table/ProjectUsersTable.tsx index 53a0e86f7..5838aaf41 100644 --- a/frontend/src/components/basic/table/ProjectUsersTable.tsx +++ b/frontend/src/components/basic/table/ProjectUsersTable.tsx @@ -8,10 +8,9 @@ import { useSubscription, useWorkspace } from "@app/context"; import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission"; import { useDeleteUserFromWorkspace, - useUpdateUserWorkspaceRole -} from "@app/hooks/api"; -import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey"; -import uploadKeys from "@app/pages/api/workspace/uploadKeys"; + useGetUserWsKey, + useUpdateUserWorkspaceRole, + useUploadWsKey} from "@app/hooks/api"; import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto"; import guidGenerator from "../../utilities/randomId"; @@ -42,7 +41,10 @@ type EnvironmentProps = { const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => { const { currentWorkspace } = useWorkspace(); const { subscription } = useSubscription(); + const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? ""); + const { mutateAsync: deleteUserFromWorkspaceMutateAsync } = useDeleteUserFromWorkspace(); + const { mutateAsync: uploadWsKeyMutateAsync } = useUploadWsKey(); const { mutateAsync: updateUserWorkspaceRoleMutateAsync } = useUpdateUserWorkspaceRole(); // const [roleSelected, setRoleSelected] = useState( // Array(userData?.length).fill(userData.map((user) => user.role)) @@ -151,26 +153,31 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa }, [userData, myUser, currentWorkspace]); const grantAccess = async (id: string, publicKey: string) => { - const result = await getLatestFileKey({ workspaceId }); + if (wsKey) { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + // assymmetrically decrypt symmetric key with local private key + const key = decryptAssymmetric({ + ciphertext: wsKey.encryptedKey, + nonce: wsKey.nonce, + publicKey: wsKey.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, + privateKey: PRIVATE_KEY + }); - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: key, - publicKey, - privateKey: PRIVATE_KEY - }); - - uploadKeys(workspaceId, id, ciphertext, nonce); - router.reload(); + await uploadWsKeyMutateAsync({ + workspaceId, + userId: id, + encryptedKey: ciphertext, + nonce + }); + router.reload(); + } }; const closeUpgradeModal = () => { diff --git a/frontend/src/components/signup/TeamInviteStep.tsx b/frontend/src/components/signup/TeamInviteStep.tsx index 398ccd78d..d1cc6ef7d 100644 --- a/frontend/src/components/signup/TeamInviteStep.tsx +++ b/frontend/src/components/signup/TeamInviteStep.tsx @@ -2,9 +2,9 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/router"; +import { useAddUserToOrg } from "@app/hooks/api"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { usePopUp } from "@app/hooks/usePopUp"; -import addUserToOrg from "@app/pages/api/organization/addUserToOrg"; import { Button, EmailServiceSetupModal } from "../v2"; @@ -12,10 +12,12 @@ import { Button, EmailServiceSetupModal } from "../v2"; * This is the last step of the signup flow. People can optionally invite their teammates here. */ export default function TeamInviteStep(): JSX.Element { - const [emails, setEmails] = useState(""); const { t } = useTranslation(); const router = useRouter(); + const [emails, setEmails] = useState(""); const { data: serverDetails } = useFetchServerStatus(); + + const { mutateAsync } = useAddUserToOrg(); const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const); // Redirect user to the getting started page @@ -27,7 +29,12 @@ export default function TeamInviteStep(): JSX.Element { inviteEmails .split(",") .map((email) => email.trim()) - .map(async (email) => addUserToOrg(email, String(localStorage.getItem("orgData.id")))); + .map(async (email) => { + mutateAsync({ + inviteeEmail: email, + organizationId: String(localStorage.getItem("orgData.id")) + }); + }); await redirectToHome(); }; diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index e0d99548a..b0a0d420f 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -9,8 +9,8 @@ import nacl from "tweetnacl"; import { encodeBase64 } from "tweetnacl-util"; import { useGetCommonPasswords } from "@app/hooks/api"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; import ProjectService from "@app/services/ProjectService"; import InputField from "../basic/InputField"; @@ -190,7 +190,8 @@ export default function UserInfoStep({ privateKey }); - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); + const orgId = userOrgs[0]?._id; const project = await ProjectService.initProject({ organizationId: orgId, diff --git a/frontend/src/components/utilities/attemptCliLogin.ts b/frontend/src/components/utilities/attemptCliLogin.ts index 099351baa..3b9ebc910 100644 --- a/frontend/src/components/utilities/attemptCliLogin.ts +++ b/frontend/src/components/utilities/attemptCliLogin.ts @@ -1,10 +1,10 @@ /* eslint-disable prefer-destructuring */ import jsrp from "jsrp"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; +import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries"; import login1 from "@app/pages/api/auth/Login1"; import login2 from "@app/pages/api/auth/Login2"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; -import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects"; import KeyService from "@app/services/KeyService"; import Telemetry from "./telemetry/Telemetry"; @@ -125,13 +125,11 @@ const attemptLogin = async ( privateKey }); - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]._id; localStorage.setItem("orgData.id", orgId); - const orgUserProjects = await getOrganizationUserProjects({ - orgId - }); + const orgUserProjects = await fetchMyOrganizationProjects(orgId); if (orgUserProjects.length > 0) { localStorage.setItem("projectData.id", orgUserProjects[0]._id); diff --git a/frontend/src/components/utilities/attemptCliLoginMfa.ts b/frontend/src/components/utilities/attemptCliLoginMfa.ts index cb33d3bad..2fc6f17b9 100644 --- a/frontend/src/components/utilities/attemptCliLoginMfa.ts +++ b/frontend/src/components/utilities/attemptCliLoginMfa.ts @@ -1,10 +1,10 @@ /* eslint-disable prefer-destructuring */ import jsrp from "jsrp"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; +import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries"; import login1 from "@app/pages/api/auth/Login1"; import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; -import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects"; import KeyService from "@app/services/KeyService"; import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; @@ -96,13 +96,11 @@ const attemptLoginMfa = async ({ // TODO: in the future - move this logic elsewhere // because this function is about logging the user in // and not initializing the login details - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]._id; localStorage.setItem("orgData.id", orgId); - const orgUserProjects = await getOrganizationUserProjects({ - orgId - }); + const orgUserProjects = await fetchMyOrganizationProjects(orgId); localStorage.setItem("projectData.id", orgUserProjects[0]._id); resolve({ diff --git a/frontend/src/components/utilities/attemptLogin.ts b/frontend/src/components/utilities/attemptLogin.ts index b3e4b38be..29c730e3f 100644 --- a/frontend/src/components/utilities/attemptLogin.ts +++ b/frontend/src/components/utilities/attemptLogin.ts @@ -1,10 +1,10 @@ /* eslint-disable prefer-destructuring */ import jsrp from "jsrp"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; +import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries"; import login1 from "@app/pages/api/auth/Login1"; import login2 from "@app/pages/api/auth/Login2"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; -import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects"; import KeyService from "@app/services/KeyService"; import Telemetry from "./telemetry/Telemetry"; @@ -36,7 +36,6 @@ const attemptLogin = async ( providerAuthToken?: string; } ): Promise => { - const telemetry = new Telemetry().getInstance(); return new Promise((resolve, reject) => { client.init( @@ -124,14 +123,12 @@ const attemptLogin = async ( // TODO: in the future - move this logic elsewhere // because this function is about logging the user in // and not initializing the login details - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]._id; localStorage.setItem("orgData.id", orgId); - const orgUserProjects = await getOrganizationUserProjects({ - orgId - }); + const orgUserProjects = await fetchMyOrganizationProjects(orgId); if (orgUserProjects.length > 0) { localStorage.setItem("projectData.id", orgUserProjects[0]._id); diff --git a/frontend/src/components/utilities/attemptLoginMfa.ts b/frontend/src/components/utilities/attemptLoginMfa.ts index 967881357..feb58b596 100644 --- a/frontend/src/components/utilities/attemptLoginMfa.ts +++ b/frontend/src/components/utilities/attemptLoginMfa.ts @@ -1,10 +1,10 @@ /* eslint-disable prefer-destructuring */ import jsrp from "jsrp"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; +import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries"; import login1 from "@app/pages/api/auth/Login1"; import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; -import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects"; import KeyService from "@app/services/KeyService"; import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; @@ -87,13 +87,11 @@ const attemptLoginMfa = async ({ // TODO: in the future - move this logic elsewhere // because this function is about logging the user in // and not initializing the login details - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]._id; localStorage.setItem("orgData.id", orgId); - const orgUserProjects = await getOrganizationUserProjects({ - orgId - }); + const orgUserProjects = await fetchMyOrganizationProjects(orgId); localStorage.setItem("projectData.id", orgUserProjects[0]._id); resolve(true); diff --git a/frontend/src/components/utilities/checks/OnboardingCheck.ts b/frontend/src/components/utilities/checks/OnboardingCheck.ts index f9b47a210..01d7a2d55 100644 --- a/frontend/src/components/utilities/checks/OnboardingCheck.ts +++ b/frontend/src/components/utilities/checks/OnboardingCheck.ts @@ -1,5 +1,4 @@ -import { fetchUserAction } from "@app/hooks/api/users/queries"; -import getOrganizationUsers from "@app/pages/api/organization/GetOrgUsers"; +import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries"; interface OnboardingCheckProps { setTotalOnboardingActionsDone?: (value: number) => void; @@ -43,9 +42,8 @@ const onboardingCheck = async ({ if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro); const orgId = localStorage.getItem("orgData.id"); - const orgUsers = await getOrganizationUsers({ - orgId: orgId || "" - }); + const orgUsers = await fetchOrgUsers(orgId || ""); + if (orgUsers.length > 1) { countActions += 1; } diff --git a/frontend/src/components/utilities/secrets/encryptSecrets.ts b/frontend/src/components/utilities/secrets/encryptSecrets.ts index 49fcf9736..610e2a6f1 100644 --- a/frontend/src/components/utilities/secrets/encryptSecrets.ts +++ b/frontend/src/components/utilities/secrets/encryptSecrets.ts @@ -2,7 +2,7 @@ import crypto from "crypto"; import { SecretDataProps, Tag } from "public/data/frequentInterfaces"; -import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey"; +import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto"; @@ -42,19 +42,21 @@ const encryptSecrets = async ({ }) => { let secrets; try { - const sharedKey = await getLatestFileKey({ workspaceId }); + // const sharedKey = await getLatestFileKey({ workspaceId }); + const wsKey = await fetchUserWsKey(workspaceId); const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; let randomBytes: string; - if (Object.keys(sharedKey).length > 0) { + if (wsKey) { // case: a (shared) key exists for the workspace randomBytes = decryptAssymmetric({ - ciphertext: sharedKey.latestKey.encryptedKey, - nonce: sharedKey.latestKey.nonce, - publicKey: sharedKey.latestKey.sender.publicKey, + ciphertext: wsKey.encryptedKey, + nonce: wsKey.nonce, + publicKey: wsKey.sender.publicKey, privateKey: PRIVATE_KEY }); + } else { // case: a (shared) key does not exist for the workspace randomBytes = crypto.randomBytes(16).toString("hex"); @@ -114,6 +116,7 @@ const encryptSecrets = async ({ return result; }); + } catch (error) { console.log("Error while encrypting secrets"); } diff --git a/frontend/src/ee/components/ActivitySideBar.tsx b/frontend/src/ee/components/ActivitySideBar.tsx index 67d673c3d..d1e068177 100644 --- a/frontend/src/ee/components/ActivitySideBar.tsx +++ b/frontend/src/ee/components/ActivitySideBar.tsx @@ -8,7 +8,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import getActionData from "@app/ee/api/secrets/GetActionData"; import patienceDiff from "@app/ee/utilities/findTextDifferences"; -import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey"; +import { + useGetUserWsKey +} from "@app/hooks/api"; import { decryptAssymmetric, @@ -59,25 +61,24 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => { const [actionData, setActionData] = useState(); const [actionMetaData, setActionMetaData] = useState(); const [isLoading, setIsLoading] = useState(false); + const { data: wsKey } = useGetUserWsKey(String(router.query.id)); useEffect(() => { const getLogData = async () => { setIsLoading(true); const tempActionData = await getActionData({ actionId: currentAction }); - const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) }); const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); // #TODO: make this a separate function and reuse across the app let decryptedLatestKey: string; - if (latestKey) { + if (wsKey) { // assymmetrically decrypt symmetric key with local private key decryptedLatestKey = decryptAssymmetric({ - ciphertext: latestKey.latestKey.encryptedKey, - nonce: latestKey.latestKey.nonce, - publicKey: latestKey.latestKey.sender.publicKey, + ciphertext: wsKey.encryptedKey, + nonce: wsKey.nonce, + publicKey: wsKey.sender.publicKey, privateKey: String(PRIVATE_KEY) }); - } const decryptedSecretVersions = tempActionData.payload.secretVersions.map( (encryptedSecretVersion: { @@ -122,9 +123,10 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => { setActionData(decryptedSecretVersions); setActionMetaData({ name: tempActionData.name }); setIsLoading(false); + } }; getLogData(); - }, [currentAction]); + }, [currentAction, wsKey]); return (
void; - setSnapshotData: (value: any) => void; - chosenSnapshot: string; -} - -interface SnaphotProps { - _id: string; - createdAt: string; - secretVersions: string[]; -} - -interface EncrypetedSecretVersionListProps { - _id: string; - createdAt: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - environment: string; - type: "personal" | "shared"; - tags: Tag[]; -} - -/** - * @param {object} obj - * @param {function} obj.toggleSidebar - function that opens or closes the sidebar - * @param {function} obj.setSnapshotData - state manager for snapshot data - * @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected - * @returns the sidebar with the options for point-in-time recovery (commits) - */ -const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: SideBarProps) => { - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - const [secretSnapshotsMetadata, setSecretSnapshotsMetadata] = useState([]); - const [currentOffset, setCurrentOffset] = useState(0); - const currentLimit = 15; - - const loadMoreSnapshots = () => { - setCurrentOffset(currentOffset + currentLimit); - }; - - useEffect(() => { - const getLogData = async () => { - setIsLoading(true); - const results = await getProjectSecretShanpshots({ - workspaceId: String(router.query.id), - limit: currentLimit, - offset: currentOffset - }); - setSecretSnapshotsMetadata(secretSnapshotsMetadata.concat(results)); - setIsLoading(false); - }; - getLogData(); - }, [currentOffset]); - - const exploreSnapshot = async ({ snapshotId }: { snapshotId: string }) => { - const secretSnapshotData = await getSecretSnapshotData({ secretSnapshotId: snapshotId }); - - const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) }); - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - - let decryptedLatestKey: string; - if (latestKey) { - // assymmetrically decrypt symmetric key with local private key - decryptedLatestKey = decryptAssymmetric({ - ciphertext: latestKey.latestKey.encryptedKey, - nonce: latestKey.latestKey.nonce, - publicKey: latestKey.latestKey.sender.publicKey, - privateKey: String(PRIVATE_KEY) - }); - } - - const decryptedSecretVersions = secretSnapshotData.secretVersions - .filter( - (sv: EncrypetedSecretVersionListProps) => - sv.type !== undefined && sv.environment !== undefined - ) - .map((encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => ({ - id: encryptedSecretVersion._id, - pos, - type: encryptedSecretVersion.type, - environment: encryptedSecretVersion.environment, - tags: encryptedSecretVersion.tags, - key: decryptSymmetric({ - ciphertext: encryptedSecretVersion.secretKeyCiphertext, - iv: encryptedSecretVersion.secretKeyIV, - tag: encryptedSecretVersion.secretKeyTag, - key: decryptedLatestKey - }), - value: decryptSymmetric({ - ciphertext: encryptedSecretVersion.secretValueCiphertext, - iv: encryptedSecretVersion.secretValueIV, - tag: encryptedSecretVersion.secretValueTag, - key: decryptedLatestKey - }) - })); - - const secretKeys = [ - ...new Set( - decryptedSecretVersions - .filter((dsv: any) => dsv.type !== undefined || dsv.environemnt !== undefined) - .map((secret: SecretDataProps) => secret.key) - ) - ]; - - const result = secretKeys.map((key, index) => - decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "shared" - )[0]?.id - ? { - id: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "shared" - )[0].id, - pos: index, - key, - environment: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "shared" - )[0].environment, - tags: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "shared" - )[0].tags, - value: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "shared" - )[0]?.value, - valueOverride: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "personal" - )[0]?.value - } - : { - id: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "personal" - )[0].id, - pos: index, - key, - environment: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "personal" - )[0].environment, - tags: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "personal" - )[0].tags, - value: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "shared" - )[0]?.value, - valueOverride: decryptedSecretVersions.filter( - (secret: SecretDataProps) => secret.key === key && secret.type === "personal" - )[0]?.value - } - ); - - setSnapshotData({ - id: secretSnapshotData._id, - version: secretSnapshotData.version, - createdAt: secretSnapshotData.createdAt, - secretVersions: result, - comment: "" - }); - }; - - return ( -
- {isLoading ? ( -
- infisical loading indicator -
- ) : ( -
-
-

Point In Recovery

-
null} - role="button" - tabIndex={0} - className="p-1" - onClick={() => toggleSidebar(false)} - > - -
-
-
- - Note: This will recover secrets for all enviroments in this project. - - {secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) => ( -
null} - role="button" - tabIndex={0} - key={snapshot._id} - onClick={() => exploreSnapshot({ snapshotId: snapshot._id })} - className={`${ - chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "") - ? "pointer-events-none bg-primary text-black" - : "cursor-pointer bg-mineshaft-700 duration-200 hover:bg-mineshaft-500" - } mb-2 flex flex-row items-center justify-between rounded-md py-3 px-4`} - > -
-
- {timeSince(new Date(snapshot.createdAt))} -
-
{` - ${snapshot.secretVersions.length} Secrets`}
-
-
- {id === 0 - ? "Current Version" - : chosenSnapshot === snapshot._id - ? "Currently Viewing" - : "Explore"} -
-
- ))} -
-
-
-
-
-
- )} -
- ); -}; - -export default PITRecoverySidebar; diff --git a/frontend/src/ee/components/SecretVersionList.tsx b/frontend/src/ee/components/SecretVersionList.tsx deleted file mode 100644 index 41a085082..000000000 --- a/frontend/src/ee/components/SecretVersionList.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import Image from "next/image"; -import { useRouter } from "next/router"; -import { faCircle, faDotCircle } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import { - decryptAssymmetric, - decryptSymmetric -} from "@app/components/utilities/cryptography/crypto"; -import getSecretVersions from "@app/ee/api/secrets/GetSecretVersions"; -import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey"; - -interface DecryptedSecretVersionListProps { - createdAt: string; - value: string; -} - -interface EncrypetedSecretVersionListProps { - createdAt: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; -} - -/** - * @param {string} secretId - the id of a secret for which are querying version history - * @returns a list of versions for a specific secret - */ -const SecretVersionList = ({ secretId }: { secretId: string }) => { - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); - const [secretVersions, setSecretVersions] = useState([]); - - useEffect(() => { - const getSecretVersionHistory = async () => { - setIsLoading(true); - try { - const encryptedSecretVersions = await getSecretVersions({ secretId, offset: 0, limit: 10 }); - const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) }); - - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - - let decryptedLatestKey: string; - if (latestKey) { - // assymmetrically decrypt symmetric key with local private key - decryptedLatestKey = decryptAssymmetric({ - ciphertext: latestKey.latestKey.encryptedKey, - nonce: latestKey.latestKey.nonce, - publicKey: latestKey.latestKey.sender.publicKey, - privateKey: String(PRIVATE_KEY) - }); - } - - const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map( - (encryptedSecretVersion: EncrypetedSecretVersionListProps) => ({ - createdAt: encryptedSecretVersion.createdAt, - value: decryptSymmetric({ - ciphertext: encryptedSecretVersion.secretValueCiphertext, - iv: encryptedSecretVersion.secretValueIV, - tag: encryptedSecretVersion.secretValueTag, - key: decryptedLatestKey - }) - }) - ); - - setSecretVersions(decryptedSecretVersions); - setIsLoading(false); - } catch (error) { - console.log(error); - } - }; - getSecretVersionHistory(); - }, [secretId]); - - return ( -
-

{t("dashboard.sidebar.version-history")}

-
- {isLoading ? ( -
- infisical loading indicator -
- ) : ( -
- {secretVersions ? ( - secretVersions - ?.sort((a, b) => b.createdAt.localeCompare(a.createdAt)) - .map((version: DecryptedSecretVersionListProps, index: number) => ( -
-
-
- -
-
-
-
-
- {new Date(version.createdAt).toLocaleDateString("en-US", { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit" - })} -
-
-

- - Value: - - {version.value} -

-
-
-
- )) - ) : ( -
- No version history yet. -
- )} -
- )} -
-
- ); -}; - -export default SecretVersionList; diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index c24d3b9cf..f0cb8ec4c 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -2,10 +2,10 @@ import crypto from "crypto"; import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto"; import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets"; +import { uploadWsKey } from "@app/hooks/api/keys/queries"; import { createSecret } from "@app/hooks/api/secrets/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { createWorkspace } from "@app/hooks/api/workspace/queries"; -import uploadKeys from "@app/pages/api/workspace/uploadKeys"; const secretsToBeAdded = [ { @@ -111,7 +111,12 @@ const initProjectHelper = async ({ privateKey: PRIVATE_KEY }); - await uploadKeys(workspace._id, user._id, ciphertext, nonce); + await uploadWsKey({ + workspaceId: workspace._id, + userId: user._id, + encryptedKey: ciphertext, + nonce + }); // encrypt and upload secrets to new project const secrets = await encryptSecrets({ diff --git a/frontend/src/hooks/api/keys/queries.tsx b/frontend/src/hooks/api/keys/queries.tsx index c44047351..143266d40 100644 --- a/frontend/src/hooks/api/keys/queries.tsx +++ b/frontend/src/hooks/api/keys/queries.tsx @@ -8,7 +8,7 @@ const encKeyKeys = { getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const }; -const fetchUserWsKey = async (workspaceID: string) => { +export const fetchUserWsKey = async (workspaceID: string) => { const { data } = await apiRequest.get<{ latestKey: UserWsKeyPair }>( `/api/v1/key/${workspaceID}/latest` ); @@ -24,8 +24,23 @@ export const useGetUserWsKey = (workspaceID: string) => }); // mutations +export const uploadWsKey = async ({ + workspaceId, + userId, + encryptedKey, + nonce +}: UploadWsKeyDTO) => { + return apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } }) +} + export const useUploadWsKey = () => useMutation<{}, {}, UploadWsKeyDTO>({ - mutationFn: ({ encryptedKey, nonce, userId, workspaceId }) => - apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } }) + mutationFn: async ({ encryptedKey, nonce, userId, workspaceId }) => { + return uploadWsKey({ + workspaceId, + userId, + encryptedKey, + nonce + }); + } }); diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 13810febd..17f15307b 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -27,12 +27,16 @@ const organizationKeys = { getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const }; +export const fetchOrganizations = async () => { + const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization"); + return organizations; +} + export const useGetOrganizations = () => { return useQuery({ queryKey: organizationKeys.getUserOrganizations, queryFn: async () => { - const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization"); - return organizations; + return fetchOrganizations(); } }); } @@ -42,7 +46,6 @@ export const useRenameOrg = () => { return useMutation<{}, {}, RenameOrgDTO>({ mutationFn: ({ newOrgName, orgId }) => { - console.log("useRenameOrg"); return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/users/index.tsx b/frontend/src/hooks/api/users/index.tsx index cb421c633..2eb96d45d 100644 --- a/frontend/src/hooks/api/users/index.tsx +++ b/frontend/src/hooks/api/users/index.tsx @@ -7,6 +7,7 @@ export { useDeleteOrgMembership, useGetMyAPIKeys, useGetMyIp, + useGetMyOrganizationProjects, useGetMySessions, useGetOrgUsers, useGetUser, diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 48dfdf564..a2d9318e5 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -29,6 +29,7 @@ const userKeys = { myIp: ["ip"] as const, myAPIKeys: ["api-keys"] as const, mySessions: ["sessions"] as const, + myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const }; export const fetchUserDetails = async () => { @@ -147,7 +148,9 @@ export const useAddUserToOrg = () => { } return useMutation({ - mutationFn: (dto) => apiRequest.post("/api/v1/invite-org/signup", dto), + mutationFn: (dto) => { + return apiRequest.post("/api/v1/invite-org/signup", dto); + }, onSuccess: (_, { organizationId }) => { queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId)); } @@ -329,4 +332,22 @@ export const useUpdateMfaEnabled = () => { queryClient.invalidateQueries(userKeys.getUser); } }); +} + +export const fetchMyOrganizationProjects = async (orgId: string) => { + const { data: { workspaces } } = await apiRequest.get( + `/api/v1/organization/${orgId}/my-workspaces` + ); + + return workspaces; +} + +export const useGetMyOrganizationProjects = (orgId: string) => { + return useQuery({ + queryKey: userKeys.myOrganizationProjects(orgId), + queryFn: async () => { + return fetchMyOrganizationProjects(orgId); + }, + enabled: true + }); } \ No newline at end of file diff --git a/frontend/src/pages/api/organization/GetOrgUserProjects.ts b/frontend/src/pages/api/organization/GetOrgUserProjects.ts deleted file mode 100644 index d8c87d6c7..000000000 --- a/frontend/src/pages/api/organization/GetOrgUserProjects.ts +++ /dev/null @@ -1,23 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -/** - * This route lets us get all the projects of a certain user in an org. - * @param {*} req - * @param {*} res - * @returns - */ -const getOrganizationUserProjects = (req: { orgId: string }) => - SecurityClient.fetchCall(`/api/v1/organization/${req.orgId}/my-workspaces`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }).then(async (res) => { - if (res && res.status === 200) { - return (await res.json()).workspaces; - } - console.log("Failed to get projects of a user in an org"); - return undefined; - }); - -export default getOrganizationUserProjects; diff --git a/frontend/src/pages/api/organization/GetOrgUsers.ts b/frontend/src/pages/api/organization/GetOrgUsers.ts deleted file mode 100644 index 9af757e68..000000000 --- a/frontend/src/pages/api/organization/GetOrgUsers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -export interface IMembershipOrg { - _id: string; - user: { - email: string; - firstName: string; - lastName: string; - _id: string; - publicKey: string; - }; - inviteEmail: string; - organization: string; - role: "owner" | "admin" | "member"; - status: "invited" | "accepted"; - deniedPermissions: any[]; -} -/** - * This route lets us get all the users in an org. - * @param {object} obj - * @param {string} obj.orgId - organization Id - * @returns - */ -const getOrganizationUsers = ({ orgId }: { orgId: string }): Promise => - SecurityClient.fetchCall(`/api/v1/organization/${orgId}/users`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }).then(async (res) => { - if (res?.status === 200) { - return (await res.json()).users; - } - console.log("Failed to get org users"); - return undefined; - }); - -export default getOrganizationUsers; diff --git a/frontend/src/pages/api/organization/addUserToOrg.ts b/frontend/src/pages/api/organization/addUserToOrg.ts deleted file mode 100644 index 7dbb65da5..000000000 --- a/frontend/src/pages/api/organization/addUserToOrg.ts +++ /dev/null @@ -1,27 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -/** - * This function sends an email invite to a user to join an org - * @param {*} email - * @param {*} orgId - * @returns - */ -const addUserToOrg = (email: string, orgId: string) => - SecurityClient.fetchCall("/api/v1/invite-org/signup", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - inviteeEmail: email, - organizationId: orgId - }) - }).then(async (res) => { - if (res && res.status === 200) { - return res; - } - console.log("Failed to add a user to an org"); - return undefined; - }); - -export default addUserToOrg; diff --git a/frontend/src/pages/api/organization/getOrgs.ts b/frontend/src/pages/api/organization/getOrgs.ts deleted file mode 100644 index 09cd0f022..000000000 --- a/frontend/src/pages/api/organization/getOrgs.ts +++ /dev/null @@ -1,23 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -/** - * This route lets us get the all the orgs of a certain user. - * @returns - */ -const getOrganizations = () => { - return SecurityClient.fetchCall("/api/v1/organization", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }).then(async (res) => { - if (res?.status === 200) { - const {organizations} = await res.json(); - return organizations; - } - console.log("Failed to get orgs of a user"); - return undefined; - }); -} - -export default getOrganizations; diff --git a/frontend/src/pages/api/workspace/getLatestFileKey.ts b/frontend/src/pages/api/workspace/getLatestFileKey.ts deleted file mode 100644 index 4dcd330d2..000000000 --- a/frontend/src/pages/api/workspace/getLatestFileKey.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { apiRequest } from "@app/config/request"; - -/** - * Get the latest key pairs from a certain workspace - * @param {string} workspaceId - * @returns - */ -const getLatestFileKey = async ({ workspaceId }: { workspaceId: string }) => { - const { data } = await apiRequest.get(`/api/v1/key/${workspaceId}/latest`); - return data; -} - -export default getLatestFileKey; diff --git a/frontend/src/pages/api/workspace/uploadKeys.ts b/frontend/src/pages/api/workspace/uploadKeys.ts deleted file mode 100644 index c28fa1fb3..000000000 --- a/frontend/src/pages/api/workspace/uploadKeys.ts +++ /dev/null @@ -1,32 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -/** - * This route uplods the keys in an encrypted format. - * @param {*} workspaceId - * @param {*} userId - * @param {*} encryptedKey - * @param {*} nonce - * @returns - */ -const uploadKeys = (workspaceId: string, userId: string, encryptedKey: string, nonce: string) => - SecurityClient.fetchCall(`/api/v1/key/${workspaceId}`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - key: { - userId, - encryptedKey, - nonce - } - }) - }).then(async (res) => { - if (res && res.status === 200) { - return res; - } - console.log("Failed to upload keys for a new user"); - return undefined; - }); - -export default uploadKeys; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index fd8a78a31..3f24ab101 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -1,10 +1,11 @@ import { useEffect } from "react"; import { useRouter } from "next/router"; -import getOrganizations from "./api/organization/getOrgs"; +import { useGetOrganizations } from "@app/hooks/api"; export default function DashboardRedirect() { const router = useRouter(); + const { data: userOrgs } = useGetOrganizations(); /** * Here we forward to the default workspace if a user opens this url @@ -16,11 +17,10 @@ export default function DashboardRedirect() { try { if (localStorage.getItem("orgData.id")) { router.push(`/org/${localStorage.getItem("orgData.id")}/overview`); - } else { - const userOrgs = await getOrganizations(); - userOrg = userOrgs[0]._id; - router.push(`/org/${userOrg}/overview`); - } + } else if (userOrgs) { + userOrg = userOrgs[0]._id; + router.push(`/org/${userOrg}/overview`); + } } catch (error) { console.log("Error - Not logged in yet"); } diff --git a/frontend/src/pages/project/[id]/members/index.tsx b/frontend/src/pages/project/[id]/members/index.tsx index 5287c0430..997606b1d 100644 --- a/frontend/src/pages/project/[id]/members/index.tsx +++ b/frontend/src/pages/project/[id]/members/index.tsx @@ -11,14 +11,18 @@ import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMembe import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable"; import guidGenerator from "@app/components/utilities/randomId"; import { Input } from "@app/components/v2"; -import { useAddUserToWorkspace,useGetUser , useGetWorkspaceUsers } from "@app/hooks/api"; +import { useOrganization } from "@app/context"; +import { + useAddUserToWorkspace, + useGetOrgUsers, + useGetUser, + useGetWorkspaceUsers} from "@app/hooks/api"; +import { uploadWsKey } from "@app/hooks/api/keys/queries"; import { decryptAssymmetric, encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto"; -import getOrganizationUsers from "../../../api/organization/GetOrgUsers"; -import uploadKeys from "../../../api/workspace/uploadKeys"; interface UserProps { firstName: string; @@ -44,6 +48,9 @@ export default function Users() { const workspaceId = router.query.id as string; const { data: user } = useGetUser(); + const { currentOrg } = useOrganization(); + const { data: orgUsers } = useGetOrgUsers(currentOrg?._id ?? ""); + const { data: workspaceUsers } = useGetWorkspaceUsers(workspaceId); const { mutateAsync: addUserToWorkspaceMutateAsync } = useAddUserToWorkspace(); @@ -62,7 +69,7 @@ export default function Users() { const [orgUserList, setOrgUserList] = useState([]); useEffect(() => { - if (user && workspaceUsers) { + if (user && workspaceUsers && orgUsers) { (async () => { setPersonalEmail(user.email); @@ -82,10 +89,6 @@ export default function Users() { setIsUserListLoading(false); - // This is needed to know wha users from an org (if any), we are able to add to a certain project - const orgUsers = await getOrganizationUsers({ - orgId: String(localStorage.getItem("orgData.id")) - }); setOrgUserList(orgUsers); setEmail( orgUsers @@ -98,7 +101,7 @@ export default function Users() { ); })(); } - }, [user, workspaceUsers]); + }, [user, workspaceUsers, orgUsers]); const closeAddModal = () => { setIsAddOpen(false); @@ -143,7 +146,12 @@ export default function Users() { privateKey: PRIVATE_KEY }); - uploadKeys(workspaceId, result.invitee._id, ciphertext, nonce); + await uploadWsKey({ + workspaceId, + userId: result.invitee._id, + encryptedKey: ciphertext, + nonce + }); } setEmail(""); setIsAddOpen(false); diff --git a/frontend/src/pages/settings/org/[id]/service-accounts/[serviceAccountId].tsx b/frontend/src/pages/settings/org/[id]/service-accounts/[serviceAccountId].tsx deleted file mode 100644 index 19ef4f768..000000000 --- a/frontend/src/pages/settings/org/[id]/service-accounts/[serviceAccountId].tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import Head from "next/head"; - -import { CreateServiceAccountPage } from "@app/views/Settings/CreateServiceAccountPage"; - -export default function ServiceAccountPage() { - return ( - <> - - Edit Service Account - - - - - ); -} - -ServiceAccountPage.requireAuth = true; diff --git a/frontend/src/pages/signup/index.tsx b/frontend/src/pages/signup/index.tsx index ff8c41e61..3a6963f60 100644 --- a/frontend/src/pages/signup/index.tsx +++ b/frontend/src/pages/signup/index.tsx @@ -12,9 +12,9 @@ import InitialSignupStep from "@app/components/signup/InitialSignupStep"; import TeamInviteStep from "@app/components/signup/TeamInviteStep"; import UserInfoStep from "@app/components/signup/UserInfoStep"; import SecurityClient from "@app/components/utilities/SecurityClient"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import checkEmailVerificationCode from "@app/pages/api/auth/CheckEmailVerificationCode"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; /** * @returns the signup page @@ -37,7 +37,7 @@ export default function SignUp() { useEffect(() => { const tryAuth = async () => { try { - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); router.push(`/org/${userOrgs[0]._id}/overview`); } catch (error) { console.log("Error - Not logged in yet"); diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 3c2b38cb1..18fff01c5 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -26,8 +26,7 @@ import SecurityClient from "@app/components/utilities/SecurityClient"; import { useGetCommonPasswords } from "@app/hooks/api"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; -import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite"; import verifySignupInvite from "./api/auth/VerifySignupInvite"; @@ -169,7 +168,7 @@ export default function SignupInvite() { privateKey }); - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]._id; localStorage.setItem("orgData.id", orgId); diff --git a/frontend/src/views/Login/Login.tsx b/frontend/src/views/Login/Login.tsx index d7ddf6078..fb10c7844 100644 --- a/frontend/src/views/Login/Login.tsx +++ b/frontend/src/views/Login/Login.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import axios from "axios" +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; import { getAuthToken, isLoggedIn } from "@app/reactQuery"; import { @@ -24,7 +24,7 @@ export const Login = () => { // TODO(akhilmhdh): workspace will be controlled by a workspace context const redirectToDashboard = async () => { try { - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); // userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id; const userOrg = userOrgs[0] && userOrgs[0]._id; diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index 2bee45669..3f7abcba4 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -12,8 +12,8 @@ import { useNotificationContext } from "@app/components/context/Notifications/No import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptLogin from "@app/components/utilities/attemptLogin"; import { Button, Input } from "@app/components/v2"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; type Props = { setStep: (step: number) => void; @@ -90,7 +90,7 @@ export const InitialStep = ({ setIsLoading(false); return; } - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const userOrg = userOrgs[0] && userOrgs[0]._id; // case: login does not require MFA step diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index fe4a0fd01..c607502c9 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -10,7 +10,7 @@ import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa" import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; import { Button } from "@app/components/v2"; import { useSendMfaToken } from "@app/hooks/api/auth"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; // The style for the verification code input const props = { @@ -110,7 +110,7 @@ export const MFAStep = ({ if (isLoginSuccessful) { setIsLoading(false); - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const userOrg = userOrgs[0] && userOrgs[0]._id; // case: login does not require MFA step diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 6076e76bb..dae175eb1 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -8,7 +8,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptLogin from "@app/components/utilities/attemptLogin"; import { Button, Input } from "@app/components/v2"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; type Props = { providerAuthToken: string; @@ -83,14 +83,14 @@ export const PasswordStep = ({ } // case: login does not require MFA step - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const userOrg = userOrgs[0]._id; setIsLoading(false); createNotification({ text: "Successfully logged in", type: "success" }); - router.push(`/org/${userOrg?._id}/overview`); + router.push(`/org/${userOrg}/overview`); } } } catch (err) { diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/CreateServiceAccountPage.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/CreateServiceAccountPage.tsx deleted file mode 100644 index b48057685..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/CreateServiceAccountPage.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useRouter } from "next/router"; - -import NavHeader from "@app/components/navigation/NavHeader"; - -import { SAProjectLevelPermissionsTable } from "./components/SAProjectLevelPermissionsTable"; -import { - CopyServiceAccountPublicKeySection, - ServiceAccountNameChangeSection -} from "./components"; - -export const CreateServiceAccountPage = () => { - const router = useRouter(); - const {serviceAccountId} = router.query; - - return ( -
- -
-

Service Account

-

- A service account represents a machine identity such as a VM or application client. -

-
- {typeof serviceAccountId === "string" && ( -
- -
- -
-
- -
-
- )} -
- ); -} \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountIDSection/CopyServiceAccountIDSection.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountIDSection/CopyServiceAccountIDSection.tsx deleted file mode 100644 index f1f4e0c60..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountIDSection/CopyServiceAccountIDSection.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useEffect } from "react"; -import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import { IconButton } from "@app/components/v2"; -import { useToggle } from "@app/hooks"; - -type Props = { - serviceAccountId: string; -} - -export const CopyServiceAccountIDSection = ({ serviceAccountId }: Props): JSX.Element => { - const [isServiceAccountIdCopied, setIsServiceAccountIdCopied] = useToggle(false); - - useEffect(() => { - let timer: NodeJS.Timeout; - - if (isServiceAccountIdCopied) { - timer = setTimeout(() => setIsServiceAccountIdCopied.off(), 2000); - } - - return () => clearTimeout(timer); - }, [isServiceAccountIdCopied]); - - const copyServiceAccountIdToClipboard = () => { - navigator.clipboard.writeText(serviceAccountId); - setIsServiceAccountIdCopied.on(); - }; - - return ( -
-

Service Account ID

-
-

{serviceAccountId}

- copyServiceAccountIdToClipboard()} - > - - - Copy - - -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountIDSection/index.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountIDSection/index.tsx deleted file mode 100644 index 9efdc2dcd..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountIDSection/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CopyServiceAccountIDSection } from "./CopyServiceAccountIDSection"; \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountPublicKeySection/CopyServiceAccountPublicKeySection.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountPublicKeySection/CopyServiceAccountPublicKeySection.tsx deleted file mode 100644 index a60dc4589..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountPublicKeySection/CopyServiceAccountPublicKeySection.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect } from "react"; -import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import { IconButton } from "@app/components/v2"; -import { useToggle } from "@app/hooks"; -import { useGetServiceAccountById } from "@app/hooks/api"; - -type Props = { - serviceAccountId: string; -} - -export const CopyServiceAccountPublicKeySection = ({ serviceAccountId }: Props): JSX.Element => { - const { data: serviceAccount } = useGetServiceAccountById(serviceAccountId); - const [isServiceAccountIdCopied, setIsServiceAccountIdCopied] = useToggle(false); - - useEffect(() => { - let timer: NodeJS.Timeout; - - if (isServiceAccountIdCopied) { - timer = setTimeout(() => setIsServiceAccountIdCopied.off(), 2000); - } - - return () => clearTimeout(timer); - }, [isServiceAccountIdCopied]); - - const copyServiceAccountIdToClipboard = () => { - if (!serviceAccount) return; - - navigator.clipboard.writeText(serviceAccount.publicKey); - setIsServiceAccountIdCopied.on(); - }; - - return serviceAccount ? ( -
-

Public Key

-
-

{serviceAccount.publicKey}

- copyServiceAccountIdToClipboard()} - > - - - Copy - - -
-
- ) :
-} \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountPublicKeySection/index.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountPublicKeySection/index.tsx deleted file mode 100644 index 2fb93656d..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/CopyServiceAccountPublicKeySection/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CopyServiceAccountPublicKeySection } from "./CopyServiceAccountPublicKeySection"; \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/SAProjectLevelPermissionsTable/SAProjectLevelPermissionsTable.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/SAProjectLevelPermissionsTable/SAProjectLevelPermissionsTable.tsx deleted file mode 100644 index dc56cce65..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/SAProjectLevelPermissionsTable/SAProjectLevelPermissionsTable.tsx +++ /dev/null @@ -1,412 +0,0 @@ -import { useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { faKey, faMagnifyingGlass, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; - -import { - decryptAssymmetric, - encryptAssymmetric, - verifyPrivateKey -} from "@app/components/utilities/cryptography/crypto"; -import { - Button, - Checkbox, - DeleteActionModal, - EmptyState, - FormControl, - IconButton, - Input, - Modal, - ModalClose, - ModalContent, - Select, - SelectItem, - Table, - TableContainer, - TableSkeleton, - TBody, - Td, - Th, - THead, - Tr -} from "@app/components/v2"; -import { usePopUp } from "@app/hooks"; -import { - useCreateServiceAccountProjectLevelPermission, - useDeleteServiceAccountProjectLevelPermission, - useGetServiceAccountById, - useGetServiceAccountProjectLevelPermissions, - useGetUserWorkspaces -} from "@app/hooks/api"; -import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey"; - -const createProjectLevelPermissionSchema = yup.object({ - privateKey: yup.string().required().label("Private Key"), - workspace: yup.string().required().label("Workspace"), - environment: yup.string().required().label("Environment"), - permissions: yup - .object() - .shape({ - read: yup.boolean().required(), - write: yup.boolean().required() - }) - .defined() - .required() -}); - -type CreateProjectLevelPermissionForm = yup.InferType; - -type Props = { - serviceAccountId: string; -}; - -export const SAProjectLevelPermissionsTable = ({ serviceAccountId }: Props): JSX.Element => { - const { data: serviceAccount } = useGetServiceAccountById(serviceAccountId); - const { data: userWorkspaces, isLoading: isUserWorkspacesLoading } = useGetUserWorkspaces(); - const [searchPermissions, setSearchPermissions] = useState(""); - - const { data: serviceAccountWorkspacePermissions, isLoading: isPermissionsLoading } = - useGetServiceAccountProjectLevelPermissions(serviceAccountId); - - const createServiceAccountProjectLevelPermission = - useCreateServiceAccountProjectLevelPermission(); - const deleteServiceAccountProjectLevelPermission = - useDeleteServiceAccountProjectLevelPermission(); - - const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - "addProjectLevelPermission", - "removeProjectLevelPermission" - ] as const); - - const [, setSelectedWorkspace] = useState(undefined); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting } - } = useForm({ - resolver: yupResolver(createProjectLevelPermissionSchema) - }); - - const onAddProjectLevelPermission = async ({ - privateKey, - workspace, - environment, - permissions: { read, write } - }: CreateProjectLevelPermissionForm) => { - // TODO: clean up / modularize this function - - if (!serviceAccount) return; - - const { latestKey } = await getLatestFileKey({ - workspaceId: workspace - }); - - verifyPrivateKey({ - privateKey, - publicKey: serviceAccount.publicKey - }); - - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - - const key = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: key, - publicKey: serviceAccount.publicKey, - privateKey - }); - - await createServiceAccountProjectLevelPermission.mutateAsync({ - serviceAccountId, - workspaceId: workspace, - environment, - read, - write, - encryptedKey: ciphertext, - nonce - }); - handlePopUpClose("addProjectLevelPermission"); - }; - - const onRemoveProjectLevelPermission = async () => { - const serviceAccountWorkspacePermissionId = ( - popUp?.removeProjectLevelPermission?.data as { _id: string } - )?._id; - await deleteServiceAccountProjectLevelPermission.mutateAsync({ - serviceAccountId, - serviceAccountWorkspacePermissionId - }); - handlePopUpClose("removeProjectLevelPermission"); - }; - - return ( -
-

Project-Level Permissions

-
-
- setSearchPermissions(e.target.value)} - leftIcon={} - placeholder="Search service account project-level permissions..." - /> -
- -
- - - - - - - - - - - - {isPermissionsLoading && ( - - )} - {!isPermissionsLoading && - serviceAccountWorkspacePermissions && - serviceAccountWorkspacePermissions.map( - ({ _id, workspace, environment, read, write }) => { - const environmentName = workspace.environments.find( - (env) => env.slug === environment - )?.name; - return ( - - - - - - - - ); - } - )} - {!isPermissionsLoading && serviceAccountWorkspacePermissions?.length === 0 && ( - - - - )} - -
ProjectEnvironmentReadWrite -
{workspace.name}{environmentName} - - {/**/} - - - - {/**/} - - - handlePopUpOpen("removeProjectLevelPermission", { _id })} - > - - -
- -
-
- { - handlePopUpToggle("addProjectLevelPermission", isOpen); - }} - > - -
- {!isUserWorkspacesLoading && userWorkspaces && ( - <> - ( - - - - )} - /> - ( - - - - )} - /> - { - const environments = - userWorkspaces?.find( - /* eslint-disable-next-line no-underscore-dangle */ - (userWorkspace) => userWorkspace._id === control?._formValues?.workspace - )?.environments ?? []; - return ( - - - - ); - }} - /> - - )} - { - const options = [ - { - label: "Read (default)", - value: "read" - }, - { - label: "Write", - value: "write" - } - ]; - - return ( - - <> - {options.map(({ label, value: optionValue }) => { - return ( - { - onChange({ - ...value, - [optionValue]: state - }); - }} - > - {label} - - ); - })} - - - ); - }} - /> -
- - - - -
- -
-
- handlePopUpToggle("removeProjectLevelPermission", isOpen)} - onDeleteApproved={onRemoveProjectLevelPermission} - /> -
- ); -}; diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/SAProjectLevelPermissionsTable/index.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/SAProjectLevelPermissionsTable/index.tsx deleted file mode 100644 index fee5544ef..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/SAProjectLevelPermissionsTable/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { SAProjectLevelPermissionsTable } from "./SAProjectLevelPermissionsTable"; \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/ServiceAccountNameChangeSection/ServiceAccountNameChangeSection.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/ServiceAccountNameChangeSection/ServiceAccountNameChangeSection.tsx deleted file mode 100644 index 4c6cd64b7..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/ServiceAccountNameChangeSection/ServiceAccountNameChangeSection.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { faCheck } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; - -import { - Button, - FormControl, - Input} from "@app/components/v2"; -import { - useGetServiceAccountById, - useRenameServiceAccount -} from "@app/hooks/api"; - -const formSchema = yup.object({ - name: yup.string().required().label("Service Account Name") -}); - -type FormData = yup.InferType; - -type Props = { - serviceAccountId: string; -} - -export const ServiceAccountNameChangeSection = ({ - serviceAccountId -}: Props) => { - const { data: serviceAccount, isLoading: isServiceAccountLoading } = useGetServiceAccountById(serviceAccountId); - - const renameServiceAccount = useRenameServiceAccount(); - - const { - handleSubmit, - control, - reset, - formState: { isDirty, isSubmitting } - } = useForm({ resolver: yupResolver(formSchema) }); - - useEffect(() => { - reset({ name: serviceAccount?.name }); - }, [serviceAccount?.name]); - - const onFormSubmit = async ({ name }: FormData) => { - try { - await renameServiceAccount.mutateAsync({ - serviceAccountId, - name - }); - } catch (err) { - console.error(err); - } - } - - return ( -
-

Name

-
- {!isServiceAccountLoading && ( - ( - - - - )} - control={control} - name="name" - /> - )} -
- -
- ); -} diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/ServiceAccountNameChangeSection/index.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/ServiceAccountNameChangeSection/index.tsx deleted file mode 100644 index bedbfa7a4..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/ServiceAccountNameChangeSection/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ServiceAccountNameChangeSection } from "./ServiceAccountNameChangeSection"; \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/components/index.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/components/index.tsx deleted file mode 100644 index 20d12ed5a..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/components/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { CopyServiceAccountIDSection } from "./CopyServiceAccountIDSection"; -export { CopyServiceAccountPublicKeySection } from "./CopyServiceAccountPublicKeySection"; -export { SAProjectLevelPermissionsTable } from "./SAProjectLevelPermissionsTable"; -export { ServiceAccountNameChangeSection } from "./ServiceAccountNameChangeSection"; \ No newline at end of file diff --git a/frontend/src/views/Settings/CreateServiceAccountPage/index.tsx b/frontend/src/views/Settings/CreateServiceAccountPage/index.tsx deleted file mode 100644 index 8dfecafb1..000000000 --- a/frontend/src/views/Settings/CreateServiceAccountPage/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CreateServiceAccountPage } from "./CreateServiceAccountPage"; \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx index 8e765f3ff..374681003 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx @@ -4,14 +4,13 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { Checkbox } from "@app/components/v2"; import { useWorkspace } from "@app/context"; -import { useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api"; - -import getLatestFileKey from "../../../../../pages/api/workspace/getLatestFileKey"; +import { useGetUserWsKey,useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api"; export const E2EESection = () => { const { currentWorkspace } = useWorkspace(); const { data: bot } = useGetWorkspaceBot(currentWorkspace?._id ?? ""); const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus(); + const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? ""); /** * Activate bot for project by performing the following steps: @@ -25,14 +24,12 @@ export const E2EESection = () => { try { if (!currentWorkspace?._id) return; - if (bot) { + if (bot && wsKey) { // case: there is a bot if (!bot.isActive) { // bot is not active -> activate bot - const key = await getLatestFileKey({ - workspaceId: currentWorkspace._id - }); + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); if (!PRIVATE_KEY) { @@ -40,9 +37,9 @@ export const E2EESection = () => { } const WORKSPACE_KEY = decryptAssymmetric({ - ciphertext: key.latestKey.encryptedKey, - nonce: key.latestKey.nonce, - publicKey: key.latestKey.sender.publicKey, + ciphertext: wsKey.encryptedKey, + nonce: wsKey.nonce, + publicKey: wsKey.sender.publicKey, privateKey: PRIVATE_KEY }); diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 032552958..01002c38f 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -17,8 +17,8 @@ import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLo import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, Input } from "@app/components/v2"; import { useGetCommonPasswords } from "@app/hooks/api"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup"; -import getOrganizations from "@app/pages/api/organization/getOrgs"; import ProjectService from "@app/services/ProjectService"; // eslint-disable-next-line new-cap @@ -188,7 +188,7 @@ export const UserInfoSSOStep = ({ privateKey }); - const userOrgs = await getOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]?._id; const project = await ProjectService.initProject({ organizationId: orgId,