mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Convert all SecurityClient API calls to hooks except auth
This commit is contained in:
@@ -8,10 +8,9 @@ import { useSubscription, useWorkspace } from "@app/context";
|
|||||||
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
|
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
|
||||||
import {
|
import {
|
||||||
useDeleteUserFromWorkspace,
|
useDeleteUserFromWorkspace,
|
||||||
useUpdateUserWorkspaceRole
|
useGetUserWsKey,
|
||||||
} from "@app/hooks/api";
|
useUpdateUserWorkspaceRole,
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
useUploadWsKey} from "@app/hooks/api";
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
||||||
import guidGenerator from "../../utilities/randomId";
|
import guidGenerator from "../../utilities/randomId";
|
||||||
@@ -42,7 +41,10 @@ type EnvironmentProps = {
|
|||||||
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
|
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
|
||||||
|
|
||||||
const { mutateAsync: deleteUserFromWorkspaceMutateAsync } = useDeleteUserFromWorkspace();
|
const { mutateAsync: deleteUserFromWorkspaceMutateAsync } = useDeleteUserFromWorkspace();
|
||||||
|
const { mutateAsync: uploadWsKeyMutateAsync } = useUploadWsKey();
|
||||||
const { mutateAsync: updateUserWorkspaceRoleMutateAsync } = useUpdateUserWorkspaceRole();
|
const { mutateAsync: updateUserWorkspaceRoleMutateAsync } = useUpdateUserWorkspaceRole();
|
||||||
// const [roleSelected, setRoleSelected] = useState(
|
// const [roleSelected, setRoleSelected] = useState(
|
||||||
// Array(userData?.length).fill(userData.map((user) => user.role))
|
// Array(userData?.length).fill(userData.map((user) => user.role))
|
||||||
@@ -151,26 +153,31 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
|
|||||||
}, [userData, myUser, currentWorkspace]);
|
}, [userData, myUser, currentWorkspace]);
|
||||||
|
|
||||||
const grantAccess = async (id: string, publicKey: string) => {
|
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 { ciphertext, nonce } = encryptAssymmetric({
|
||||||
const key = decryptAssymmetric({
|
plaintext: key,
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
publicKey,
|
||||||
nonce: result.latestKey.nonce,
|
privateKey: PRIVATE_KEY
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
});
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
await uploadWsKeyMutateAsync({
|
||||||
plaintext: key,
|
workspaceId,
|
||||||
publicKey,
|
userId: id,
|
||||||
privateKey: PRIVATE_KEY
|
encryptedKey: ciphertext,
|
||||||
});
|
nonce
|
||||||
|
});
|
||||||
uploadKeys(workspaceId, id, ciphertext, nonce);
|
router.reload();
|
||||||
router.reload();
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeUpgradeModal = () => {
|
const closeUpgradeModal = () => {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { useAddUserToOrg } from "@app/hooks/api";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
import addUserToOrg from "@app/pages/api/organization/addUserToOrg";
|
|
||||||
|
|
||||||
import { Button, EmailServiceSetupModal } from "../v2";
|
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.
|
* This is the last step of the signup flow. People can optionally invite their teammates here.
|
||||||
*/
|
*/
|
||||||
export default function TeamInviteStep(): JSX.Element {
|
export default function TeamInviteStep(): JSX.Element {
|
||||||
const [emails, setEmails] = useState("");
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [emails, setEmails] = useState("");
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
|
||||||
|
const { mutateAsync } = useAddUserToOrg();
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
|
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
|
||||||
|
|
||||||
// Redirect user to the getting started page
|
// Redirect user to the getting started page
|
||||||
@@ -27,7 +29,12 @@ export default function TeamInviteStep(): JSX.Element {
|
|||||||
inviteEmails
|
inviteEmails
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((email) => email.trim())
|
.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();
|
await redirectToHome();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import nacl from "tweetnacl";
|
|||||||
import { encodeBase64 } from "tweetnacl-util";
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
|
||||||
import { useGetCommonPasswords } from "@app/hooks/api";
|
import { useGetCommonPasswords } from "@app/hooks/api";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
|
import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
|
||||||
import ProjectService from "@app/services/ProjectService";
|
import ProjectService from "@app/services/ProjectService";
|
||||||
|
|
||||||
import InputField from "../basic/InputField";
|
import InputField from "../basic/InputField";
|
||||||
@@ -190,7 +190,8 @@ export default function UserInfoStep({
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
const orgId = userOrgs[0]?._id;
|
const orgId = userOrgs[0]?._id;
|
||||||
const project = await ProjectService.initProject({
|
const project = await ProjectService.initProject({
|
||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
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 login1 from "@app/pages/api/auth/Login1";
|
||||||
import login2 from "@app/pages/api/auth/Login2";
|
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 KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import Telemetry from "./telemetry/Telemetry";
|
import Telemetry from "./telemetry/Telemetry";
|
||||||
@@ -125,13 +125,11 @@ const attemptLogin = async (
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orgUserProjects.length > 0) {
|
if (orgUserProjects.length > 0) {
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
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 login1 from "@app/pages/api/auth/Login1";
|
||||||
import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
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 KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||||
@@ -96,13 +96,11 @@ const attemptLoginMfa = async ({
|
|||||||
// TODO: in the future - move this logic elsewhere
|
// TODO: in the future - move this logic elsewhere
|
||||||
// because this function is about logging the user in
|
// because this function is about logging the user in
|
||||||
// and not initializing the login details
|
// and not initializing the login details
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
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 login1 from "@app/pages/api/auth/Login1";
|
||||||
import login2 from "@app/pages/api/auth/Login2";
|
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 KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import Telemetry from "./telemetry/Telemetry";
|
import Telemetry from "./telemetry/Telemetry";
|
||||||
@@ -36,7 +36,6 @@ const attemptLogin = async (
|
|||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}
|
}
|
||||||
): Promise<IsLoginSuccessful> => {
|
): Promise<IsLoginSuccessful> => {
|
||||||
|
|
||||||
const telemetry = new Telemetry().getInstance();
|
const telemetry = new Telemetry().getInstance();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
client.init(
|
client.init(
|
||||||
@@ -124,14 +123,12 @@ const attemptLogin = async (
|
|||||||
// TODO: in the future - move this logic elsewhere
|
// TODO: in the future - move this logic elsewhere
|
||||||
// because this function is about logging the user in
|
// because this function is about logging the user in
|
||||||
// and not initializing the login details
|
// and not initializing the login details
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orgUserProjects.length > 0) {
|
if (orgUserProjects.length > 0) {
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
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 login1 from "@app/pages/api/auth/Login1";
|
||||||
import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
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 KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||||
@@ -87,13 +87,11 @@ const attemptLoginMfa = async ({
|
|||||||
// TODO: in the future - move this logic elsewhere
|
// TODO: in the future - move this logic elsewhere
|
||||||
// because this function is about logging the user in
|
// because this function is about logging the user in
|
||||||
// and not initializing the login details
|
// and not initializing the login details
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { fetchUserAction } from "@app/hooks/api/users/queries";
|
import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries";
|
||||||
import getOrganizationUsers from "@app/pages/api/organization/GetOrgUsers";
|
|
||||||
|
|
||||||
interface OnboardingCheckProps {
|
interface OnboardingCheckProps {
|
||||||
setTotalOnboardingActionsDone?: (value: number) => void;
|
setTotalOnboardingActionsDone?: (value: number) => void;
|
||||||
@@ -43,9 +42,8 @@ const onboardingCheck = async ({
|
|||||||
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
|
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
|
||||||
|
|
||||||
const orgId = localStorage.getItem("orgData.id");
|
const orgId = localStorage.getItem("orgData.id");
|
||||||
const orgUsers = await getOrganizationUsers({
|
const orgUsers = await fetchOrgUsers(orgId || "");
|
||||||
orgId: orgId || ""
|
|
||||||
});
|
|
||||||
if (orgUsers.length > 1) {
|
if (orgUsers.length > 1) {
|
||||||
countActions += 1;
|
countActions += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
|
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";
|
import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto";
|
||||||
|
|
||||||
@@ -42,19 +42,21 @@ const encryptSecrets = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
let secrets;
|
let secrets;
|
||||||
try {
|
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;
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
||||||
|
|
||||||
let randomBytes: string;
|
let randomBytes: string;
|
||||||
if (Object.keys(sharedKey).length > 0) {
|
if (wsKey) {
|
||||||
// case: a (shared) key exists for the workspace
|
// case: a (shared) key exists for the workspace
|
||||||
randomBytes = decryptAssymmetric({
|
randomBytes = decryptAssymmetric({
|
||||||
ciphertext: sharedKey.latestKey.encryptedKey,
|
ciphertext: wsKey.encryptedKey,
|
||||||
nonce: sharedKey.latestKey.nonce,
|
nonce: wsKey.nonce,
|
||||||
publicKey: sharedKey.latestKey.sender.publicKey,
|
publicKey: wsKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// case: a (shared) key does not exist for the workspace
|
// case: a (shared) key does not exist for the workspace
|
||||||
randomBytes = crypto.randomBytes(16).toString("hex");
|
randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
@@ -114,6 +116,7 @@ const encryptSecrets = async ({
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error while encrypting secrets");
|
console.log("Error while encrypting secrets");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
|
|
||||||
import getActionData from "@app/ee/api/secrets/GetActionData";
|
import getActionData from "@app/ee/api/secrets/GetActionData";
|
||||||
import patienceDiff from "@app/ee/utilities/findTextDifferences";
|
import patienceDiff from "@app/ee/utilities/findTextDifferences";
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
import {
|
||||||
|
useGetUserWsKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
@@ -59,25 +61,24 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
|||||||
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
|
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
|
||||||
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
|
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { data: wsKey } = useGetUserWsKey(String(router.query.id));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getLogData = async () => {
|
const getLogData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const tempActionData = await getActionData({ actionId: currentAction });
|
const tempActionData = await getActionData({ actionId: currentAction });
|
||||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
// #TODO: make this a separate function and reuse across the app
|
// #TODO: make this a separate function and reuse across the app
|
||||||
let decryptedLatestKey: string;
|
let decryptedLatestKey: string;
|
||||||
if (latestKey) {
|
if (wsKey) {
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
decryptedLatestKey = decryptAssymmetric({
|
decryptedLatestKey = decryptAssymmetric({
|
||||||
ciphertext: latestKey.latestKey.encryptedKey,
|
ciphertext: wsKey.encryptedKey,
|
||||||
nonce: latestKey.latestKey.nonce,
|
nonce: wsKey.nonce,
|
||||||
publicKey: latestKey.latestKey.sender.publicKey,
|
publicKey: wsKey.sender.publicKey,
|
||||||
privateKey: String(PRIVATE_KEY)
|
privateKey: String(PRIVATE_KEY)
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
|
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
|
||||||
(encryptedSecretVersion: {
|
(encryptedSecretVersion: {
|
||||||
@@ -122,9 +123,10 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
|||||||
setActionData(decryptedSecretVersions);
|
setActionData(decryptedSecretVersions);
|
||||||
setActionMetaData({ name: tempActionData.name });
|
setActionMetaData({ name: tempActionData.name });
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
getLogData();
|
getLogData();
|
||||||
}, [currentAction]);
|
}, [currentAction, wsKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,287 +0,0 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { Tag } from "public/data/frequentInterfaces";
|
|
||||||
|
|
||||||
import Button from "@app/components/basic/buttons/Button";
|
|
||||||
import {
|
|
||||||
decryptAssymmetric,
|
|
||||||
decryptSymmetric
|
|
||||||
} from "@app/components/utilities/cryptography/crypto";
|
|
||||||
import getProjectSecretShanpshots from "@app/ee/api/secrets/GetProjectSercetShanpshots";
|
|
||||||
import getSecretSnapshotData from "@app/ee/api/secrets/GetSecretSnapshotData";
|
|
||||||
import timeSince from "@app/ee/utilities/timeSince";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
export interface SecretDataProps {
|
|
||||||
pos: number;
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
environment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SideBarProps {
|
|
||||||
toggleSidebar: (value: boolean) => 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<SnaphotProps[]>([]);
|
|
||||||
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 (
|
|
||||||
<div
|
|
||||||
className={`min-w-sm absolute w-full max-w-sm border-l border-mineshaft-500 ${
|
|
||||||
isLoading ? "bg-bunker-800" : "bg-bunker"
|
|
||||||
} fixed sticky right-0 top-0 z-[40] flex h-full flex-col justify-between shadow-xl`}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="mb-8 flex h-full items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="h-min">
|
|
||||||
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
|
|
||||||
<p className="text-lg font-semibold text-bunker-200">Point In Recovery</p>
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="p-1"
|
|
||||||
onClick={() => toggleSidebar(false)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-[calc(100vh-115px)] w-96 flex-col overflow-y-auto border-l border-mineshaft-600 bg-bunker px-2 py-2">
|
|
||||||
<span className="px-2 pb-2 text-sm text-bunker-200">
|
|
||||||
Note: This will recover secrets for all enviroments in this project.
|
|
||||||
</span>
|
|
||||||
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) => (
|
|
||||||
<div
|
|
||||||
onKeyDown={() => 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`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-start">
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "text-bunker-800"
|
|
||||||
: "text-bunker-200"
|
|
||||||
} mr-1.5 text-sm`}
|
|
||||||
>
|
|
||||||
{timeSince(new Date(snapshot.createdAt))}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "text-bunker-900"
|
|
||||||
: "text-bunker-300"
|
|
||||||
} text-sm `}
|
|
||||||
>{` - ${snapshot.secretVersions.length} Secrets`}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "pointer-events-none text-bunker-800"
|
|
||||||
: "cursor-pointer text-bunker-200 duration-200 hover:text-primary"
|
|
||||||
} text-sm`}
|
|
||||||
>
|
|
||||||
{id === 0
|
|
||||||
? "Current Version"
|
|
||||||
: chosenSnapshot === snapshot._id
|
|
||||||
? "Currently Viewing"
|
|
||||||
: "Explore"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="mb-14 flex w-full justify-center">
|
|
||||||
<div className="w-40 items-center">
|
|
||||||
<Button
|
|
||||||
text="View More"
|
|
||||||
textDisabled="End of History"
|
|
||||||
active={secretSnapshotsMetadata.length % 15 === 0}
|
|
||||||
onButtonPressed={loadMoreSnapshots}
|
|
||||||
size="md"
|
|
||||||
color="mineshaft"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PITRecoverySidebar;
|
|
||||||
@@ -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<DecryptedSecretVersionListProps[]>([]);
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="min-w-40 overflow-x-none dark mt-4 h-[12.4rem] w-full px-4 text-sm text-bunker-300">
|
|
||||||
<p className="">{t("dashboard.sidebar.version-history")}</p>
|
|
||||||
<div className="overflow-x-none h-full rounded-md border border-mineshaft-500 bg-bunker-800 py-0.5 pl-1">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="overflow-x-none h-48 overflow-y-auto dark:[color-scheme:dark]">
|
|
||||||
{secretVersions ? (
|
|
||||||
secretVersions
|
|
||||||
?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
||||||
.map((version: DecryptedSecretVersionListProps, index: number) => (
|
|
||||||
<div key={`${version.createdAt}.${index + 1}`} className="flex flex-row">
|
|
||||||
<div className="flex flex-col items-center pr-1">
|
|
||||||
<div className="p-1">
|
|
||||||
<FontAwesomeIcon icon={index === 0 ? faDotCircle : faCircle} />
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 h-full w-0 border-l border-bunker-300" />
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full max-w-[calc(100%-2.3rem)] flex-col">
|
|
||||||
<div className="pr-2 text-bunker-300/90">
|
|
||||||
{new Date(version.createdAt).toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit"
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="">
|
|
||||||
<p className="ph-no-capture break-words">
|
|
||||||
<span className="mr-1.5 rounded-sm bg-primary-500/30 py-0.5 px-1">
|
|
||||||
Value:
|
|
||||||
</span>
|
|
||||||
<span className="font-mono">{version.value}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="flex h-full w-full items-center justify-center text-bunker-400">
|
|
||||||
No version history yet.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SecretVersionList;
|
|
||||||
@@ -2,10 +2,10 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||||
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
|
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
|
||||||
|
import { uploadWsKey } from "@app/hooks/api/keys/queries";
|
||||||
import { createSecret } from "@app/hooks/api/secrets/queries";
|
import { createSecret } from "@app/hooks/api/secrets/queries";
|
||||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||||
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
|
||||||
|
|
||||||
const secretsToBeAdded = [
|
const secretsToBeAdded = [
|
||||||
{
|
{
|
||||||
@@ -111,7 +111,12 @@ const initProjectHelper = async ({
|
|||||||
privateKey: PRIVATE_KEY
|
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
|
// encrypt and upload secrets to new project
|
||||||
const secrets = await encryptSecrets({
|
const secrets = await encryptSecrets({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const encKeyKeys = {
|
|||||||
getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const
|
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 }>(
|
const { data } = await apiRequest.get<{ latestKey: UserWsKeyPair }>(
|
||||||
`/api/v1/key/${workspaceID}/latest`
|
`/api/v1/key/${workspaceID}/latest`
|
||||||
);
|
);
|
||||||
@@ -24,8 +24,23 @@ export const useGetUserWsKey = (workspaceID: string) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
|
export const uploadWsKey = async ({
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
encryptedKey,
|
||||||
|
nonce
|
||||||
|
}: UploadWsKeyDTO) => {
|
||||||
|
return apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
|
||||||
|
}
|
||||||
|
|
||||||
export const useUploadWsKey = () =>
|
export const useUploadWsKey = () =>
|
||||||
useMutation<{}, {}, UploadWsKeyDTO>({
|
useMutation<{}, {}, UploadWsKeyDTO>({
|
||||||
mutationFn: ({ encryptedKey, nonce, userId, workspaceId }) =>
|
mutationFn: async ({ encryptedKey, nonce, userId, workspaceId }) => {
|
||||||
apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
|
return uploadWsKey({
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
encryptedKey,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,12 +27,16 @@ const organizationKeys = {
|
|||||||
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
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 = () => {
|
export const useGetOrganizations = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: organizationKeys.getUserOrganizations,
|
queryKey: organizationKeys.getUserOrganizations,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
return fetchOrganizations();
|
||||||
return organizations;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -42,7 +46,6 @@ export const useRenameOrg = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, RenameOrgDTO>({
|
return useMutation<{}, {}, RenameOrgDTO>({
|
||||||
mutationFn: ({ newOrgName, orgId }) => {
|
mutationFn: ({ newOrgName, orgId }) => {
|
||||||
console.log("useRenameOrg");
|
|
||||||
return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName });
|
return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName });
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
useDeleteOrgMembership,
|
useDeleteOrgMembership,
|
||||||
useGetMyAPIKeys,
|
useGetMyAPIKeys,
|
||||||
useGetMyIp,
|
useGetMyIp,
|
||||||
|
useGetMyOrganizationProjects,
|
||||||
useGetMySessions,
|
useGetMySessions,
|
||||||
useGetOrgUsers,
|
useGetOrgUsers,
|
||||||
useGetUser,
|
useGetUser,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const userKeys = {
|
|||||||
myIp: ["ip"] as const,
|
myIp: ["ip"] as const,
|
||||||
myAPIKeys: ["api-keys"] as const,
|
myAPIKeys: ["api-keys"] as const,
|
||||||
mySessions: ["sessions"] as const,
|
mySessions: ["sessions"] as const,
|
||||||
|
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchUserDetails = async () => {
|
export const fetchUserDetails = async () => {
|
||||||
@@ -147,7 +148,9 @@ export const useAddUserToOrg = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return useMutation<Response, {}, AddUserToOrgDTO>({
|
return useMutation<Response, {}, AddUserToOrgDTO>({
|
||||||
mutationFn: (dto) => apiRequest.post("/api/v1/invite-org/signup", dto),
|
mutationFn: (dto) => {
|
||||||
|
return apiRequest.post("/api/v1/invite-org/signup", dto);
|
||||||
|
},
|
||||||
onSuccess: (_, { organizationId }) => {
|
onSuccess: (_, { organizationId }) => {
|
||||||
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
||||||
}
|
}
|
||||||
@@ -329,4 +332,22 @@ export const useUpdateMfaEnabled = () => {
|
|||||||
queryClient.invalidateQueries(userKeys.getUser);
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
@@ -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<IMembershipOrg[]> =>
|
|
||||||
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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import getOrganizations from "./api/organization/getOrgs";
|
import { useGetOrganizations } from "@app/hooks/api";
|
||||||
|
|
||||||
export default function DashboardRedirect() {
|
export default function DashboardRedirect() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { data: userOrgs } = useGetOrganizations();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Here we forward to the default workspace if a user opens this url
|
* Here we forward to the default workspace if a user opens this url
|
||||||
@@ -16,11 +17,10 @@ export default function DashboardRedirect() {
|
|||||||
try {
|
try {
|
||||||
if (localStorage.getItem("orgData.id")) {
|
if (localStorage.getItem("orgData.id")) {
|
||||||
router.push(`/org/${localStorage.getItem("orgData.id")}/overview`);
|
router.push(`/org/${localStorage.getItem("orgData.id")}/overview`);
|
||||||
} else {
|
} else if (userOrgs) {
|
||||||
const userOrgs = await getOrganizations();
|
userOrg = userOrgs[0]._id;
|
||||||
userOrg = userOrgs[0]._id;
|
router.push(`/org/${userOrg}/overview`);
|
||||||
router.push(`/org/${userOrg}/overview`);
|
}
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error - Not logged in yet");
|
console.log("Error - Not logged in yet");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,18 @@ import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMembe
|
|||||||
import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable";
|
import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable";
|
||||||
import guidGenerator from "@app/components/utilities/randomId";
|
import guidGenerator from "@app/components/utilities/randomId";
|
||||||
import { Input } from "@app/components/v2";
|
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 {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric
|
||||||
} from "../../../../components/utilities/cryptography/crypto";
|
} from "../../../../components/utilities/cryptography/crypto";
|
||||||
import getOrganizationUsers from "../../../api/organization/GetOrgUsers";
|
|
||||||
import uploadKeys from "../../../api/workspace/uploadKeys";
|
|
||||||
|
|
||||||
interface UserProps {
|
interface UserProps {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -44,6 +48,9 @@ export default function Users() {
|
|||||||
const workspaceId = router.query.id as string;
|
const workspaceId = router.query.id as string;
|
||||||
|
|
||||||
const { data: user } = useGetUser();
|
const { data: user } = useGetUser();
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { data: orgUsers } = useGetOrgUsers(currentOrg?._id ?? "");
|
||||||
|
|
||||||
const { data: workspaceUsers } = useGetWorkspaceUsers(workspaceId);
|
const { data: workspaceUsers } = useGetWorkspaceUsers(workspaceId);
|
||||||
const { mutateAsync: addUserToWorkspaceMutateAsync } = useAddUserToWorkspace();
|
const { mutateAsync: addUserToWorkspaceMutateAsync } = useAddUserToWorkspace();
|
||||||
|
|
||||||
@@ -62,7 +69,7 @@ export default function Users() {
|
|||||||
const [orgUserList, setOrgUserList] = useState<any[]>([]);
|
const [orgUserList, setOrgUserList] = useState<any[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && workspaceUsers) {
|
if (user && workspaceUsers && orgUsers) {
|
||||||
(async () => {
|
(async () => {
|
||||||
setPersonalEmail(user.email);
|
setPersonalEmail(user.email);
|
||||||
|
|
||||||
@@ -82,10 +89,6 @@ export default function Users() {
|
|||||||
|
|
||||||
setIsUserListLoading(false);
|
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);
|
setOrgUserList(orgUsers);
|
||||||
setEmail(
|
setEmail(
|
||||||
orgUsers
|
orgUsers
|
||||||
@@ -98,7 +101,7 @@ export default function Users() {
|
|||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}, [user, workspaceUsers]);
|
}, [user, workspaceUsers, orgUsers]);
|
||||||
|
|
||||||
const closeAddModal = () => {
|
const closeAddModal = () => {
|
||||||
setIsAddOpen(false);
|
setIsAddOpen(false);
|
||||||
@@ -143,7 +146,12 @@ export default function Users() {
|
|||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadKeys(workspaceId, result.invitee._id, ciphertext, nonce);
|
await uploadWsKey({
|
||||||
|
workspaceId,
|
||||||
|
userId: result.invitee._id,
|
||||||
|
encryptedKey: ciphertext,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setEmail("");
|
setEmail("");
|
||||||
setIsAddOpen(false);
|
setIsAddOpen(false);
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Edit Service Account</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
</Head>
|
|
||||||
<CreateServiceAccountPage />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceAccountPage.requireAuth = true;
|
|
||||||
@@ -12,9 +12,9 @@ import InitialSignupStep from "@app/components/signup/InitialSignupStep";
|
|||||||
import TeamInviteStep from "@app/components/signup/TeamInviteStep";
|
import TeamInviteStep from "@app/components/signup/TeamInviteStep";
|
||||||
import UserInfoStep from "@app/components/signup/UserInfoStep";
|
import UserInfoStep from "@app/components/signup/UserInfoStep";
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import checkEmailVerificationCode from "@app/pages/api/auth/CheckEmailVerificationCode";
|
import checkEmailVerificationCode from "@app/pages/api/auth/CheckEmailVerificationCode";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the signup page
|
* @returns the signup page
|
||||||
@@ -37,7 +37,7 @@ export default function SignUp() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tryAuth = async () => {
|
const tryAuth = async () => {
|
||||||
try {
|
try {
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
router.push(`/org/${userOrgs[0]._id}/overview`);
|
router.push(`/org/${userOrgs[0]._id}/overview`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error - Not logged in yet");
|
console.log("Error - Not logged in yet");
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
|
|||||||
import {
|
import {
|
||||||
useGetCommonPasswords
|
useGetCommonPasswords
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
|
||||||
|
|
||||||
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
|
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
|
||||||
import verifySignupInvite from "./api/auth/VerifySignupInvite";
|
import verifySignupInvite from "./api/auth/VerifySignupInvite";
|
||||||
@@ -169,7 +168,7 @@ export default function SignupInvite() {
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useEffect, useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
|
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
|
||||||
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
|
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +24,7 @@ export const Login = () => {
|
|||||||
// TODO(akhilmhdh): workspace will be controlled by a workspace context
|
// TODO(akhilmhdh): workspace will be controlled by a workspace context
|
||||||
const redirectToDashboard = async () => {
|
const redirectToDashboard = async () => {
|
||||||
try {
|
try {
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
// userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
// userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
||||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
|||||||
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
||||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||||
import { Button, Input } from "@app/components/v2";
|
import { Button, Input } from "@app/components/v2";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setStep: (step: number) => void;
|
setStep: (step: number) => void;
|
||||||
@@ -90,7 +90,7 @@ export const InitialStep = ({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||||
|
|
||||||
// case: login does not require MFA step
|
// case: login does not require MFA step
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"
|
|||||||
import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
|
import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
import { useSendMfaToken } from "@app/hooks/api/auth";
|
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
|
// The style for the verification code input
|
||||||
const props = {
|
const props = {
|
||||||
@@ -110,7 +110,7 @@ export const MFAStep = ({
|
|||||||
|
|
||||||
if (isLoginSuccessful) {
|
if (isLoginSuccessful) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||||
|
|
||||||
// case: login does not require MFA step
|
// case: login does not require MFA step
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
|||||||
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
||||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||||
import { Button, Input } from "@app/components/v2";
|
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 = {
|
type Props = {
|
||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
@@ -83,14 +83,14 @@ export const PasswordStep = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// case: login does not require MFA step
|
// case: login does not require MFA step
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const userOrg = userOrgs[0]._id;
|
const userOrg = userOrgs[0]._id;
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully logged in",
|
text: "Successfully logged in",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
router.push(`/org/${userOrg?._id}/overview`);
|
router.push(`/org/${userOrg}/overview`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
|
||||||
<NavHeader
|
|
||||||
pageName="Service Account"
|
|
||||||
isOrganizationRelated
|
|
||||||
/>
|
|
||||||
<div className="my-8 ml-6 max-w-5xl">
|
|
||||||
<p className="text-3xl font-semibold text-gray-200">Service Account</p>
|
|
||||||
<p className="text-base font-normal text-gray-400">
|
|
||||||
A service account represents a machine identity such as a VM or application client.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{typeof serviceAccountId === "string" && (
|
|
||||||
<div className="max-w-8xl mx-6">
|
|
||||||
<ServiceAccountNameChangeSection
|
|
||||||
serviceAccountId={serviceAccountId}
|
|
||||||
/>
|
|
||||||
<div className="mt-8">
|
|
||||||
<CopyServiceAccountPublicKeySection
|
|
||||||
serviceAccountId={serviceAccountId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8">
|
|
||||||
<SAProjectLevelPermissionsTable
|
|
||||||
serviceAccountId={serviceAccountId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<div className="flex w-full flex-col items-start rounded-md bg-white/5 px-6 p-6">
|
|
||||||
<p className="text-xl font-semibold">Service Account ID</p>
|
|
||||||
<div className="mt-4 flex items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400 p-2">
|
|
||||||
<p className="mr-4 break-all">{serviceAccountId}</p>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => copyServiceAccountIdToClipboard()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={isServiceAccountIdCopied ? faCheck : faCopy} />
|
|
||||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { CopyServiceAccountIDSection } from "./CopyServiceAccountIDSection";
|
|
||||||
@@ -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 ? (
|
|
||||||
<div className="flex w-full flex-col items-start rounded-md bg-white/5 px-6 p-6">
|
|
||||||
<p className="text-xl font-semibold">Public Key</p>
|
|
||||||
<div className="mt-4 flex items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400 p-2">
|
|
||||||
<p className="mr-4 break-all">{serviceAccount.publicKey}</p>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => copyServiceAccountIdToClipboard()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={isServiceAccountIdCopied ? faCheck : faCopy} />
|
|
||||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : <div />
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { CopyServiceAccountPublicKeySection } from "./CopyServiceAccountPublicKeySection";
|
|
||||||
@@ -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<typeof createProjectLevelPermissionSchema>;
|
|
||||||
|
|
||||||
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 | string>(undefined);
|
|
||||||
|
|
||||||
const {
|
|
||||||
control,
|
|
||||||
handleSubmit,
|
|
||||||
reset,
|
|
||||||
formState: { isSubmitting }
|
|
||||||
} = useForm<CreateProjectLevelPermissionForm>({
|
|
||||||
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 (
|
|
||||||
<div className="w-full bg-white/5 p-6">
|
|
||||||
<p className="mb-4 text-xl font-semibold">Project-Level Permissions</p>
|
|
||||||
<div className="mb-4 flex">
|
|
||||||
<div className="mr-4 flex-1">
|
|
||||||
<Input
|
|
||||||
value={searchPermissions}
|
|
||||||
onChange={(e) => setSearchPermissions(e.target.value)}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
|
||||||
placeholder="Search service account project-level permissions..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
onClick={() => {
|
|
||||||
handlePopUpOpen("addProjectLevelPermission");
|
|
||||||
reset();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add Permission
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
|
||||||
<THead>
|
|
||||||
<Tr>
|
|
||||||
<Th>Project</Th>
|
|
||||||
<Th>Environment</Th>
|
|
||||||
<Th>Read</Th>
|
|
||||||
<Th>Write</Th>
|
|
||||||
<Th aria-label="actions" />
|
|
||||||
</Tr>
|
|
||||||
</THead>
|
|
||||||
<TBody>
|
|
||||||
{isPermissionsLoading && (
|
|
||||||
<TableSkeleton columns={6} innerKey="service-account-project-level-permissions" />
|
|
||||||
)}
|
|
||||||
{!isPermissionsLoading &&
|
|
||||||
serviceAccountWorkspacePermissions &&
|
|
||||||
serviceAccountWorkspacePermissions.map(
|
|
||||||
({ _id, workspace, environment, read, write }) => {
|
|
||||||
const environmentName = workspace.environments.find(
|
|
||||||
(env) => env.slug === environment
|
|
||||||
)?.name;
|
|
||||||
return (
|
|
||||||
<Tr key={`service-account-project-level-permission-${_id}`} className="w-full">
|
|
||||||
<Td>{workspace.name}</Td>
|
|
||||||
<Td>{environmentName}</Td>
|
|
||||||
<Td>
|
|
||||||
<Checkbox id="isReadPermissionEnabled" isChecked={read} isDisabled>
|
|
||||||
{/**/}
|
|
||||||
</Checkbox>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Checkbox id="isWritePermissionEnabled" isChecked={write} isDisabled>
|
|
||||||
{/**/}
|
|
||||||
</Checkbox>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="delete"
|
|
||||||
colorSchema="danger"
|
|
||||||
onClick={() => handlePopUpOpen("removeProjectLevelPermission", { _id })}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
|
||||||
</IconButton>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
{!isPermissionsLoading && serviceAccountWorkspacePermissions?.length === 0 && (
|
|
||||||
<Tr>
|
|
||||||
<Td colSpan={7} className="py-6 text-center text-bunker-400">
|
|
||||||
<EmptyState title="No permissions found" icon={faKey} />
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
)}
|
|
||||||
</TBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
<Modal
|
|
||||||
isOpen={popUp?.addProjectLevelPermission?.isOpen}
|
|
||||||
onOpenChange={(isOpen) => {
|
|
||||||
handlePopUpToggle("addProjectLevelPermission", isOpen);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
title="Add a Project-Level Permission"
|
|
||||||
subTitle="The service account will be granted scoped access to the specified project and environment"
|
|
||||||
>
|
|
||||||
<form onSubmit={handleSubmit(onAddProjectLevelPermission)}>
|
|
||||||
{!isUserWorkspacesLoading && userWorkspaces && (
|
|
||||||
<>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
defaultValue=""
|
|
||||||
name="privateKey"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Service Account Private Key"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="workspace"
|
|
||||||
defaultValue={String(userWorkspaces?.[0]?._id)}
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl label="Project" errorText={error?.message}>
|
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => {
|
|
||||||
onChange(e);
|
|
||||||
setSelectedWorkspace(e);
|
|
||||||
}}
|
|
||||||
className="border-mine-shaft-500 w-full border"
|
|
||||||
>
|
|
||||||
{userWorkspaces && userWorkspaces.length > 0 ? (
|
|
||||||
userWorkspaces.map((userWorkspace) => {
|
|
||||||
return (
|
|
||||||
<SelectItem
|
|
||||||
value={userWorkspace._id}
|
|
||||||
key={`project-${userWorkspace._id}`}
|
|
||||||
>
|
|
||||||
{userWorkspace.name}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<SelectItem value="none" key="target-app-none">
|
|
||||||
No projects found
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="environment"
|
|
||||||
defaultValue={String(userWorkspaces?.[0]?.environments?.[0]?.slug)}
|
|
||||||
render={({ field: { onChange, ...field } }) => {
|
|
||||||
const environments =
|
|
||||||
userWorkspaces?.find(
|
|
||||||
/* eslint-disable-next-line no-underscore-dangle */
|
|
||||||
(userWorkspace) => userWorkspace._id === control?._formValues?.workspace
|
|
||||||
)?.environments ?? [];
|
|
||||||
return (
|
|
||||||
<FormControl label="Environment" className="mt-4">
|
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="border-mine-shaft-500 w-full border"
|
|
||||||
>
|
|
||||||
{environments.length > 0 ? (
|
|
||||||
environments.map((environment) => {
|
|
||||||
return (
|
|
||||||
<SelectItem
|
|
||||||
value={environment.slug}
|
|
||||||
key={`environment-${environment.slug}`}
|
|
||||||
>
|
|
||||||
{environment.name}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<SelectItem value="none" key="target-app-none">
|
|
||||||
No environments found
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="permissions"
|
|
||||||
defaultValue={{
|
|
||||||
read: true,
|
|
||||||
write: false
|
|
||||||
}}
|
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
||||||
const options = [
|
|
||||||
{
|
|
||||||
label: "Read (default)",
|
|
||||||
value: "read"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Write",
|
|
||||||
value: "write"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControl
|
|
||||||
label="Permissions"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
{options.map(({ label, value: optionValue }) => {
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
id={value[optionValue]}
|
|
||||||
key={optionValue}
|
|
||||||
className="data-[state=checked]:bg-primary"
|
|
||||||
isChecked={value[optionValue]}
|
|
||||||
isDisabled={optionValue === "read"}
|
|
||||||
onCheckedChange={(state) => {
|
|
||||||
onChange({
|
|
||||||
...value,
|
|
||||||
[optionValue]: state
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="mt-8 flex items-center">
|
|
||||||
<Button
|
|
||||||
className="mr-4"
|
|
||||||
type="submit"
|
|
||||||
isDisabled={isSubmitting}
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
<ModalClose asChild>
|
|
||||||
<Button variant="plain" colorSchema="secondary">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</ModalClose>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
<DeleteActionModal
|
|
||||||
isOpen={popUp.removeProjectLevelPermission.isOpen}
|
|
||||||
deleteKey="remove"
|
|
||||||
title="Do you want to remove this permission from the service account?"
|
|
||||||
onChange={(isOpen) => handlePopUpToggle("removeProjectLevelPermission", isOpen)}
|
|
||||||
onDeleteApproved={onRemoveProjectLevelPermission}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { SAProjectLevelPermissionsTable } from "./SAProjectLevelPermissionsTable";
|
|
||||||
@@ -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<typeof formSchema>;
|
|
||||||
|
|
||||||
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<FormData>({ 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 (
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
|
||||||
className="rounded-md bg-white/5 p-6"
|
|
||||||
>
|
|
||||||
<p className="mb-4 text-xl font-semibold">Name</p>
|
|
||||||
<div className="mb-2 w-full max-w-lg">
|
|
||||||
{!isServiceAccountLoading && (
|
|
||||||
<Controller
|
|
||||||
defaultValue=""
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
|
||||||
<Input placeholder="Type your service account name..." {...field} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
control={control}
|
|
||||||
name="name"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
color="mineshaft"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isDisabled={!isDirty || isSubmitting}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faCheck} />}
|
|
||||||
>
|
|
||||||
Save Changes
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { ServiceAccountNameChangeSection } from "./ServiceAccountNameChangeSection";
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export { CopyServiceAccountIDSection } from "./CopyServiceAccountIDSection";
|
|
||||||
export { CopyServiceAccountPublicKeySection } from "./CopyServiceAccountPublicKeySection";
|
|
||||||
export { SAProjectLevelPermissionsTable } from "./SAProjectLevelPermissionsTable";
|
|
||||||
export { ServiceAccountNameChangeSection } from "./ServiceAccountNameChangeSection";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { CreateServiceAccountPage } from "./CreateServiceAccountPage";
|
|
||||||
@@ -4,14 +4,13 @@ import {
|
|||||||
} from "@app/components/utilities/cryptography/crypto";
|
} from "@app/components/utilities/cryptography/crypto";
|
||||||
import { Checkbox } from "@app/components/v2";
|
import { Checkbox } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api";
|
import { useGetUserWsKey,useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api";
|
||||||
|
|
||||||
import getLatestFileKey from "../../../../../pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
export const E2EESection = () => {
|
export const E2EESection = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data: bot } = useGetWorkspaceBot(currentWorkspace?._id ?? "");
|
const { data: bot } = useGetWorkspaceBot(currentWorkspace?._id ?? "");
|
||||||
const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus();
|
const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus();
|
||||||
|
const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate bot for project by performing the following steps:
|
* Activate bot for project by performing the following steps:
|
||||||
@@ -25,14 +24,12 @@ export const E2EESection = () => {
|
|||||||
try {
|
try {
|
||||||
if (!currentWorkspace?._id) return;
|
if (!currentWorkspace?._id) return;
|
||||||
|
|
||||||
if (bot) {
|
if (bot && wsKey) {
|
||||||
// case: there is a bot
|
// case: there is a bot
|
||||||
|
|
||||||
if (!bot.isActive) {
|
if (!bot.isActive) {
|
||||||
// bot is not active -> activate bot
|
// bot is not active -> activate bot
|
||||||
const key = await getLatestFileKey({
|
|
||||||
workspaceId: currentWorkspace._id
|
|
||||||
});
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
if (!PRIVATE_KEY) {
|
if (!PRIVATE_KEY) {
|
||||||
@@ -40,9 +37,9 @@ export const E2EESection = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WORKSPACE_KEY = decryptAssymmetric({
|
const WORKSPACE_KEY = decryptAssymmetric({
|
||||||
ciphertext: key.latestKey.encryptedKey,
|
ciphertext: wsKey.encryptedKey,
|
||||||
nonce: key.latestKey.nonce,
|
nonce: wsKey.nonce,
|
||||||
publicKey: key.latestKey.sender.publicKey,
|
publicKey: wsKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLo
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { Button, Input } from "@app/components/v2";
|
import { Button, Input } from "@app/components/v2";
|
||||||
import { useGetCommonPasswords } from "@app/hooks/api";
|
import { useGetCommonPasswords } from "@app/hooks/api";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
|
import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
|
||||||
import ProjectService from "@app/services/ProjectService";
|
import ProjectService from "@app/services/ProjectService";
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
@@ -188,7 +188,7 @@ export const UserInfoSSOStep = ({
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]?._id;
|
const orgId = userOrgs[0]?._id;
|
||||||
const project = await ProjectService.initProject({
|
const project = await ProjectService.initProject({
|
||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
|
|||||||
Reference in New Issue
Block a user