Merge pull request #2021 from Infisical/feat/add-option-to-share-secrets-directly

feat: added option to share secret directly from main page
This commit is contained in:
Maidul Islam
2024-06-25 15:55:04 -04:00
committed by GitHub
6 changed files with 82 additions and 25 deletions

View File

@@ -7,6 +7,7 @@ import {
faCircleDot, faCircleDot,
faClock, faClock,
faPlus, faPlus,
faShare,
faTag faTag
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -57,6 +58,7 @@ type Props = {
) => Promise<void>; ) => Promise<void>;
tags: WsTag[]; tags: WsTag[];
onCreateTag: () => void; onCreateTag: () => void;
handleSecretShare: (value: string) => void;
}; };
export const SecretDetailSidebar = ({ export const SecretDetailSidebar = ({
@@ -69,7 +71,8 @@ export const SecretDetailSidebar = ({
tags, tags,
onCreateTag, onCreateTag,
environment, environment,
secretPath secretPath,
handleSecretShare
}: Props) => { }: Props) => {
const { const {
register, register,
@@ -381,7 +384,7 @@ export const SecretDetailSidebar = ({
rows={5} rows={5}
/> />
</FormControl> </FormControl>
<div className="my-2 mb-6 border-b border-mineshaft-600 pb-4"> <div className="my-2 mb-4 border-b border-mineshaft-600 pb-4">
<Controller <Controller
control={control} control={control}
name="skipMultilineEncoding" name="skipMultilineEncoding"
@@ -412,7 +415,17 @@ export const SecretDetailSidebar = ({
)} )}
/> />
</div> </div>
<div className="dark mb-4 flex-grow text-sm text-bunker-300"> <div className="ml-1 flex items-center space-x-2">
<Button
className="px-2 py-1"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => handleSecretShare(secret.valueOverride ?? secret.value)}
>
Share Secret
</Button>
</div>
<div className="dark mt-4 mb-4 flex-grow text-sm text-bunker-300">
<div className="mb-2">Version History</div> <div className="mb-2">Version History</div>
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]"> <div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
{secretVersion?.map(({ createdAt, value, id }, i) => ( {secretVersion?.map(({ createdAt, value, id }, i) => (

View File

@@ -61,6 +61,7 @@ type Props = {
onCreateTag: () => void; onCreateTag: () => void;
environment: string; environment: string;
secretPath: string; secretPath: string;
handleSecretShare: () => void;
}; };
export const SecretItem = memo( export const SecretItem = memo(
@@ -75,7 +76,8 @@ export const SecretItem = memo(
onCreateTag, onCreateTag,
onToggleSecretSelect, onToggleSecretSelect,
environment, environment,
secretPath secretPath,
handleSecretShare
}: Props) => { }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
@@ -420,8 +422,9 @@ export const SecretItem = memo(
<Tooltip <Tooltip
content={ content={
secretReminderRepeatDays && secretReminderRepeatDays > 0 secretReminderRepeatDays && secretReminderRepeatDays > 0
? `Every ${secretReminderRepeatDays} day${Number(secretReminderRepeatDays) > 1 ? "s" : "" ? `Every ${secretReminderRepeatDays} day${
} Number(secretReminderRepeatDays) > 1 ? "s" : ""
}
` `
: "Reminder" : "Reminder"
} }
@@ -461,6 +464,20 @@ export const SecretItem = memo(
</PopoverTrigger> </PopoverTrigger>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<IconButton
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
variant="plain"
size="md"
ariaLabel="share-secret"
onClick={handleSecretShare}
>
<Tooltip content="Share Secret">
<FontAwesomeSymbol
className="h-3.5 w-3.5"
symbolName={FontAwesomeSpriteName.ShareSecret}
/>
</Tooltip>
</IconButton>
<PopoverContent <PopoverContent
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl" className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl"
sticky="always" sticky="always"

View File

@@ -13,6 +13,7 @@ import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types"; import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries"; import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types"; import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal";
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store"; import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types"; import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
@@ -95,7 +96,8 @@ export const SecretListView = ({
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
"deleteSecret", "deleteSecret",
"secretDetail", "secretDetail",
"createTag" "createTag",
"createSharedSecret"
] as const); ] as const);
// strip of side effect queries // strip of side effect queries
@@ -365,6 +367,11 @@ export const SecretListView = ({
onDeleteSecret={onDeleteSecret} onDeleteSecret={onDeleteSecret}
onDetailViewSecret={onDetailViewSecret} onDetailViewSecret={onDetailViewSecret}
onCreateTag={onCreateTag} onCreateTag={onCreateTag}
handleSecretShare={() =>
handlePopUpOpen("createSharedSecret", {
value: secret.valueOverride ?? secret.value
})
}
/> />
))} ))}
</div> </div>
@@ -391,11 +398,18 @@ export const SecretListView = ({
onSaveSecret={handleSaveSecret} onSaveSecret={handleSaveSecret}
tags={wsTags} tags={wsTags}
onCreateTag={() => handlePopUpOpen("createTag")} onCreateTag={() => handlePopUpOpen("createTag")}
handleSecretShare={(value: string) => handlePopUpOpen("createSharedSecret", { value })}
/> />
<CreateTagModal <CreateTagModal
isOpen={popUp.createTag.isOpen} isOpen={popUp.createTag.isOpen}
onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)} onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)}
/> />
<AddShareSecretModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
isPublic={false}
inModal
/>
</> </>
); );
}; };

