mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #1152 from akhilmhdh/fix/key-rogue
fix: changed 2 fold operation of member workspace to one api call
This commit is contained in:
@@ -23,7 +23,7 @@ export * from "./secrets/types";
|
||||
export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types";
|
||||
export type { SubscriptionPlan } from "./subscriptions/types";
|
||||
export type { WsTag } from "./tags/types";
|
||||
export type { AddUserToWsDTO, AddUserToWsRes, OrgUser, TWorkspaceUser, User } from "./users/types";
|
||||
export type { AddUserToWsDTO, OrgUser, TWorkspaceUser, User } from "./users/types";
|
||||
export type { TWebhook } from "./webhooks/types";
|
||||
export type {
|
||||
CreateEnvironmentDTO,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export { useAddUserToWs } from "./mutation";
|
||||
export {
|
||||
fetchOrgUsers,
|
||||
useAddUserToOrg,
|
||||
useAddUserToWs,
|
||||
useCreateAPIKey,
|
||||
useDeleteAPIKey,
|
||||
useDeleteOrgMembership,
|
||||
@@ -20,4 +20,4 @@ export {
|
||||
useUpdateMfaEnabled,
|
||||
useUpdateOrgUserRole,
|
||||
useUpdateUserAuthMethods
|
||||
} from "./queries";
|
||||
} from "./queries";
|
||||
|
||||
47
frontend/src/hooks/api/users/mutation.tsx
Normal file
47
frontend/src/hooks/api/users/mutation.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
} from "@app/components/utilities/cryptography/crypto";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import { AddUserToWsDTO } from "./types";
|
||||
|
||||
export const useAddUserToWs = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, AddUserToWsDTO>({
|
||||
mutationFn: async ({ workspaceId, members, decryptKey, userPrivateKey }) => {
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: decryptKey.encryptedKey,
|
||||
nonce: decryptKey.nonce,
|
||||
publicKey: decryptKey.sender.publicKey,
|
||||
privateKey: userPrivateKey
|
||||
});
|
||||
|
||||
const newWsMembers = members.map(({ orgMembershipId, userPublicKey }) => {
|
||||
const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: userPublicKey,
|
||||
privateKey: userPrivateKey
|
||||
});
|
||||
|
||||
return {
|
||||
orgMembershipId,
|
||||
workspaceEncryptedKey: inviteeCipherText,
|
||||
workspaceEncryptedNonce: inviteeNonce
|
||||
};
|
||||
});
|
||||
const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceId}/memberships`, {
|
||||
members: newWsMembers
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { workspaceId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(workspaceId));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,19 +1,11 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
} from "@app/components/utilities/cryptography/crypto";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { setAuthToken } from "@app/reactQuery";
|
||||
|
||||
import { APIKeyDataV2 } from "../apiKeys/types";
|
||||
import { useUploadWsKey } from "../keys/queries";
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import {
|
||||
AddUserToOrgDTO,
|
||||
AddUserToWsDTO,
|
||||
AddUserToWsRes,
|
||||
APIKeyData,
|
||||
AuthMethod,
|
||||
CreateAPIKeyRes,
|
||||
@@ -49,7 +41,9 @@ export const useDeleteUser = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data: { user } } = await apiRequest.delete<{ user: User }>("/api/v2/users/me");
|
||||
const {
|
||||
data: { user }
|
||||
} = await apiRequest.delete<{ user: User }>("/api/v2/users/me");
|
||||
return user;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -134,43 +128,7 @@ export const useGetOrgUsers = (orgId: string) =>
|
||||
});
|
||||
|
||||
// mutation
|
||||
export const useAddUserToWs = () => {
|
||||
const uploadWsKey = useUploadWsKey();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ data: AddUserToWsRes }, {}, AddUserToWsDTO>({
|
||||
mutationFn: ({ email, workspaceId }) =>
|
||||
apiRequest.post(`/api/v1/workspace/${workspaceId}/invite-signup`, { email }),
|
||||
onSuccess: ({ data }, { workspaceId }) => {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
if (!PRIVATE_KEY) return;
|
||||
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: data.latestKey.encryptedKey,
|
||||
nonce: data.latestKey.nonce,
|
||||
publicKey: data.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
|
||||
const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: data.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
|
||||
uploadWsKey.mutate({
|
||||
encryptedKey: inviteeCipherText,
|
||||
nonce: inviteeNonce,
|
||||
userId: data.invitee._id,
|
||||
workspaceId
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(workspaceId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TODO(akhilmhdh): move all mutation to mutation file
|
||||
export const useAddUserToOrg = () => {
|
||||
const queryClient = useQueryClient();
|
||||
type Response = {
|
||||
@@ -254,12 +212,11 @@ export const useLogoutUser = () => {
|
||||
localStorage.removeItem("PRIVATE_KEY");
|
||||
localStorage.removeItem("orgData.id");
|
||||
localStorage.removeItem("projectData.id");
|
||||
|
||||
|
||||
queryClient.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export const useGetMyIp = () => {
|
||||
return useQuery({
|
||||
@@ -272,7 +229,8 @@ export const useGetMyIp = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetMyAPIKeys = () => { // TODO: deprecate (moving to API Key V2)
|
||||
export const useGetMyAPIKeys = () => {
|
||||
// TODO: deprecate (moving to API Key V2)
|
||||
return useQuery({
|
||||
queryKey: userKeys.myAPIKeys,
|
||||
queryFn: async () => {
|
||||
@@ -287,14 +245,17 @@ export const useGetMyAPIKeysV2 = () => {
|
||||
return useQuery({
|
||||
queryKey: userKeys.myAPIKeysV2,
|
||||
queryFn: async () => {
|
||||
const { data: { apiKeyData } } = await apiRequest.get<{ apiKeyData: APIKeyDataV2[] }>("/api/v3/users/me/api-keys");
|
||||
const {
|
||||
data: { apiKeyData }
|
||||
} = await apiRequest.get<{ apiKeyData: APIKeyDataV2[] }>("/api/v3/users/me/api-keys");
|
||||
return apiKeyData;
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateAPIKey = () => { // TODO: deprecate (moving to API Key V2)
|
||||
export const useCreateAPIKey = () => {
|
||||
// TODO: deprecate (moving to API Key V2)
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ name, expiresIn }: { name: string; expiresIn: number }) => {
|
||||
@@ -311,7 +272,8 @@ export const useCreateAPIKey = () => { // TODO: deprecate (moving to API Key V2)
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteAPIKey = () => { // TODO: deprecate (moving to API Key V2)
|
||||
export const useDeleteAPIKey = () => {
|
||||
// TODO: deprecate (moving to API Key V2)
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (apiKeyDataId: string) => {
|
||||
|
||||
@@ -5,9 +5,9 @@ export enum AuthMethod {
|
||||
GOOGLE = "google",
|
||||
GITHUB = "github",
|
||||
GITLAB = "gitlab",
|
||||
OKTA_SAML = "okta-saml",
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml"
|
||||
OKTA_SAML = "okta-saml",
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml"
|
||||
}
|
||||
|
||||
export type User = {
|
||||
@@ -53,12 +53,12 @@ export type TWorkspaceUser = OrgUser;
|
||||
|
||||
export type AddUserToWsDTO = {
|
||||
workspaceId: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type AddUserToWsRes = {
|
||||
invitee: OrgUser["user"];
|
||||
latestKey: UserWsKeyPair;
|
||||
decryptKey: UserWsKeyPair;
|
||||
userPrivateKey: string;
|
||||
members: {
|
||||
orgMembershipId: string;
|
||||
userPublicKey: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type UpdateOrgUserRoleDTO = {
|
||||
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
useRegisterUserAction,
|
||||
useUploadWsKey
|
||||
} from "@app/hooks/api";
|
||||
import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
|
||||
import { CreateOrgModal } from "@app/views/Org/components";
|
||||
|
||||
interface LayoutProps {
|
||||
@@ -249,11 +250,19 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
if (addMembers) {
|
||||
// not using hooks because need at this point only
|
||||
const orgUsers = await fetchOrgUsers(currentOrg._id);
|
||||
orgUsers.forEach(({ status, user: orgUser }) => {
|
||||
// skip if status of org user is not accepted
|
||||
// this orgUser is the person who created the ws
|
||||
if (status !== "accepted" || user.email === orgUser.email) return;
|
||||
addWsUser.mutate({ email: orgUser.email, workspaceId: newWorkspaceId });
|
||||
const decryptKey = await fetchUserWsKey(newWorkspaceId);
|
||||
await addWsUser.mutateAsync({
|
||||
workspaceId: newWorkspaceId,
|
||||
decryptKey,
|
||||
userPrivateKey: PRIVATE_KEY,
|
||||
members: orgUsers
|
||||
.filter(
|
||||
({ status, user: orgUser }) => status === "accepted" && user.email !== orgUser.email
|
||||
)
|
||||
.map(({ user: orgUser, _id: orgMembershipId }) => ({
|
||||
userPublicKey: orgUser.publicKey,
|
||||
orgMembershipId
|
||||
}))
|
||||
});
|
||||
}
|
||||
createNotification({ text: "Workspace created", type: "success" });
|
||||
@@ -288,13 +297,16 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
<div className="flex h-5 w-5 items-center justify-center rounded-md bg-primary text-sm">
|
||||
{currentOrg?.name.charAt(0)}
|
||||
</div>
|
||||
<div className="pl-3 text-sm text-mineshaft-100">
|
||||
{currentOrg?.name}{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleDown}
|
||||
className="pl-1 pt-1 text-xs text-mineshaft-300"
|
||||
/>
|
||||
<div
|
||||
className="pl-2 text-sm text-mineshaft-100 text-ellipsis overflow-hidden"
|
||||
style={{ maxWidth: "140px" }}
|
||||
>
|
||||
{currentOrg?.name}
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleDown}
|
||||
className="pl-1 pt-1 text-xs text-mineshaft-300"
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
@@ -346,7 +358,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
asChild
|
||||
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
|
||||
>
|
||||
<div className="child flex h-6 w-6 items-center justify-center rounded-full bg-mineshaft pr-1 text-xs text-mineshaft-300 hover:bg-mineshaft-500">
|
||||
<div
|
||||
className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
|
||||
style={{ fontSize: "11px", width: "26px", height: "26px" }}
|
||||
>
|
||||
{user?.firstName?.charAt(0)}
|
||||
{user?.lastName && user?.lastName?.charAt(0)}
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@ import * as yup from "yup";
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
||||
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -58,11 +59,10 @@ import {
|
||||
useRegisterUserAction,
|
||||
useUploadWsKey
|
||||
} from "@app/hooks/api";
|
||||
import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto";
|
||||
|
||||
const features = [
|
||||
{
|
||||
_id: 0,
|
||||
@@ -527,11 +527,19 @@ const OrganizationPage = withPermission(
|
||||
if (addMembers) {
|
||||
// not using hooks because need at this point only
|
||||
const orgUsers = await fetchOrgUsers(currentOrg);
|
||||
orgUsers.forEach(({ status, user: orgUser }) => {
|
||||
// skip if status of org user is not accepted
|
||||
// this orgUser is the person who created the ws
|
||||
if (status !== "accepted" || user.email === orgUser.email) return;
|
||||
addWsUser.mutate({ email: orgUser.email, workspaceId: newWorkspaceId });
|
||||
const decryptKey = await fetchUserWsKey(newWorkspaceId);
|
||||
await addWsUser.mutateAsync({
|
||||
workspaceId: newWorkspaceId,
|
||||
decryptKey,
|
||||
userPrivateKey: PRIVATE_KEY,
|
||||
members: orgUsers
|
||||
.filter(
|
||||
({ status, user: orgUser }) => status === "accepted" && user.email !== orgUser.email
|
||||
)
|
||||
.map(({ user: orgUser, _id: orgMembershipId }) => ({
|
||||
userPublicKey: orgUser.publicKey,
|
||||
orgMembershipId
|
||||
}))
|
||||
});
|
||||
}
|
||||
createNotification({ text: "Workspace created", type: "success" });
|
||||
@@ -682,7 +690,10 @@ const OrganizationPage = withPermission(
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!(new Date().getTime() - new Date(user?.createdAt).getTime() < 30 * 24 * 60 * 60 * 1000) && (
|
||||
{!(
|
||||
new Date().getTime() - new Date(user?.createdAt).getTime() <
|
||||
30 * 24 * 60 * 60 * 1000
|
||||
) && (
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
||||
<p className="mr-4 mb-4 font-semibold text-white">Onboarding Guide</p>
|
||||
<div className="mb-3 grid w-full grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
|
||||
@@ -59,7 +59,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const addMemberFormSchema = z.object({
|
||||
email: z.string().email().trim()
|
||||
orgMembershipId: z.string().trim()
|
||||
});
|
||||
|
||||
type TAddMemberForm = z.infer<typeof addMemberFormSchema>;
|
||||
@@ -100,13 +100,25 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => {
|
||||
const { mutateAsync: removeUserFromWorkspace } = useDeleteUserFromWorkspace();
|
||||
const { mutateAsync: updateUserWorkspaceRole } = useUpdateUserWorkspaceRole();
|
||||
|
||||
const onAddMember = async ({ email }: TAddMemberForm) => {
|
||||
const onAddMember = async ({ orgMembershipId }: TAddMemberForm) => {
|
||||
if (!currentOrg?._id) return;
|
||||
// TODO(akhilmhdh): Move to memory storage
|
||||
const userPrivateKey = localStorage.getItem("PRIVATE_KEY");
|
||||
if (!userPrivateKey || !wsKey) {
|
||||
createNotification({
|
||||
text: "Failed to find private key. Try re-login"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const orgUser = (orgUsers || []).find(({ _id }) => _id === orgMembershipId);
|
||||
if (!orgUser) return;
|
||||
|
||||
try {
|
||||
await addUserToWorkspace({
|
||||
email,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
userPrivateKey,
|
||||
decryptKey: wsKey,
|
||||
members: [{ orgMembershipId, userPublicKey: orgUser.user.publicKey }]
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully invited user to the organization.",
|
||||
@@ -365,7 +377,7 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => {
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
||||
name="email"
|
||||
name="orgMembershipId"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Select
|
||||
@@ -376,7 +388,7 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => {
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{filteredOrgUsers.map(({ _id: orgUserId, user: u }) => (
|
||||
<SelectItem value={u?.email} key={`org-membership-join-${orgUserId}`}>
|
||||
<SelectItem value={orgUserId} key={`org-membership-join-${orgUserId}`}>
|
||||
{u?.email}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user