View File

@@ -10,6 +10,7 @@ import {
faCopy, faCopy,
faEllipsis, faEllipsis,
faKey, faKey,
faShare,
faTags faTags
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { z } from "zod"; import { z } from "zod";
@@ -66,7 +67,8 @@ export enum FontAwesomeSpriteName {
Override = "secret-override", Override = "secret-override",
Close = "close", Close = "close",
CheckedCircle = "check-circle", CheckedCircle = "check-circle",
ReplicatedSecretKey = "secret-replicated" ReplicatedSecretKey = "secret-replicated",
ShareSecret = "share-secret"
} }
// this is an optimization technique // this is an optimization technique
@@ -82,5 +84,6 @@ export const FontAwesomeSpriteSymbols = [
{ icon: faCodeBranch, symbol: FontAwesomeSpriteName.Override }, { icon: faCodeBranch, symbol: FontAwesomeSpriteName.Override },
{ icon: faClose, symbol: FontAwesomeSpriteName.Close }, { icon: faClose, symbol: FontAwesomeSpriteName.Close },
{ icon: faCheckCircle, symbol: FontAwesomeSpriteName.CheckedCircle }, { icon: faCheckCircle, symbol: FontAwesomeSpriteName.CheckedCircle },
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey } { icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey },
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret }
]; ];

View File

@@ -6,14 +6,7 @@ import * as yup from "yup";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto"; import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
import { import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
Button,
FormControl,
Input,
ModalClose,
Select,
SelectItem
} from "@app/components/v2";
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing"; import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
const schema = yup.object({ const schema = yup.object({
@@ -31,7 +24,8 @@ export const AddShareSecretForm = ({
handleSubmit, handleSubmit,
control, control,
isSubmitting, isSubmitting,
setNewSharedSecret setNewSharedSecret,
isInputDisabled
}: { }: {
isPublic: boolean; isPublic: boolean;
inModal: boolean; inModal: boolean;
@@ -39,6 +33,7 @@ export const AddShareSecretForm = ({
control: any; control: any;
isSubmitting: boolean; isSubmitting: boolean;
setNewSharedSecret: (value: string) => void; setNewSharedSecret: (value: string) => void;
isInputDisabled?: boolean;
}) => { }) => {
const publicSharedSecretCreator = useCreatePublicSharedSecret(); const publicSharedSecretCreator = useCreatePublicSharedSecret();
const privateSharedSecretCreator = useCreateSharedSecret(); const privateSharedSecretCreator = useCreateSharedSecret();
@@ -124,12 +119,13 @@ export const AddShareSecretForm = ({
}; };
return ( return (
<form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}> <form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}>
<div className={`${!inModal && "border border-mineshaft-600 bg-mineshaft-800 rounded-md p-6"}`}> <div
className={`${!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"}`}
>
<div className="mb-4"> <div className="mb-4">
<Controller <Controller
control={control} control={control}
name="value" name="value"
defaultValue=""
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Shared Secret" label="Shared Secret"
@@ -137,16 +133,17 @@ export const AddShareSecretForm = ({
errorText={error?.message} errorText={error?.message}
> >
<textarea <textarea
disabled={isInputDisabled}
placeholder="Enter sensitive data to share via an encrypted link..." placeholder="Enter sensitive data to share via an encrypted link..."
{...field} {...field}
className="py-1.5 w-full h-40 placeholder:text-mineshaft-400 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/30 focus:border-primary-400/50 outline-none border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]" className="h-40 min-h-[70px] w-full rounded-md border border-mineshaft-600 bg-mineshaft-900 py-1.5 px-2 text-bunker-300 outline-none transition-all placeholder:text-mineshaft-400 hover:border-primary-400/30 focus:border-primary-400/50 group-hover:mr-2"
/> />
</FormControl> </FormControl>
)} )}
/> />
</div> </div>
<div className="flex w-full flex-row justify-center"> <div className="flex w-full flex-row justify-center">
<div className="hidden sm:block sm:w-2/6 flex"> <div className="flex hidden sm:block sm:w-2/6">
<Controller <Controller
control={control} control={control}
name="expiresAfterViews" name="expiresAfterViews"
@@ -163,12 +160,12 @@ export const AddShareSecretForm = ({
)} )}
/> />
</div> </div>
<div className="hidden sm:flex sm:w-1/7 items-center justify-center px-2 mx-auto"> <div className="sm:w-1/7 mx-auto hidden items-center justify-center px-2 sm:flex">
<p className="px-4 text-sm text-gray-400">OR</p> <p className="px-4 text-sm text-gray-400">OR</p>
</div> </div>
<div className="w-full sm:w-3/6 flex justify-end"> <div className="flex w-full justify-end sm:w-3/6">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="flex w-full pr-2 justify-center"> <div className="flex w-full justify-center pr-2">
<Controller <Controller
control={control} control={control}
name="expiresInValue" name="expiresInValue"

View File

@@ -34,6 +34,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
control, control,
reset, reset,
handleSubmit, handleSubmit,
setValue,
formState: { isSubmitting } formState: { isSubmitting }
} = useForm<FormData>({ } = useForm<FormData>({
resolver: yupResolver(schema) resolver: yupResolver(schema)
@@ -45,6 +46,8 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
initialState: false initialState: false
}); });
const [isSecretInputDisabled, setIsSecretInputDisabled] = useState(false);
const copyUrlToClipboard = () => { const copyUrlToClipboard = () => {
navigator.clipboard.writeText(newSharedSecret); navigator.clipboard.writeText(newSharedSecret);
setIsUrlCopied(true); setIsUrlCopied(true);
@@ -55,6 +58,13 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
} }
}, [isUrlCopied]); }, [isUrlCopied]);
useEffect(() => {
if (popUp.createSharedSecret.data) {
setValue("value", (popUp.createSharedSecret.data as { value: string }).value);
setIsSecretInputDisabled(true);
}
}, [popUp.createSharedSecret.data]);
// eslint-disable-next-line no-nested-ternary // eslint-disable-next-line no-nested-ternary
return inModal ? ( return inModal ? (
<Modal <Modal
@@ -63,6 +73,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
handlePopUpToggle("createSharedSecret", open); handlePopUpToggle("createSharedSecret", open);
reset(); reset();
setNewSharedSecret(""); setNewSharedSecret("");
setIsSecretInputDisabled(false);
}} }}
> >
<ModalContent <ModalContent
@@ -77,6 +88,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
setNewSharedSecret={setNewSharedSecret} setNewSharedSecret={setNewSharedSecret}
isInputDisabled={isSecretInputDisabled}
/> />
) : ( ) : (
<ViewAndCopySharedSecret <ViewAndCopySharedSecret
@@ -96,6 +108,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
setNewSharedSecret={setNewSharedSecret} setNewSharedSecret={setNewSharedSecret}
isInputDisabled={isSecretInputDisabled}
/> />
) : ( ) : (
<ViewAndCopySharedSecret <ViewAndCopySharedSecret